mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
use def_command
This commit is contained in:
parent
5ccbb9719f
commit
cfd19062fd
37 changed files with 1578 additions and 1594 deletions
|
|
@ -10,6 +10,8 @@ mod app_view; pub use self::app_view::*;
|
|||
/// Total state
|
||||
#[derive(Default, Debug)]
|
||||
pub struct App {
|
||||
/// Base color.
|
||||
pub color: ItemTheme,
|
||||
/// Must not be dropped for the duration of the process
|
||||
pub jack: Jack<'static>,
|
||||
/// Display size
|
||||
|
|
@ -20,26 +22,24 @@ pub struct App {
|
|||
pub config: Config,
|
||||
/// Currently selected mode
|
||||
pub mode: Arc<Mode<Arc<str>>>,
|
||||
/// Contains the currently edited musical arrangement
|
||||
pub project: Arrangement,
|
||||
/// Contains all recently created clips.
|
||||
pub pool: Pool,
|
||||
/// Undo history
|
||||
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
||||
/// Dialog overlay
|
||||
pub dialog: Dialog,
|
||||
/// Base color.
|
||||
pub color: ItemTheme,
|
||||
/// Contains all recently created clips.
|
||||
pub pool: Pool,
|
||||
/// Contains the currently edited musical arrangement
|
||||
pub project: Arrangement,
|
||||
}
|
||||
/// Various possible dialog modes.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub enum Dialog {
|
||||
#[default] None,
|
||||
Help(usize),
|
||||
Menu(usize),
|
||||
Menu(usize, usize),
|
||||
Device(usize),
|
||||
Message(Arc<str>),
|
||||
Browser(BrowserTarget, Arc<Browser>),
|
||||
Browse(BrowseTarget, Arc<Browse>),
|
||||
Options,
|
||||
}
|
||||
has!(Jack<'static>: |self: App|self.jack);
|
||||
|
|
@ -61,68 +61,9 @@ maybe_has!(Scene: |self: App| { MaybeHas::<Scene>::get(&self.project) };
|
|||
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
||||
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
||||
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||
pub fn view_nil (_: &App) -> Box<dyn Render<TuiOut>> {
|
||||
Box::new(Fill::xy("·"))
|
||||
}
|
||||
content!(TuiOut:|self: App|Fill::xy(Stack::above(|add|{
|
||||
for dsl in self.mode.view.iter() { add(&Fill::xy(self.view(dsl.as_ref()))); }
|
||||
})));
|
||||
impl App {
|
||||
fn view <D: Dsl> (&self, index: D) -> Box<dyn Render<TuiOut>> {
|
||||
match index.src() {
|
||||
Ok(Some(src)) => render_dsl(self, src),
|
||||
Ok(None) => Box::new(Tui::fg(Color::Rgb(192, 192, 192), "empty view")),
|
||||
Err(e) => Box::new(format!("{e}")),
|
||||
}
|
||||
}
|
||||
pub fn update_clock (&self) {
|
||||
ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80)
|
||||
}
|
||||
}
|
||||
fn render_dsl <'t> (
|
||||
state: &'t impl DslNs<'t, Box<dyn Render<TuiOut>>>,
|
||||
src: &str
|
||||
) -> Box<dyn Render<TuiOut>> {
|
||||
let err: Option<Box<dyn Error>> = match state.from(&src) {
|
||||
Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e),
|
||||
};
|
||||
let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0));
|
||||
let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0));
|
||||
let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0));
|
||||
let bg = Color::Rgb(24, 0, 0);
|
||||
Box::new(col! {
|
||||
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▄")))),
|
||||
Tui::bg(bg, col! {
|
||||
Fill::x(Bsp::e(
|
||||
Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")),
|
||||
Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))),
|
||||
)),
|
||||
Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))),
|
||||
}),
|
||||
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▀")))),
|
||||
})
|
||||
}
|
||||
handle!(TuiIn:|self: App, input|{
|
||||
for id in self.mode.keys.iter() {
|
||||
if let Some(event_map) = self.config.binds.read().unwrap().get(id.as_ref()) {
|
||||
if let Some(bindings) = event_map.query(input.event()) {
|
||||
for binding in bindings {
|
||||
for command in binding.commands.iter() {
|
||||
let command: Option<AppCommand> = self.from(command)?;
|
||||
if let Some(command) = command {
|
||||
panic!("{command:?}");
|
||||
}
|
||||
}
|
||||
panic!("{binding:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
});
|
||||
impl Dialog {
|
||||
pub fn menu_selected (&self) -> Option<usize> {
|
||||
if let Self::Menu(selected) = self { Some(*selected) } else { None }
|
||||
if let Self::Menu(selected, _) = self { Some(*selected) } else { None }
|
||||
}
|
||||
pub fn device_kind (&self) -> Option<usize> {
|
||||
if let Self::Device(index) = self { Some(*index) } else { None }
|
||||
|
|
@ -136,10 +77,10 @@ impl Dialog {
|
|||
pub fn message (&self) -> Option<&str> {
|
||||
todo!()
|
||||
}
|
||||
pub fn browser (&self) -> Option<&Arc<Browser>> {
|
||||
pub fn browser (&self) -> Option<&Arc<Browse>> {
|
||||
todo!()
|
||||
}
|
||||
pub fn browser_target (&self) -> Option<&BrowserTarget> {
|
||||
pub fn browser_target (&self) -> Option<&BrowseTarget> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -189,8 +130,8 @@ impl App {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub fn browser (&self) -> Option<&Browser> {
|
||||
if let Dialog::Browser(_, ref b) = self.dialog { Some(b) } else { None }
|
||||
pub fn browser (&self) -> Option<&Browse> {
|
||||
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
|
||||
}
|
||||
pub fn device_pick (&mut self, index: usize) {
|
||||
self.dialog = Dialog::Device(index);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,62 @@
|
|||
use crate::*;
|
||||
|
||||
handle!(TuiIn:|self: App, input|{
|
||||
let mut commands = vec![];
|
||||
for id in self.mode.keys.iter() {
|
||||
if let Some(event_map) = self.config.binds.clone().read().unwrap().get(id.as_ref()) {
|
||||
if let Some(bindings) = event_map.query(input.event()) {
|
||||
for binding in bindings {
|
||||
for command in binding.commands.iter() {
|
||||
if let Some(command) = self.from(command)? as Option<AppCommand> {
|
||||
commands.push(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for command in commands.into_iter() {
|
||||
let result = command.execute(self);
|
||||
match result {
|
||||
Ok(undo) => {
|
||||
self.history.push((command, undo));
|
||||
},
|
||||
Err(e) => {
|
||||
self.history.push((command, None));
|
||||
return Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
});
|
||||
|
||||
impl<'t> DslNs<'t, AppCommand> for App {
|
||||
dsl_exprs!(|app| -> AppCommand { /* TODO */ });
|
||||
dsl_words!(|app| -> AppCommand {
|
||||
"x/inc" => todo!(),
|
||||
"x/dec" => todo!(),
|
||||
"y/inc" => todo!(),
|
||||
"y/dec" => todo!(),
|
||||
"y/inc" => match app.dialog {
|
||||
Dialog::Menu(index, count) => AppCommand::SetDialog {
|
||||
dialog: Dialog::Menu(if count > 0 {
|
||||
(index + 1) % count
|
||||
} else { 0 }, count)
|
||||
},
|
||||
_ => todo!(),
|
||||
},
|
||||
"y/dec" => match app.dialog {
|
||||
Dialog::Menu(index, count) => AppCommand::SetDialog {
|
||||
dialog: Dialog::Menu(if count > 0 {
|
||||
index.overflowing_sub(1).0.min(count.saturating_sub(1))
|
||||
} else { 0 }, count)
|
||||
},
|
||||
_ => todo!(),
|
||||
},
|
||||
"confirm" => todo!(),
|
||||
});
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum AppCommand { /* TODO */ }
|
||||
|
||||
#[tengri_proc::command(Option<Dialog>)]
|
||||
impl DialogCommand {
|
||||
fn open (dialog: &mut Option<Dialog>, new: Dialog) -> Perhaps<Self> {
|
||||
*dialog = Some(new);
|
||||
Ok(None)
|
||||
}
|
||||
fn close (dialog: &mut Option<Dialog>) -> Perhaps<Self> {
|
||||
*dialog = None;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
def_command!(AppCommand: |app: App| {
|
||||
SetDialog { dialog: Dialog } =>
|
||||
swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog }),
|
||||
});
|
||||
|
||||
//AppCommand => {
|
||||
//("x/inc" /
|
||||
|
|
|
|||
|
|
@ -43,19 +43,19 @@ impl<'t> DslNs<'t, Dialog> for App {
|
|||
":dialog/device/prev" => Dialog::Device(0),
|
||||
":dialog/device/next" => Dialog::Device(0),
|
||||
":dialog/help" => Dialog::Help(0),
|
||||
":dialog/menu" => Dialog::Menu(0),
|
||||
":dialog/save" => Dialog::Browser(BrowserTarget::SaveProject,
|
||||
Browser::new(None).unwrap().into()),
|
||||
":dialog/load" => Dialog::Browser(BrowserTarget::LoadProject,
|
||||
Browser::new(None).unwrap().into()),
|
||||
":dialog/import/clip" => Dialog::Browser(BrowserTarget::ImportClip(Default::default()),
|
||||
Browser::new(None).unwrap().into()),
|
||||
":dialog/export/clip" => Dialog::Browser(BrowserTarget::ExportClip(Default::default()),
|
||||
Browser::new(None).unwrap().into()),
|
||||
":dialog/import/sample" => Dialog::Browser(BrowserTarget::ImportSample(Default::default()),
|
||||
Browser::new(None).unwrap().into()),
|
||||
":dialog/export/sample" => Dialog::Browser(BrowserTarget::ExportSample(Default::default()),
|
||||
Browser::new(None).unwrap().into()),
|
||||
":dialog/menu" => Dialog::Menu(0, 0),
|
||||
":dialog/save" => Dialog::Browse(BrowseTarget::SaveProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
":dialog/load" => Dialog::Browse(BrowseTarget::LoadProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
":dialog/import/clip" => Dialog::Browse(BrowseTarget::ImportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":dialog/export/clip" => Dialog::Browse(BrowseTarget::ExportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":dialog/import/sample" => Dialog::Browse(BrowseTarget::ImportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,49 @@
|
|||
use crate::*;
|
||||
|
||||
pub fn view_nil (_: &App) -> Box<dyn Render<TuiOut>> {
|
||||
Box::new(Fill::xy("·"))
|
||||
}
|
||||
|
||||
content!(TuiOut:|self: App|Fill::xy(Stack::above(|add|{
|
||||
for dsl in self.mode.view.iter() { add(&Fill::xy(self.view(dsl.as_ref()))); }
|
||||
})));
|
||||
|
||||
impl App {
|
||||
fn view <D: Dsl> (&self, index: D) -> Box<dyn Render<TuiOut>> {
|
||||
match index.src() {
|
||||
Ok(Some(src)) => render_dsl(self, src),
|
||||
Ok(None) => Box::new(Tui::fg(Color::Rgb(192, 192, 192), "empty view")),
|
||||
Err(e) => Box::new(format!("{e}")),
|
||||
}
|
||||
}
|
||||
pub fn update_clock (&self) {
|
||||
ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80)
|
||||
}
|
||||
}
|
||||
fn render_dsl <'t> (
|
||||
state: &'t impl DslNs<'t, Box<dyn Render<TuiOut>>>,
|
||||
src: &str
|
||||
) -> Box<dyn Render<TuiOut>> {
|
||||
let err: Option<Box<dyn Error>> = match state.from(&src) {
|
||||
Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e),
|
||||
};
|
||||
let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0));
|
||||
let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0));
|
||||
let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0));
|
||||
let bg = Color::Rgb(24, 0, 0);
|
||||
Box::new(col! {
|
||||
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▄")))),
|
||||
Tui::bg(bg, col! {
|
||||
Fill::x(Bsp::e(
|
||||
Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")),
|
||||
Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))),
|
||||
)),
|
||||
Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))),
|
||||
}),
|
||||
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▀")))),
|
||||
})
|
||||
}
|
||||
|
||||
impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||
dsl_exprs!(|app| -> Box<dyn Render<TuiOut>> {
|
||||
"text" (tail: Arc<str>) => Box::new(tail),
|
||||
|
|
@ -71,12 +115,12 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
|||
)))),
|
||||
":browse/title" => Box::new(Fill::x(Align::w(FieldV(Default::default(),
|
||||
match app.dialog.browser_target().unwrap() {
|
||||
BrowserTarget::SaveProject => "Save project:",
|
||||
BrowserTarget::LoadProject => "Load project:",
|
||||
BrowserTarget::ImportSample(_) => "Import sample:",
|
||||
BrowserTarget::ExportSample(_) => "Export sample:",
|
||||
BrowserTarget::ImportClip(_) => "Import clip:",
|
||||
BrowserTarget::ExportClip(_) => "Export clip:",
|
||||
BrowseTarget::SaveProject => "Save project:",
|
||||
BrowseTarget::LoadProject => "Load project:",
|
||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||
}, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
|
||||
":device" => {
|
||||
let selected = app.dialog.device_kind().unwrap();
|
||||
|
|
@ -130,7 +174,7 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
|||
//b.next().map(|t|Fixed::x(16, Align::w(Tui::fg(Rgb(64,224,0), format!("{}", t.value))))),
|
||||
//Bsp::e(" ", Align::w(format!("{}", b.0.0.trim()))))))))))),
|
||||
|
||||
//Dialog::Browser(BrowserTarget::Load, browser) => {
|
||||
//Dialog::Browse(BrowseTarget::Load, browser) => {
|
||||
//"bobcat".boxed()
|
||||
////Bsp::s(
|
||||
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||
|
|
@ -139,7 +183,7 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
|||
////Outer(true, Style::default().fg(Tui::g(96)))
|
||||
////.enclose(Fill::xy(browser)))
|
||||
//},
|
||||
//Dialog::Browser(BrowserTarget::Export, browser) => {
|
||||
//Dialog::Browse(BrowseTarget::Export, browser) => {
|
||||
//"bobcat".boxed()
|
||||
////Bsp::s(
|
||||
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||
|
|
@ -148,7 +192,7 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
|||
////Outer(true, Style::default().fg(Tui::g(96)))
|
||||
////.enclose(Fill::xy(browser)))
|
||||
//},
|
||||
//Dialog::Browser(BrowserTarget::Import, browser) => {
|
||||
//Dialog::Browse(BrowseTarget::Import, browser) => {
|
||||
//"bobcat".boxed()
|
||||
////Bsp::s(
|
||||
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue