From 4aefaf452c26fa6b583915ba155f51eb675e0ee4 Mon Sep 17 00:00:00 2001 From: i do not exist Date: Thu, 6 Aug 2026 18:30:18 +0300 Subject: [PATCH] fix main menu --- shell.nix | 7 +- src/device/clock.rs | 14 ++- src/device/dialog.rs | 138 ++++++++++++++++++++---- src/device/editor.rs | 55 ++++++++++ src/device/menu.rs | 27 ----- src/tek.edn | 21 ++-- src/tek.rs | 242 ++++++++++--------------------------------- tengri | 2 +- 8 files changed, 260 insertions(+), 246 deletions(-) delete mode 100644 src/device/menu.rs diff --git a/shell.nix b/shell.nix index 78a0b912..7417c55d 100755 --- a/shell.nix +++ b/shell.nix @@ -4,18 +4,19 @@ stdenv = pkgs.clang19Stdenv; nativeBuildInputs = [ pkgs.bacon - pkgs.pkg-config + pkgs.cloc pkgs.freetype + pkgs.grcov pkgs.libclang pkgs.mold - pkgs.cloc + pkgs.pkg-config pkgs.watchexec ]; buildInputs = [ pkgs.libclang pkgs.jack2 pkgs.lilv - pkgs.serd + pkgs.serd #pkgs.glib #pkgs.atk diff --git a/src/device/clock.rs b/src/device/clock.rs index 5781a743..5cf352b7 100644 --- a/src/device/clock.rs +++ b/src/device/clock.rs @@ -8,7 +8,19 @@ mod ticker; pub use self::ticker::*; mod timebase; pub use self::timebase::*; mod clock_view; pub use self::clock_view::*; -impl +AsMut> 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 +AsMut> HasClock for T {} pub trait HasClock: AsRef + AsMut { fn clock (&self) -> &Clock { self.as_ref() } fn clock_mut (&mut self) -> &mut Clock { self.as_mut() } diff --git a/src/device/dialog.rs b/src/device/dialog.rs index 40dcfbb7..7b28cf07 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -1,22 +1,39 @@ use crate::{*, device::*}; -/// 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), - Browse(BrowseTarget, Arc), - Options, +pub fn draw_dialog ( + to: &mut Tui, mut frags: std::str::Split<&str>, state: &App, dsl: &impl Expression +) -> Drawn { + 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()).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 { - fn namespace (&self, src: impl Language) -> Perhaps { +impl App { + pub fn get_dialog (&self, src: impl Language) -> Perhaps { src.word()?.map(|word|Ok(match word { ":dialog/none" => Dialog::None, ":dialog/options" => Dialog::Options, @@ -47,8 +64,87 @@ impl<'n> Namespace<'n, Dialog> for App { ) })).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 { + 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 { + 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 { + 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), + Browse(BrowseTarget, Arc), + 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, + /// Callback + pub ArcUsually<()> + 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> for MenuItems { fn as_ref (&self) -> &Arc<[MenuItem]> { &self.0 } } + impl Dialog { /// ``` /// let _ = tek::Dialog::welcome(); @@ -56,16 +152,20 @@ impl Dialog { pub fn welcome () -> Self { 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.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(); println!("Exited cleanly.") })))), diff --git a/src/device/editor.rs b/src/device/editor.rs index 7e130b37..5b51d7b3 100644 --- a/src/device/editor.rs +++ b/src/device/editor.rs @@ -1,6 +1,61 @@ #![allow(unused)] 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) { + //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. /// /// ``` diff --git a/src/device/menu.rs b/src/device/menu.rs deleted file mode 100644 index 8e1cb5f2..00000000 --- a/src/device/menu.rs +++ /dev/null @@ -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> 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, - /// Callback - pub ArcUsually<()> + Send + Sync>> -); diff --git a/src/tek.edn b/src/tek.edn index 1a6803d3..7dac49a6 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -1,7 +1,7 @@ -(view :logo (bsp/s (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~) - (bsp/s (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ heatwave is the new darkwave ~~) - (bsp/s (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~) - (text powered by tengri & dizzle\n))))) +(view :logo (bsp/s (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ) + (bsp/s (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ heatwave is the new darkwave ~~ ) + (bsp/s (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ) + (text))))) (view :browse (bsp/s (padding 3 1 :browse-title) @@ -14,7 +14,12 @@ :transport) (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 (bsp/s (align/w (text L-AUDIO-OUT)) @@ -22,7 +27,7 @@ (align/e (text AUDIO-OUT-R))))) (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) (align/e (text AUDIO-IN-R))))) @@ -71,9 +76,7 @@ (mode :track (keys :track)) (mode :scene (keys :scene)) (mode :mix (keys :mix)) - (view (bsp/n :status (bsp/w :meters/output (bsp/e :meters/input (bsp/n :tracks/inputs - (bsp/s :tracks/outputs (bsp/s :tracks/names (bsp/s :tracks/devices - (fill (either :mode/editor (bsp/e :scenes/names :editor) :scenes))))))))))) + (view (bg (g 64) (bsp/n :status (text test))))) (keys :back (@escape back)) (keys :confirm (@enter confirm)) diff --git a/src/tek.rs b/src/tek.rs index 18ca7439..85efad6a 100644 --- a/src/tek.rs +++ b/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, bounce: Option) -> Usually<()> { + pub fn watch (config: Arc, poll: Option) -> 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 App::get_opt_u16)] #[namespace(Option App::get_opt_usize)] #[namespace(Option>> 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 { - 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 { - 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 { - 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) { - //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 ( @@ -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 for App { fn view (&self) -> impl Draw { - 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("")).unwrap_or(""); - 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("")).unwrap_or(""); + 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 { - 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 { 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 { - 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 { diff --git a/tengri b/tengri index cf67185f..799e4976 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit cf67185ffde1719bd5cc0c75cb0f9ac49d95a6d7 +Subproject commit 799e49762250f48778dadd92874a29495114a6af