Compare commits

...

3 commits

Author SHA1 Message Date
99d9da6ffd arranger: add history field
Some checks are pending
/ build (push) Waiting to run
2025-05-18 19:18:39 +03:00
7746abc9ee euphuckingwrecka 2025-05-18 18:54:30 +03:00
3e9545fe26 wip: modularize dialog 2025-05-18 18:32:39 +03:00
18 changed files with 251 additions and 240 deletions

View file

@ -3,7 +3,8 @@
(info "A session grid.")
(keys
(layer-if :is-editing "./keys_editor.edn")
(layer-if :focus-editor "./keys_editor.edn")
(layer-if :focus-dialog "./keys_dialog.edn")
(layer-if :focus-message "./keys_message.edn")
(layer-if :focus-device-add "./keys_device_add.edn")
(layer-if :focus-browser "./keys_browser.edn")
@ -26,6 +27,6 @@
(bsp/s :view-tracks-devices
(bsp/s :view-tracks-outputs
(bsp/s :view-tracks-names
(fill/xy (either :is-editing
(fill/xy (either :focus-editor
(bsp/e :view-scenes-names :view-editor)
:view-scenes)))))))))))

View file

@ -1,7 +1,5 @@
(@c color)
(@q launch)
(@t select :select-track-header)
(@s select :select-scene-header)
(@tab project edit)
(@enter project edit)
(@escape project home)
@ -15,3 +13,5 @@
(@down select :select-scene-next)
(@left select :select-track-prev)
(@right select :select-track-next)
(@t select :select-track)
(@s select :select-scene)

View file

@ -0,0 +1 @@

View file

View file

View file

View file

View file

0
config/keys_dialog.edn Normal file
View file

View file

