mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 03:36:41 +01:00
working main menu
This commit is contained in:
parent
559d2fc4a1
commit
f81f16b47b
7 changed files with 130 additions and 80 deletions
|
|
@ -2,10 +2,11 @@
|
||||||
#![feature(adt_const_params, associated_type_defaults, if_let_guard, impl_trait_in_assoc_type,
|
#![feature(adt_const_params, associated_type_defaults, if_let_guard, impl_trait_in_assoc_type,
|
||||||
type_alias_impl_trait, trait_alias, type_changing_struct_update, closure_lifetime_binder)]
|
type_alias_impl_trait, trait_alias, type_changing_struct_update, closure_lifetime_binder)]
|
||||||
#[cfg(test)] mod app_test;
|
#[cfg(test)] mod app_test;
|
||||||
mod app_deps; pub use self::app_deps::*;
|
|
||||||
mod app_jack; pub use self::app_jack::*;
|
|
||||||
mod app_bind; pub use self::app_bind::*;
|
mod app_bind; pub use self::app_bind::*;
|
||||||
mod app_data; pub use self::app_data::*;
|
mod app_data; pub use self::app_data::*;
|
||||||
|
mod app_deps; pub use self::app_deps::*;
|
||||||
|
mod app_jack; pub use self::app_jack::*;
|
||||||
|
mod app_menu; pub use self::app_menu::*;
|
||||||
mod app_view; pub use self::app_view::*;
|
mod app_view; pub use self::app_view::*;
|
||||||
/// Total state
|
/// Total state
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
|
@ -31,23 +32,6 @@ pub struct App {
|
||||||
/// Contains the currently edited musical arrangement
|
/// Contains the currently edited musical arrangement
|
||||||
pub project: Arrangement,
|
pub project: Arrangement,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
|
||||||
pub struct Axis {
|
|
||||||
min: usize,
|
|
||||||
max: usize,
|
|
||||||
step: usize,
|
|
||||||
}
|
|
||||||
/// Various possible dialog modes.
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
|
||||||
pub enum Dialog {
|
|
||||||
#[default] None,
|
|
||||||
Help(usize),
|
|
||||||
Menu(usize, Arc<[Arc<str>]>),
|
|
||||||
Device(usize),
|
|
||||||
Message(Arc<str>),
|
|
||||||
Browse(BrowseTarget, Arc<Browse>),
|
|
||||||
Options,
|
|
||||||
}
|
|
||||||
has!(Jack<'static>: |self: App|self.jack);
|
has!(Jack<'static>: |self: App|self.jack);
|
||||||
has!(Pool: |self: App|self.pool);
|
has!(Pool: |self: App|self.pool);
|
||||||
has!(Dialog: |self: App|self.dialog);
|
has!(Dialog: |self: App|self.dialog);
|
||||||
|
|
@ -67,48 +51,6 @@ 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 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 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() } }
|
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||||
impl Dialog {
|
|
||||||
pub fn welcome () -> Self {
|
|
||||||
Self::Menu(0, [
|
|
||||||
"Continue session".into(),
|
|
||||||
"Load old session".into(),
|
|
||||||
"Begin new session".into(),
|
|
||||||
].into())
|
|
||||||
}
|
|
||||||
pub fn menu_next (&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.len()), items.clone()),
|
|
||||||
_ => Self::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn menu_prev (&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.len()), items.clone()),
|
|
||||||
_ => Self::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn menu_selected (&self) -> Option<usize> {
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
pub fn device_kind_next (&self) -> Option<usize> {
|
|
||||||
self.device_kind().map(|index|(index + 1) % device_kinds().len())
|
|
||||||
}
|
|
||||||
pub fn device_kind_prev (&self) -> Option<usize> {
|
|
||||||
self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)))
|
|
||||||
}
|
|
||||||
pub fn message (&self) -> Option<&str> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
pub fn browser (&self) -> Option<&Arc<Browse>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
pub fn browser_target (&self) -> Option<&BrowseTarget> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn editor_focused (&self) -> bool {
|
pub fn editor_focused (&self) -> bool {
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,15 @@ impl Default for AppCommand { fn default () -> Self { Self::Nop } }
|
||||||
|
|
||||||
def_command!(AppCommand: |app: App| {
|
def_command!(AppCommand: |app: App| {
|
||||||
Nop => Ok(None),
|
Nop => Ok(None),
|
||||||
Confirm => todo!(),
|
Confirm => Ok(match &app.dialog {
|
||||||
Cancel => todo!(), // TODO delegate:
|
Dialog::Menu(index, items) => {
|
||||||
|
let callback = items.0[*index].1.clone();
|
||||||
|
callback(app)?;
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => todo!(),
|
||||||
|
}),
|
||||||
|
Cancel => todo!(), // TODO delegate:
|
||||||
Inc { axis: Axis } => Ok(match (&app.dialog, axis) {
|
Inc { axis: Axis } => Ok(match (&app.dialog, axis) {
|
||||||
(Dialog::None, _) => todo!(),
|
(Dialog::None, _) => todo!(),
|
||||||
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_next() }
|
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_next() }
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,17 @@ pub use ::{
|
||||||
tek_config::*,
|
tek_config::*,
|
||||||
tek_device::{self, *},
|
tek_device::{self, *},
|
||||||
tengri::{
|
tengri::{
|
||||||
Usually, Perhaps, Has, MaybeHas, has, maybe_has,
|
Usually, Perhaps, Has, MaybeHas, has, maybe_has, impl_debug,
|
||||||
dsl::*, input::*, output::*, tui::*,
|
dsl::*, input::*, output::*, tui::*,
|
||||||
tui::ratatui,
|
tui::{
|
||||||
tui::ratatui::prelude::buffer::Cell,
|
ratatui::{
|
||||||
tui::ratatui::prelude::Color::{self, *},
|
self, prelude::{Style, Stylize, Buffer, Modifier, buffer::Cell, Color::{self, *}}
|
||||||
tui::ratatui::prelude::{Style, Stylize, Buffer, Modifier},
|
},
|
||||||
tui::crossterm,
|
crossterm::{
|
||||||
tui::crossterm::event::{Event, KeyCode::{self, *}},
|
self,
|
||||||
|
event::{Event, KeyCode::{self, *}},
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
|
|
||||||
88
crates/app/app_menu.rs
Normal file
88
crates/app/app_menu.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Various possible dialog modes.
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct MenuItems(pub Arc<[MenuItem]>);
|
||||||
|
|
||||||
|
impl AsRef<Arc<[MenuItem]>> for MenuItems {
|
||||||
|
fn as_ref (&self) -> &Arc<[MenuItem]> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MenuItem(
|
||||||
|
/// Label
|
||||||
|
pub Arc<str>,
|
||||||
|
/// Callback
|
||||||
|
pub Arc<Box<dyn Fn(&mut App)->Usually<()> + Send + Sync>>
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Default for MenuItem {
|
||||||
|
fn default () -> Self { Self("".into(), Arc::new(Box::new(|_|Ok(())))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
|
||||||
|
|
||||||
|
impl PartialEq for MenuItem {
|
||||||
|
fn eq (&self, other: &Self) -> bool {
|
||||||
|
self.0 == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dialog {
|
||||||
|
pub fn welcome () -> Self {
|
||||||
|
Self::Menu(1, MenuItems([
|
||||||
|
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
|
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
|
||||||
|
app.dialog = Dialog::None;
|
||||||
|
app.mode = app.config.modes.clone().read().unwrap().get(":arranger").cloned().unwrap();
|
||||||
|
})))),
|
||||||
|
MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
|
].into()))
|
||||||
|
}
|
||||||
|
pub fn menu_next (&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.0.len()), items.clone()),
|
||||||
|
_ => Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn menu_prev (&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.0.len()), items.clone()),
|
||||||
|
_ => Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn menu_selected (&self) -> Option<usize> {
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
pub fn device_kind_next (&self) -> Option<usize> {
|
||||||
|
self.device_kind().map(|index|(index + 1) % device_kinds().len())
|
||||||
|
}
|
||||||
|
pub fn device_kind_prev (&self) -> Option<usize> {
|
||||||
|
self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)))
|
||||||
|
}
|
||||||
|
pub fn message (&self) -> Option<&str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn browser (&self) -> Option<&Arc<Browse>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn browser_target (&self) -> Option<&BrowseTarget> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -86,18 +86,25 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||||
":dialog/menu" => Box::new(if let Dialog::Menu(selected, items) = &app.dialog {
|
":dialog/menu" => Box::new(if let Dialog::Menu(selected, items) = &app.dialog {
|
||||||
let items = items.clone();
|
let items = items.clone();
|
||||||
let selected = *selected;
|
let selected = *selected;
|
||||||
Some(Fill::xy(Align::c(Tui::bg(Red, Fill::x(Stack::south(move|add|{
|
Some(Fill::x(Stack::south(move|add|{
|
||||||
for (index, item) in items.iter().enumerate() {
|
for (index, MenuItem(item, _)) in items.0.iter().enumerate() {
|
||||||
add(&Tui::fg_bg(
|
add(&Tui::fg_bg(
|
||||||
if selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
|
if selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
|
||||||
if selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
|
if selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
|
||||||
Fixed::y(2, Align::n(Fill::x(item)))
|
Fixed::y(2, Align::n(Fill::x(item)))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}))))))
|
})))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}),
|
}),
|
||||||
|
":logo" => Box::new(Fixed::xy(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), Stack::south(|add|{
|
||||||
|
add(&Fixed::y(1, ""));
|
||||||
|
add(&Fixed::y(1, ""));
|
||||||
|
add(&Fixed::y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"));
|
||||||
|
add(&Fixed::y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~"))));
|
||||||
|
add(&Fixed::y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"));
|
||||||
|
}))))),
|
||||||
":templates" => Box::new({
|
":templates" => Box::new({
|
||||||
let modes = app.config.modes.clone();
|
let modes = app.config.modes.clone();
|
||||||
let height = (modes.read().unwrap().len() * 2) as u16;
|
let height = (modes.read().unwrap().len() * 2) as u16;
|
||||||
|
|
|
||||||
|
|
@ -115,12 +115,15 @@ impl Cli {
|
||||||
|
|
||||||
/// CLI header
|
/// CLI header
|
||||||
const HEADER: &'static str = r#"
|
const HEADER: &'static str = r#"
|
||||||
~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~~~~
|
~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ ~~~~~~ ~ ~~~
|
||||||
~ ~ ║ ~ ╟─╌ ~╟─< ~ v0.3.0-rc.0 "no, i insist that i am not a dj ~
|
~~ ║ ~ ╟─╌ ~╟─< ~ v0.3.0, 2025 sum(m)er @ the nose of the cat. ~
|
||||||
~ ~ ╨ ~ ╙──╜ ╨ ╜ ~ 2025, summer, the nose of the cat. J ~
|
~~~ ╨ ~ ╙──╜ ╨ ╜ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ ~~ ~~ ~ ~~
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
On first run, Tek will create configuration and state dirs:
|
||||||
On first run, Tek will create configuration and state dirs. ~
|
* [x] ~/.config/tek - config
|
||||||
On subsequent runs, Tek should resume from where you left off. ~"#;
|
* [ ] ~/.local/share/tek - projects
|
||||||
|
* [ ] ~/.local/lib/tek - plugins
|
||||||
|
* [ ] ~/.cache/tek - cache
|
||||||
|
~"#;
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_cli () {
|
#[cfg(test)] #[test] fn test_cli () {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
|
|
||||||
2
tek.edn
2
tek.edn
|
|
@ -12,7 +12,7 @@
|
||||||
(mode :menu (keys :axis/y :confirm) :menu)
|
(mode :menu (keys :axis/y :confirm) :menu)
|
||||||
(keys :confirm (@enter confirm))
|
(keys :confirm (@enter confirm))
|
||||||
(view :menu (bg (g 40) (bsp/s :ports/out (bsp/n :ports/in
|
(view :menu (bg (g 40) (bsp/s :ports/out (bsp/n :ports/in
|
||||||
(bg (g 30) (fill/xy (align/c :dialog/menu)))))))
|
(bg (g 30) (bsp/s (fixed/y 7 :logo) (fill/xy :dialog/menu)))))))
|
||||||
(view :ports/out (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-OUT)))
|
(view :ports/out (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-OUT)))
|
||||||
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT R))))))))
|
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT R))))))))
|
||||||
(view :ports/in (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-IN)))
|
(view :ports/in (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-IN)))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue