fix main menu
Some checks are pending
/ build (push) Waiting to run

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

@ -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 = [

View file

@ -8,6 +8,18 @@ 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 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 {} 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() }

View file

@ -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,7 +64,86 @@ 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 {
/// ``` /// ```
@ -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.")
})))), })))),

View file

@ -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.
/// ///
/// ``` /// ```

View file

@ -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>>
);

View file

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

View file

@ -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,6 +1406,7 @@ 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> {
self.perf.cycle(&mut |_|{
draw(|to: &mut Tui|{ draw(|to: &mut Tui|{
let xywh = to.area().into(); let xywh = to.area().into();
@ -1553,11 +1450,12 @@ mod draw {
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) }, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w()
).align_w().full_w()
} }
pub fn view_device (state: &App) -> impl Draw<Tui> { pub fn view_device (state: &App) -> impl Draw<Tui> {

2
tengri

@ -1 +1 @@
Subproject commit cf67185ffde1719bd5cc0c75cb0f9ac49d95a6d7 Subproject commit 799e49762250f48778dadd92874a29495114a6af