@ -1,38 +1,72 @@
use crate::*;
use std::path::PathBuf;
type MaybeClip = Option<Arc<RwLock<MidiClip>>>;
macro_rules! ns { ($C:ty, $s:expr, $a:expr, $W:expr) => { <$C>::try_from_expr($s, $a).map($W) } }
macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; }
macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; }
handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.command(self, input) {
let undo = command.execute(self)?;
if let Some(undo) = undo {
self.history.push(undo);
}
handle!(TuiIn: |self: App, input|self.handle_tui_key_with_history(input));
impl App {
fn handle_tui_key_with_history (&mut self, input: &TuiIn) -> Perhaps<bool> {
Ok(if let Some(command) = self.config.keys.command(self, input) {
// FIXME failed commands not persisted in undo history
let undo = command.clone().execute(self)?;
self.history.push((command, undo));
Some(true)
} else {
} else {
None
}));
#[tengri_proc::command(App)] impl AppCommand {
fn dialog (app: &mut App, dialog: Option<Dialog>) -> Perhaps<Self> {
app.toggle_dialog(dialog);
Ok(None)
}
fn cancel_dialog (app: &mut App) -> Perhaps<Self> {
app.toggle_dialog(None);
Ok(None)
})
}
}
#[tengri_proc::command(App)]
impl AppCommand {
fn toggle_editor (app: &mut App, value: bool) -> Perhaps<Self> {
app.toggle_editor(Some(value));
Ok(None)
}
//fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
//Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
//}
fn editor (app: &mut App, command: MidiEditCommand) -> Perhaps<Self> {
Ok(if let Some(editor) = app.editor_mut() {
let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?;
// update linked sampler after editor action
app.project.sampler_mut().map(|sampler|match command {
// autoselect: automatically select sample in sampler
MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); },
_ => {}
});
undo
} else {
None
})
}
fn dialog (app: &mut App, command: DialogCommand) -> Perhaps<Self> {
Ok(command.delegate(&mut app.dialog, |command|Self::Dialog{command})?)
}
fn project (app: &mut App, command: ArrangementCommand) -> Perhaps<Self> {
Ok(command.delegate(&mut app.project, |command|Self::Project{command})?)
}
fn clock (app: &mut App, command: ClockCommand) -> Perhaps<Self> {
Ok(command.execute(app.clock_mut())?.map(|command|Self::Clock{command}))
}
fn sampler (app: &mut App, command: SamplerCommand) -> Perhaps<Self> {
Ok(app.project.sampler_mut()
.map(|s|command.delegate(s, |command|Self::Sampler{command}))
.transpose()?
.flatten())
}
fn pool (app: &mut App, command: PoolCommand) -> Perhaps<Self> {
let undo = command.clone().delegate(&mut app.pool, |command|AppCommand::Pool{command})?;
// update linked editor after pool action
match command {
// autoselect: automatically load selected clip in editor
PoolCommand::Select { .. } |
// autocolor: update color in all places simultaneously
PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => {
let clip = app.pool.clip().clone();
app.editor_mut().map(|editor|editor.set_clip(clip.as_ref()))
},
_ => None
};
Ok(undo)
}
fn enqueue (app: &mut App, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<Self> {
todo!()
}
@ -42,10 +76,6 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
fn zoom (app: &mut App, zoom: usize) -> Perhaps<Self> {
todo!()
}
//fn launch (app: &mut App) -> Perhaps<Self> {
//app.project.launch();
//Ok(None)
//}
fn select (app: &mut App, selection: Selection) -> Perhaps<Self> {
*app.project.selection_mut() = selection;
//todo!
@ -73,88 +103,41 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
app.tracks_stop_all();
Ok(None)
}
fn sampler (app: &mut App, command: SamplerCommand) -> Perhaps<Self> {
Ok(app.project.sampler_mut()
.map(|s|command.delegate(s, |command|Self::Sampler{command}))
.transpose()?
.flatten())
}
fn project (app: &mut App, command: ArrangementCommand) -> Perhaps<Self> {
Ok(command.delegate(&mut app.project, |command|Self::Project{command})?)
}
fn clock (app: &mut App, command: ClockCommand) -> Perhaps<Self> {
Ok(command.execute(app.clock_mut())?.map(|command|Self::Clock{command}))
}
fn message (app: &mut App, command: MessageCommand) -> Perhaps<Self> {
Ok(command.delegate(app, |command|Self::Message{command})?)
}
fn editor (app: &mut App, command: MidiEditCommand) -> Perhaps<Self> {
Ok(if let Some(editor) = app.editor_mut() {
let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?;
// update linked sampler after editor action
app.project.sampler_mut().map(|sampler|match command {
// autoselect: automatically select sample in sampler
MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); },
_ => {}
});
undo
} else {
None
})
}
fn pool (app: &mut App, command: PoolCommand) -> Perhaps<Self> {
let undo = command.clone().delegate(
&mut app.pool,
|command|AppCommand::Pool{command}
)?;
// update linked editor after pool action
match command {
// autoselect: automatically load selected clip in editor
PoolCommand::Select { .. } |
// autocolor: update color in all places simultaneously
PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => {
let clip = app.pool.clip().clone();
app.editor_mut().map(|editor|editor.set_clip(clip.as_ref()))
},
_ => None
};
Ok(undo)
}
//fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
//Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
//}
//fn launch (app: &mut App) -> Perhaps<Self> {
//app.project.launch();
//Ok(None)
//}
}
impl<'state> Context<'state, ClockCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<ClockCommand> {
Context::get(&self.clock(), iter)
}
}
impl<'state> Context<'state, MidiEditCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<MidiEditCommand> {
self.editor().map(|e|Context::get(e, iter)).flatten()
}
}
impl<'state> Context<'state, PoolCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<PoolCommand> {
Context::get(&self.pool, iter)
}
}
impl<'state> Context<'state, SamplerCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<SamplerCommand> {
self.project.sampler().map(|p|Context::get(p, iter)).flatten()
}
}
impl<'state> Context<'state, ArrangementCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<ArrangementCommand> {
Context::get(&self.project, iter)
}
}
#[tengri_proc::command(App)] impl MessageCommand {
fn dismiss (app: &mut App) -> Perhaps<Self> {
app.dialog = None;
Ok(None)
impl<'state> Context<'state, DialogCommand> for App {
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<DialogCommand> {
Context::get(&self, iter)
}
}

View file

@ -18,7 +18,7 @@ pub struct App {
/// Contains the currently edited musical arrangement
pub project: Arrangement,
/// Undo history
pub history: Vec<AppCommand>,
pub history: Vec<(AppCommand, Option<AppCommand>)>,
// Dialog overlay
pub dialog: Option<Dialog>,
// Cache of formatted strings
@ -179,8 +179,8 @@ impl App {
fn focus_editor (&self) -> bool {
self.project.editor.is_some()
}
fn is_editing (&self) -> bool {
self.project.editor.is_some()
fn focus_dialog (&self) -> bool {
self.dialog.is_some()
}
fn focus_message (&self) -> bool {
matches!(self.dialog, Some(Dialog::Message(..)))
@ -192,16 +192,20 @@ impl App {
self.browser().is_some()
}
fn focus_clip (&self) -> bool {
!self.is_editing() && self.selection().is_clip()
!self.focus_editor() && matches!(self.selection(),
Selection::TrackClip{..})
}
fn focus_track (&self) -> bool {
!self.is_editing() && self.selection().is_track()
!self.focus_editor() && matches!(self.selection(),
Selection::Track(..))
}
fn focus_scene (&self) -> bool {
!self.is_editing() && self.selection().is_scene()
!self.focus_editor() && matches!(self.selection(),
Selection::Scene(..))
}
fn focus_mix (&self) -> bool {
!self.is_editing() && self.selection().is_mix()
!self.focus_editor() && matches!(self.selection(),
Selection::Mix)
}
fn focus_pool_import (&self) -> bool {
matches!(self.pool.mode, Some(PoolMode::Import(..)))
@ -260,29 +264,32 @@ impl App {
fn scene_count (&self) -> usize {
self.scenes().len()
}
fn scene_selection (&self) -> Option<usize> {
fn scene_selected (&self) -> Option<usize> {
self.selection().scene()
}
fn track_count (&self) -> usize {
self.tracks().len()
}
fn track_selection (&self) -> Option<usize> {
fn track_selected (&self) -> Option<usize> {
self.selection().track()
}
fn select_scene (&self) -> Selection {
self.selection().select_scene(self.tracks().len())
}
fn select_scene_next (&self) -> Selection {
self.selection().scene_next(self.scenes().len())
self.selection().select_scene_next(self.scenes().len())
}
fn select_scene_prev (&self) -> Selection {
self.selection().scene_prev()
self.selection().select_scene_prev()
}
fn select_track_header (&self) -> Selection {
self.selection().track_header(self.tracks().len())
fn select_track (&self) -> Selection {
self.selection().select_track(self.tracks().len())
}
fn select_track_next (&self) -> Selection {
self.selection().track_next(self.tracks().len())
self.selection().select_track_next(self.tracks().len())
}
fn select_track_prev (&self) -> Selection {
self.selection().track_prev()
self.selection().select_track_prev()
}
fn clip_selected (&self) -> Option<Arc<RwLock<MidiClip>>> {
match self.selection() {
@ -447,13 +454,11 @@ impl Configuration {
let cond = cond.unwrap();
println!("ok");
map.add_layer_if(
Box::new(move |state|{
let mut exp = exp.clone();
let value = Context::get(state, &mut format!(":{cond}").as_str().into()).unwrap_or(false);
if value {
panic!("layer-if cond={cond:?} exp={exp:?} value={value:?}");
}
value
Box::new(move |state: &App|{
Context::get(state,
&mut format!("{cond}").as_str().into())
.unwrap_or_else(||panic!(
"missing input layer conditional {cond} from {exp:?}"))
}),
keys
);

View file

@ -7,6 +7,11 @@ impl App {
pub fn view_nil (&self) -> impl Content<TuiOut> + use<'_> {
"nil"
}
pub fn view_history (&self) -> impl Content<TuiOut> {
Fixed::y(1, Fill::x(Align::w(FieldH(self.color,
format!("History ({})", self.history.len()),
self.history.last().map(|last|Fill::x(Align::w(format!("{:?}", last.0))))))))
}
pub fn view_status_h2 (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let theme = self.color;
@ -24,24 +29,33 @@ impl App {
)
)
)));
add(&" ");
{
let cache = self.view_cache.read().unwrap();
add(&Fixed::x(16, Align::w(Bsp::s(
add(&Fixed::x(15, Align::w(Bsp::s(
FieldH(theme, "Beat", cache.beat.view.clone()),
FieldH(theme, "Time", cache.time.view.clone()),
))));
add(&Fixed::x(16, Align::w(Bsp::s(
add(&Fixed::x(13, Align::w(Bsp::s(
Fill::x(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
Fill::x(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
))));
add(&Fixed::x(16, Align::w(Bsp::s(
add(&Fixed::x(12, Align::w(Bsp::s(
Fill::x(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))),
Fill::x(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))),
))));
add(&FieldV(theme, "Selection", Fill::x(Align::w(self.selection().describe(
add(&Bsp::s(
Fill::x(Align::w(FieldH(theme, "Selected", Align::w(self.selection().describe(
self.tracks(),
self.scenes()
)))));
))))),
Fill::x(Align::w(FieldH(theme, format!("History ({})", self.history.len()),
self.history.last().map(|last|Fill::x(Align::w(format!("{:?}", last.0)))))))
));
//if let Some(last) = self.history.last() {
//add(&FieldV(theme, format!("History ({})", self.history.len()),
//Fill::x(Align::w(format!("{:?}", last.0)))));
//}
}
}))
}

View file

@ -39,18 +39,6 @@ pub enum Selection {
/// Focus identification methods
impl Selection {
pub fn is_mix (&self) -> bool {
matches!(self, Self::Mix)
}
pub fn is_track (&self) -> bool {
matches!(self, Self::Track(_))
}
pub fn is_scene (&self) -> bool {
matches!(self, Self::Scene(_))
}
pub fn is_clip (&self) -> bool {
matches!(self, Self::TrackClip {..})
}
pub fn track (&self) -> Option<usize> {
use Selection::*;
match self {
@ -62,7 +50,7 @@ impl Selection {
_ => None
}
}
pub fn track_header (&self, track_count: usize) -> Self {
pub fn select_track (&self, track_count: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
@ -72,7 +60,7 @@ impl Selection {
_ => todo!(),
}
}
pub fn track_next (&self, len: usize) -> Self {
pub fn select_track_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
@ -90,7 +78,7 @@ impl Selection {
_ => todo!()
}
}
pub fn track_prev (&self) -> Self {
pub fn select_track_prev (&self) -> Self {
use Selection::*;
match self {
Mix => Mix,
@ -109,7 +97,16 @@ impl Selection {
_ => None
}
}
pub fn scene_next (&self, len: usize) -> Self {
pub fn select_scene (&self, scene_count: usize) -> Self {
use Selection::*;
match self {
Mix | Track(_) => Scene(0),
Scene(s) => Scene((s + 1) % scene_count),
TrackClip { scene, .. } => Track(*scene),
_ => todo!(),
}
}
pub fn select_scene_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Scene(0),
@ -127,7 +124,7 @@ impl Selection {
_ => todo!()
}
}
pub fn scene_prev (&self) -> Self {
pub fn select_scene_prev (&self) -> Self {
use Selection::*;
match self {
Mix | Scene(0) => Mix,
@ -142,11 +139,9 @@ impl Selection {
use Selection::*;
format!("{}", match self {
Mix => "Everything".to_string(),
Scene(s) => scenes.get(*s)
.map(|scene|format!("S{s}: {}", &scene.name))
Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
.unwrap_or_else(||"S??".into()),
Track(t) => tracks.get(*t)
.map(|track|format!("T{t}: {}", &track.name))
Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
.unwrap_or_else(||"T??".into()),
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
(Some(_), Some(s)) => match s.clip(*track) {
@ -159,6 +154,3 @@ impl Selection {
}).into()
}
}
impl Arrangement {
}

View file

@ -90,7 +90,10 @@ impl Arrangement {
for (index, track, x1, x2) in self.tracks_with_sizes() {
add(&Fixed::xy(self.track_width(index, track), h + 1,
Tui::bg(track.color.dark.rgb, Align::nw(Map::south(2, move||0..h,
|_, index|Fixed::xy(track.width as u16, 2, Tui::bg(ItemTheme::G[32].base.rgb,
|_, index|Fixed::xy(track.width as u16, 2,
Tui::fg_bg(
ItemTheme::G[32].lightest.rgb,
ItemTheme::G[32].dark.rgb,
Align::nw(format!(" · {}", "--")))))))));
}
}))

View file

@ -1,4 +1,6 @@
use crate::*;
mod dialog_api; pub use self::dialog_api::*;
mod dialog_view; pub use self::dialog_view::*;
/// Various possible dialog overlays
#[derive(Clone, Debug)]
@ -16,98 +18,3 @@ pub enum Dialog {
pub enum Message {
FailedToAddDevice,
}
content!(TuiOut: |self: Message| match self {
Self::FailedToAddDevice => "Failed to add device."
});
content!(TuiOut: |self: Dialog| match self {
Self::Menu(_) =>
self.view_dialog_menu().boxed(),
Self::Help(offset) =>
self.view_dialog_help(*offset).boxed(),
Self::Browser(target, browser) =>
self.view_dialog_browser(target, browser).boxed(),
Self::Options =>
self.view_dialog_options().boxed(),
Self::Device(index) =>
self.view_dialog_device(*index).boxed(),
Self::Message(message) =>
self.view_dialog_message(message).boxed(),
});
impl Dialog {
pub fn view_dialog_menu (&self) -> impl Content<TuiOut> {
let options = ||["Projects", "Settings", "Help", "Quit"].iter();
let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a));
Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
}
pub fn view_dialog_help <'a> (&'a self, offset: usize) -> impl Content<TuiOut> + use<'a> {
Bsp::s(Tui::bold(true, "Help"), "FIXME")
//Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1,
//move||self.config.keys.layers.iter()
//.filter_map(|a|(a.0)(self).then_some(a.1))
//.flat_map(|a|a)
//.filter_map(|x|if let Value::Exp(_, iter)=x.value{ Some(iter) } else { None })
//.skip(offset)
//.take(20),
//|mut b,i|Fixed::x(60, Align::w(Bsp::e("(", Bsp::e(
//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()))))))))))
}
pub fn view_dialog_device (&self, index: usize) -> impl Content<TuiOut> + use<'_> {
let choices = ||device_kinds().iter();
let choice = move|label, i|
Fill::x(Tui::bg(if i == index { Rgb(64,128,32) } else { Rgb(0,0,0) },
Bsp::e(if i == index { "[ " } else { " " },
Bsp::w(if i == index { " ]" } else { " " },
label))));
Bsp::s(Tui::bold(true, "Add device"), Map::south(1, choices, choice))
}
pub fn view_dialog_message <'a> (&'a self, message: &'a Message) -> impl Content<TuiOut> + use<'a> {
Bsp::s(message, Bsp::s("", "[ OK ]"))
}
pub fn view_dialog_browser <'a> (&'a self, target: &BrowserTarget, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
Bsp::s(
Padding::xy(3, 1, Fill::x(Align::w(FieldV(
Default::default(),
match target {
BrowserTarget::SaveProject => "Save project:",
BrowserTarget::LoadProject => "Load project:",
BrowserTarget::ImportSample(_) => "Import sample:",
BrowserTarget::ExportSample(_) => "Export sample:",
BrowserTarget::ImportClip(_) => "Import clip:",
BrowserTarget::ExportClip(_) => "Export clip:",
},
Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(Fill::xy(browser)))
}
pub fn view_dialog_load <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
Bsp::s(
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
Tui::bold(true, " Load project: "),
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(Fill::xy(browser)))
}
pub fn view_dialog_export <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
Bsp::s(
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
Tui::bold(true, " Export: "),
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(Fill::xy(browser)))
}
pub fn view_dialog_import <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
Bsp::s(
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
Tui::bold(true, " Import: "),
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(Fill::xy(browser)))
}
pub fn view_dialog_options <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
"TODO"
}
}

View file

@ -0,0 +1,9 @@
use crate::*;
#[tengri_proc::command(Option<Dialog>)]
impl DialogCommand {
fn dismiss (dialog: &mut Option<Dialog>) -> Perhaps<Self> {
*dialog = None;
Ok(None)
}
}

View file

@ -0,0 +1,96 @@
use crate::*;
content!(TuiOut: |self: Dialog| match self {
Self::Menu(_) =>
self.view_dialog_menu().boxed(),
Self::Help(offset) =>
self.view_dialog_help(*offset).boxed(),
Self::Browser(target, browser) =>
self.view_dialog_browser(target, browser).boxed(),
Self::Options =>
self.view_dialog_options().boxed(),
Self::Device(index) =>
self.view_dialog_device(*index).boxed(),
Self::Message(message) =>
self.view_dialog_message(message).boxed(),
});
content!(TuiOut: |self: Message| match self {
Self::FailedToAddDevice => "Failed to add device."
});
impl Dialog {
pub fn view_dialog_menu (&self) -> impl Content<TuiOut> {
let options = ||["Projects", "Settings", "Help", "Quit"].iter();
let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a));
Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
}
pub fn view_dialog_help <'a> (&'a self, offset: usize) -> impl Content<TuiOut> + use<'a> {
Bsp::s(Tui::bold(true, "Help"), "FIXME")
//Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1,
//move||self.config.keys.layers.iter()
//.filter_map(|a|(a.0)(self).then_some(a.1))
//.flat_map(|a|a)
//.filter_map(|x|if let Value::Exp(_, iter)=x.value{ Some(iter) } else { None })
//.skip(offset)
//.take(20),
//|mut b,i|Fixed::x(60, Align::w(Bsp::e("(", Bsp::e(
//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()))))))))))
}
pub fn view_dialog_device (&self, index: usize) -> impl Content<TuiOut> + use<'_> {
let choices = ||device_kinds().iter();
let choice = move|label, i|
Fill::x(Tui::bg(if i == index { Rgb(64,128,32) } else { Rgb(0,0,0) },
Bsp::e(if i == index { "[ " } else { " " },
Bsp::w(if i == index { " ]" } else { " " },
label))));
Bsp::s(Tui::bold(true, "Add device"), Map::south(1, choices, choice))
}
pub fn view_dialog_message <'a> (&'a self, message: &'a Message) -> impl Content<TuiOut> + use<'a> {
Bsp::s(message, Bsp::s("", "[ OK ]"))
}
pub fn view_dialog_browser <'a> (&'a self, target: &BrowserTarget, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
Bsp::s(
Padding::xy(3, 1, Fill::x(Align::w(FieldV(
Default::default(),
match target {
BrowserTarget::SaveProject => "Save project:",
BrowserTarget::LoadProject => "Load project:",
BrowserTarget::ImportSample(_) => "Import sample:",
BrowserTarget::ExportSample(_) => "Export sample:",
BrowserTarget::ImportClip(_) => "Import clip:",
BrowserTarget::ExportClip(_) => "Export clip:",
},
Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(Fill::xy(browser)))
}
pub fn view_dialog_load <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
Bsp::s(
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
Tui::bold(true, " Load project: "),
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(Fill::xy(browser)))
}
pub fn view_dialog_export <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
Bsp::s(
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
Tui::bold(true, " Export: "),
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(Fill::xy(browser)))
}
pub fn view_dialog_import <'a> (&'a self, browser: &'a Browser) -> impl Content<TuiOut> + use<'a> {
Bsp::s(
Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
Tui::bold(true, " Import: "),
Shrink::x(3, Fixed::y(1, RepeatH("🭻"))))))),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(Fill::xy(browser)))
}
pub fn view_dialog_options <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
"TODO"
}
}

2
deps/tengri vendored

@ -1 +1 @@
Subproject commit 921378b6dbb38d4f301f688abd1cfef9bdc0f941
Subproject commit 3bc739328eed0c8fa67b432c7354c7929ddb505f