use crate::*; tui_view!(|self: App| { "" }); /// The [Draw] implementation for [App] handles the loaded view, /// which is defined in terms of [dizzle] DSL. /// /// If there is an error, the error is displayed. FIXME: overlay it /// Then, every top-level form of the DSL description is rendered. impl Draw for App { fn draw (self, to: &mut Tui) -> Usually> { let xywh = to.area().into(); if let Some(e) = self.error.read().unwrap().as_ref() { to.show(xywh, e.as_ref())?; } for (index, dsl) in self.mode.view.iter().enumerate() { if let Err(e) = self.interpret(to, dsl) { *self.error.write().unwrap() = Some(format!("view #{index}: {e}").into()); break; } } Ok(xywh) } } impl Interpret> for App { fn interpret_expr <'a> (&'a self, to: &mut Tui, lang: &'a impl Expression) -> Usually> { tek_draw_expr(self, to, lang) } fn interpret_word <'a> (&'a self, to: &mut Tui, lang: &'a impl Expression) -> Usually> { tek_draw_word(self, to, lang) } } fn tek_draw_expr (state: &App, to: &mut Tui, lang: &impl Expression) -> Usually> { if let Some(area) = eval_view(state, to, lang)? { Ok(area) } else if let Some(area) = eval_view_tui(state, to, lang)? { Ok(area) } else { Err(format!("App::interpret_expr: unexpected: {lang:?}").into()) } } fn tek_draw_word (state: &App, to: &mut Tui, dsl: &impl Expression) -> Usually> { let mut frags = dsl.src()?.unwrap().split("/"); match frags.next() { Some(":logo") => view_logo().draw(to), Some(":meters") => draw_meter_section(to, frags), Some(":tracks") => draw_tracks(to, frags, state), Some(":scenes") => draw_scenes(to, frags), Some(":dialog") => draw_dialog(to, frags, state, dsl), Some(":templates") => draw_templates(to, frags, state), Some(":sessions") => view_sessions().draw(to), Some(":browse/title") => view_browse_title(state).draw(to), Some(":device") => view_device(state).draw(to), Some(":status") => h_exact(1, "TODO: Status Bar").draw(to), Some(":editor") => "TODO Editor".draw(to), Some(":debug") => h_exact(1, format!("[{:?}]", to.area())).draw(to), Some(_) => { let views = state.config.views.read().unwrap(); if let Some(dsl) = views.get(dsl.src()?.unwrap()) { let dsl = dsl.clone(); std::mem::drop(views); state.interpret(to, &dsl) } else { unimplemented!("{dsl:?}"); } }, _ => unreachable!() } } pub fn draw_meter_section (to: &mut Tui, mut frags: std::str::Split<&str>) -> Usually> { match frags.next() { Some("input") => bg(Rgb(30, 30, 30), h_full(origin_s("Input Meters"))).draw(to), Some("output") => bg(Rgb(30, 30, 30), h_full(origin_s("Output Meters"))).draw(to), _ => panic!() } } pub fn draw_tracks (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App) -> Usually> { match frags.next() { None => "TODO tracks".draw(to), Some("names") => state.project.view_track_names(state.color.clone()).draw(to),//bg(Rgb(40, 40, 40), w_full(origin_w("Track Names")))), Some("inputs") => bg(Rgb(40, 40, 40), w_full(origin_w("Track Inputs"))).draw(to), Some("devices") => bg(Rgb(40, 40, 40), w_full(origin_w("Track Devices"))).draw(to), Some("outputs") => bg(Rgb(40, 40, 40), w_full(origin_w("Track Outputs"))).draw(to), _ => panic!() } } pub fn draw_scenes (to: &mut Tui, mut frags: std::str::Split<&str>) -> Usually> { match frags.next() { None => "TODO Scenes".draw(to), Some(":scenes/names") => "TODO Scene Names".draw(to), _ => panic!() } } pub fn draw_dialog ( to: &mut Tui, mut frags: std::str::Split<&str>, state: &App, dsl: &impl Expression ) -> Usually> { match frags.next() { Some("menu") => if let Dialog::Menu(selected, items) = &state.dialog { let items = items.clone(); let selected = selected; Some(wh_full(thunk(move|to: &mut Tui|{ for (index, MenuItem(item, _)) in items.0.iter().enumerate() { y_push((2 * index) as u16,fg_bg( if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) }, if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) }, h_exact(2, origin_n(w_full(item))) )).draw(to); } Ok(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) -> Usually> { let height = (state.config.modes.len() * 2) as u16; h_exact(height, w_min(Some(30), thunk(move |to: &mut Tui|{ let mut index = 0; state.config.modes.for_each(|id, profile| { let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) }; let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or(""); let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); let fg1 = Rgb(224, 192, 128); let fg2 = Rgb(224, 128, 32); let field_name = w_full(origin_w(fg(fg1, name))); let field_id = w_full(origin_e(fg(fg2, id))); let field_info = w_full(origin_w(info)); let _ = y_push((2 * index) as u16, h_exact(2, w_full(bg(b, south( above(field_name, field_id), field_info ))))).draw(to); index += 1; }); Ok(to.area().into()) }))).draw(to) } /// ``` /// let _ = tek::view_logo(); /// ``` pub fn view_logo () -> impl Draw { wh_exact(Some(32), Some(7), bold(true, fg(Rgb(240, 200, 180), south!{ h_exact(1, ""), h_exact(1, ""), h_exact(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"), h_exact(1, east("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", east(fg(Rgb(230,100,40), "v0.3.0"), " ~~"))), h_exact(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"), }))) } /// ``` /// let x = ""; /// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref()); /// let _ = tek::view_transport(false, x.as_ref(), x.as_ref(), x.as_ref()); /// ``` pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw { let theme = ItemTheme::G[96]; bg(Black, east!(above( wh_full(origin_w(button_play_pause(play, false))), wh_full(origin_e(east!( field_h(theme, "BPM", bpm), field_h(theme, "Beat", beat), field_h(theme, "Time", time), ))) ))) } /// ``` /// let x = ""; /// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref()); /// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref()); /// ``` pub fn view_status (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw { let theme = ItemTheme::G[96]; let sr = field_h(theme, "SR", sr); let buf = field_h(theme, "Buf", buf); let lat = field_h(theme, "Lat", lat); bg(Black, east!(above( wh_full(origin_w(sel.map(|sel|field_h(theme, "Selected", sel)))), wh_full(origin_e(east!(sr, buf, lat))), ))) } /// ``` /// let _ = tek::button_play_pause(true, true); /// let _ = tek::button_play_pause(true, false); /// let _ = tek::button_play_pause(false, true); /// let _ = tek::button_play_pause(false, false); /// ``` pub fn button_play_pause (playing: bool, compact: bool) -> impl Draw { bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, either(compact, thunk(move|to: &mut Tui|w_exact(9, either(playing, fg(Rgb(0, 255, 0), " PLAYING "), fg(Rgb(255, 128, 0), " STOPPED ")) ).draw(to)), thunk(move|to: &mut Tui|w_exact(5, either(playing, fg(Rgb(0, 255, 0), south(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), fg(Rgb(255, 128, 0), south(" ▗▄▖ ", " ▝▀▘ ",))) ).draw(to)), ) ) } #[cfg(feature = "track")] pub fn view_track_row_section ( _theme: ItemTheme, button: impl Draw, button_add: impl Draw, content: impl Draw, ) -> impl Draw { west(h_full(w_exact(4, origin_nw(button_add))), east(w_exact(20, h_full(origin_nw(button))), wh_full(origin_c(content)))) } /// ``` /// let bg = tengri::ratatui::style::Color::Red; /// let fg = tengri::ratatui::style::Color::Green; /// let _ = tek::view_wrap(bg, fg, "and then blue, too!"); /// ``` pub fn view_wrap (bg: Color, fg: Color, content: impl Draw) -> impl Draw { let left = fg_bg(bg, Reset, w_exact(1, y_repeat("▐"))); let right = fg_bg(bg, Reset, w_exact(1, y_repeat("▌"))); east(left, west(right, fg_bg(fg, bg, content))) } /// ``` /// let _ = tek::view_meter("", 0.0); /// let _ = tek::view_meters(&[0.0, 0.0]); /// ``` pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Draw + 'a { let f = field_h(ItemTheme::G[128], label, format!("{:>+9.3}", value)); let w = if value >= 0.0 { 13 } else if value >= -1.0 { 12 } else if value >= -2.0 { 11 } else if value >= -3.0 { 10 } else if value >= -4.0 { 9 } else if value >= -6.0 { 8 } else if value >= -9.0 { 7 } else if value >= -12.0 { 6 } else if value >= -15.0 { 5 } else if value >= -20.0 { 4 } else if value >= -25.0 { 3 } else if value >= -30.0 { 2 } else if value >= -40.0 { 1 } else { 0 }; let c = if value >= 0.0 { Red } else if value >= -3.0 { Yellow } else { Green }; south!(f, wh_exact(Some(w), Some(1), bg(c, ()))) } pub fn view_meters (values: &[f32;2]) -> impl Draw + use<'_> { let left = format!("L/{:>+9.3}", values[0]); let right = format!("R/{:>+9.3}", values[1]); south(left, right) } pub fn view_sample_info (sample: Option<&Arc>>) -> impl Draw + use<'_> { when(sample.is_some(), thunk(move|to: &mut Tui|{ let sample = sample.unwrap().read().unwrap(); let theme = sample.color; east!( field_h(theme, "Name", format!("{:<10}", sample.name.clone())), field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())), field_h(theme, "Start", format!("{:<8}", sample.start)), field_h(theme, "End", format!("{:<8}", sample.end)), field_h(theme, "Trans", "0"), field_h(theme, "Gain", format!("{}", sample.gain)), ).draw(to) })) } pub fn view_sample_info_v (sample: Option<&Arc>>) -> impl Draw + use<'_> { let a = thunk(move|to: &mut Tui|{ let sample = sample.unwrap().read().unwrap(); let theme = sample.color; w_exact(20, south!( w_full(origin_w(field_h(theme, "Name ", format!("{:<10}", sample.name.clone())))), w_full(origin_w(field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())))), w_full(origin_w(field_h(theme, "Start ", format!("{:<8}", sample.start)))), w_full(origin_w(field_h(theme, "End ", format!("{:<8}", sample.end)))), w_full(origin_w(field_h(theme, "Trans ", "0"))), w_full(origin_w(field_h(theme, "Gain ", format!("{}", sample.gain)))), )).draw(to) }); let b = thunk(|to: &mut Tui|fg(Red, south!( bold(true, "× No sample."), "[r] record", "[Shift-F9] import", )).draw(to)); either(sample.is_some(), a, b) } pub fn view_sample_status (sample: Option<&Arc>>) -> impl Draw { bold(true, fg(g(224), sample .map(|sample|{ let sample = sample.read().unwrap(); format!("Sample {}-{}", sample.start, sample.end) }) .unwrap_or_else(||"No sample".to_string()))) } pub fn view_track_header (theme: ItemTheme, content: impl Draw) -> impl Draw { w_exact(12, bg(theme.darker.term, w_full(origin_e(content)))) } pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) -> impl Draw + use<'a, T> { let ins = ports.len() as u16; let frame = Outer(true, Style::default().fg(g(96))); let iter = move||ports.iter(); let names = iter_south(iter, move|port, index|h_full(origin_w(format!(" {index} {}", port.port_name())))); let field = field_v(theme, title, names); wh_exact(Some(20), Some(1 + ins), border(true, frame, wh_exact(Some(20), Some(1 + ins), field))) } pub fn view_io_ports <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a ) -> impl Draw + 'a { type Item<'a> = (usize, &'a Arc, &'a [Connect], usize, usize); iter(items, move|(_index, name, connections, y, y2): Item<'a>, _| { y_push(y as u16, h_exact((y2-y) as u16, south(h_full(bold(true, fg_bg(fg, bg, origin_w(east(" 󰣲 ", name))))), iter(||connections.iter(), move|connect: &'a Connect, index|y_push( index as u16, h_exact(1, origin_w(bold(false, fg_bg(fg, bg, &connect.info)))) ))))) }) } pub fn view_scenes_clips <'a, S: ScenesSizes<'a>> ( scenes: impl Fn()->S, tracks: impl TracksSizes<'a>, select: &Selection, editor: Option<&MidiEditor>, size: &Size, editing: bool, ) -> impl Draw { let status = wh_full(origin_se(fg(Green, format!("{}x{}", size.w(), size.h())))); let tracks = iter_once(tracks, move|(track_index, track, _, _), _| { let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| { let (name, theme): (Arc, ItemTheme) = scene_name_theme(scene, track_index); let f = theme.lightest.term; let (b, o) = scene_bg(theme, select, track_index, scene_index); let w = scene_w(track, select, track_index, editor); let y = scene_y(select, scene_index, editor); let is_selected = scene_sel(select, track_index, scene_index, editing); wh_exact(Some(w), Some(y), below( wh_full(Outer(true, Style::default().fg(o))), wh_full(below( below( fg_bg(o, b, wh_full("")), wh_full(origin_nw(fg_bg(f, b, bold(true, name)))), ), wh_full(when(is_selected, editor.map(|e|e.view()))))))) }); w_exact(track.width as u16, h_full(scenes)) }); return size.of(wh_full(above(status, tracks))); fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc, ItemTheme) { if let Some(Some(clip)) = &scene.clips.get(track_index) { let clip = clip.read().unwrap(); (format!(" ⏹ {}", &clip.name).into(), clip.color) } else { (" ⏹ -- ".into(), ItemTheme::G[32]) } } fn scene_bg ( theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize ) -> (Color, Color) { let mut outline = theme.base.term; (if select.track() == Some(track_index) && select.scene() == Some(scene_index) { outline = theme.lighter.term; theme.light.term } else if select.track() == Some(track_index) || select.scene() == Some(scene_index) { outline = theme.darkest.term; theme.base.term } else { theme.dark.term }, outline) } fn scene_w ( track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor> ) -> u16 { if select.track() == Some(track_index) && let Some(editor) = editor { (editor.size.w() as usize).max(24).max(track.width) as u16 } else { track.width as u16 } } fn scene_y ( select: &Selection, scene_index: usize, editor: Option<&MidiEditor> ) -> u16 { if select.scene() == Some(scene_index) && let Some(editor) = editor { editor.size.h().max(12) } else { H_SCENE as u16 } } fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool { editing && select.track() == Some(track_index) && select.scene() == Some(scene_index) } } pub fn view_track_names ( theme: ItemTheme, tracks: impl TracksSizes<'_>, track_count: usize, scene_count: usize, selected: &Selection, ) -> impl Draw { let button = south( button_3("t", "rack ", format!("{}{track_count}", selected.track() .map(|track|format!("{track}/")).unwrap_or_default()), false), button_3("s", "cene ", format!("{}{scene_count}", selected.scene() .map(|scene|format!("{scene}/")).unwrap_or_default()), false)); let button_2 = south( button_2("T", "+", false), button_2("S", "+", false)); view_track_row_section(theme, button, button_2, bg(theme.darker.term, h_exact(2, thunk(|to: &mut Tui|{ for (index, track, x1, _x2) in tracks { x_push(x1 as u16, w_exact(track_width(index, track), bg(if selected.track() == Some(index) { track.color.light.term } else { track.color.base.term }, south(w_full(origin_nw(east( format!("·t{index:02} "), fg(Rgb(255, 255, 255), bold(true, &track.name)) ))), ""))) ).draw(to); } Ok(XYWH(0, 0, 0, 0)) })))) } pub fn view_track_outputs ( theme: ItemTheme, tracks: impl TracksSizes<'_>, midi_outs: impl Iterator, ) -> impl Draw { view_track_row_section(theme, south(w_full(origin_w(button_2("o", "utput", false))), thunk(|to: &mut Tui|{ for port in midi_outs { w_full(origin_w(port.port_name())).draw(to); } Ok(XYWH(0, 0, 0, 0)) })), button_2("O", "+", false), bg(theme.darker.term, origin_w(thunk(|to: &mut Tui|{ for (index, track, _x1, _x2) in tracks { let iter = ||track.sequencer.midi_outs.iter(); let draw = |port: &MidiOutput, _|fg(Rgb(255, 255, 255), h_exact(1, bg(track.color.dark.term, w_full(origin_w( format!("·o{index:02} {}", port.port_name())))))); w_exact(track_width(index, track), origin_nw(h_full(iter_south(iter, draw)))).draw(to); } Ok(XYWH(0, 0, 0, 0)) })))) } pub fn view_track_inputs ( theme: ItemTheme, tracks: impl TracksSizes<'_>, height: u16, ) -> impl Draw { view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), bg(theme.darker.term, origin_w(thunk(move|to: &mut Tui|{ for (index, track, _x1, _x2) in tracks { wh_exact(Some(track_width(index, track)), Some(height + 1), origin_nw(south( bg(track.color.base.term, w_full(origin_w(east!( either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "), either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), )))), iter_south(||track.sequencer.midi_ins.iter(), |port, _|fg_bg(Rgb(255, 255, 255), track.color.dark.term, w_full(origin_w(format!("·i{index:02} {}", port.port_name())))))))) .draw(to); } Ok(XYWH(0, 0, 0, 0)) })))) } pub fn view_scenes_names ( scenes: impl ScenesSizes<'_>, select: &Selection, editor: Option<&MidiEditor>, editing: bool, ) -> impl Draw { w_exact(20, thunk(move |to: &mut Tui|{ for (index, scene, ..) in scenes { view_scene_name(select, editor, index, scene, editing).draw(to); } Ok(XYWH(1, 1, 1, 1)) })) } pub fn view_scene_name ( select: &Selection, editor: Option<&MidiEditor>, index: usize, scene: &Scene, editing: bool ) -> impl Draw { let h = if select.scene() == Some(index) && let Some(_editor) = editor { 7 } else { H_SCENE as u16 }; let a = w_full(origin_w(east(format!("·s{index:02} "), fg(g(255), bold(true, &scene.name))))); let b = when(select.scene() == Some(index) && editing, wh_full(origin_nw(south( editor.as_ref().map(|e|e.clip_status()), editor.as_ref().map(|e|e.edit_status()))))); let c = if select.scene() == Some(index) { scene.color.light.term } else { scene.color.base.term }; wh_exact(Some(20), Some(h), bg(c, origin_nw(south(a, b)))) } pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins)) } pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs)) } pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins())) } pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) } pub fn view_track_per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw { iter_east(tracks, move|(index, track, x1, x2): (usize, &Track, usize, usize), _|{ w_exact((x2 - x1) as u16, fg_bg( track.color.lightest.term, track.color.base.term, callback(index, track))) }) } pub fn view_per_track () -> impl Draw {} pub fn view_per_track_top () -> impl Draw {} pub fn view_inputs (tracks: impl TracksSizes<'_>, midi_ins: &[MidiInput]) -> impl Draw { let title_1 = wh_exact(Some(20), Some(1), origin_w(button_3("i", "nput ", format!("{}", midi_ins.len()), false))); let title_2 = wh_exact(Some(4), Some(1), w_exact(4, button_2("I", "+", false))); east(title_1, west(title_2, thunk(move|to: &mut Tui|{ for (_index, track, x1, _x2) in tracks { south( x_push(x1 as u16, bg(track.color.dark.term, origin_w(w_exact(track.width as u16, east!( either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), either(track.sequencer.recording, fg(Red, "rec "), "rec "), either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "), ))))), thunk(move |to: &mut Tui|{ for (index, port) in midi_ins.iter().enumerate() { x_push(index as u16 * 10, h_exact(1, east(w_exact(20, origin_w(east(" ● ", bold(true, fg(Rgb(255,255,255), port.port_name()))))), west(w_exact(4, ()), thunk(move|to: &mut Tui|{ bg(track.color.darker.term, origin_w(w_exact(track.width as u16, east!( either(track.sequencer.monitoring, fg(Green, " ● "), " · "), either(track.sequencer.recording, fg(Red, " ● "), " · "), either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), )))).draw(to); todo!() }))))).draw(to); } todo!() }) ).draw(to); } todo!() }))) } pub fn view_outputs ( theme: ItemTheme, tracks: impl TracksSizes<'_>, midi_outs: &[MidiOutput], height: u16, ) -> impl Draw { let list = south( h_exact(1, w_full(origin_w(button_3( "o", "utput", format!("{}", midi_outs.len()), false )))), h_exact(height - 1, wh_full(origin_nw(thunk(|to: &mut Tui|{ for (_index, port) in midi_outs.iter().enumerate() { h_exact(1,w_full(east( origin_w(east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name())))), w_full(origin_e(format!("{}/{} ", port.port().get_connections().len(), port.connections.len())))))).draw(to); for (index, conn) in port.connections.iter().enumerate() { h_exact(1, w_full(origin_w(format!(" c{index:02}{}", conn.info())))) .draw(to); } } todo!(); })))) ); h_exact(height, view_track_row_section(theme, list, button_2("O", "+", false), bg(theme.darker.term, origin_w(w_full( thunk(|to: &mut Tui|{ for (index, track, _x1, _x2) in tracks { w_exact(track_width(index, track), thunk(|to: &mut Tui|{ h_exact(1, origin_w(east( either(true, fg(Green, "play "), "play "), either(false, fg(Yellow, "solo "), "solo "), ))).draw(to); for (_index, port) in midi_outs.iter().enumerate() { h_exact(1, origin_w(east( either(true, fg(Green, " ● "), " · "), either(false, fg(Yellow, " ● "), " · "), ))).draw(to); for (_index, _conn) in port.connections.iter().enumerate() { h_exact(1, w_full("")).draw(to); } } todo!() }) ).draw(to); } todo!() }) ))) )) } pub fn view_track_devices ( theme: ItemTheme, tracks: impl TracksSizes<'_>, track: Option<&Track>, h: u16, ) -> impl Draw { view_track_row_section(theme, button_3("d", "evice", format!("{}", track.map(|t|t.devices.len()).unwrap_or(0)), false), button_2("D", "+", false), iter_once(tracks, move|(_, track, _x1, _x2), index| wh_exact( Some(track_width(index, track)), Some(h + 1), bg(track.color.dark.term, origin_nw(iter_south(move||0..h, |_, _index|wh_exact(Some(track.width as u16), Some(2), fg_bg( ItemTheme::G[32].lightest.term, ItemTheme::G[32].dark.term, origin_nw(format!(" · {}", "--")) ) ))))))) } pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw + 'a { per_track_top(tracks, move|index, track|h_full(origin_y(callback(index, track)))) } pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw + 'a { origin_x(bg(Reset, iter_east(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ w_exact((x2 - x1) as u16, fg_bg( track.color.lightest.term, track.color.base.term, callback(index, track))) }))) } pub fn field_h (theme: ItemTheme, head: impl Draw, body: impl Draw) -> impl Draw { } pub fn field_v (theme: ItemTheme, head: impl Draw, body: impl Draw) -> impl Draw { } pub fn view_sessions () -> impl Draw { let h = 6; let w = Some(30); let f = Rgb(224, 192, 128); h_exact(h, w_min(w, thunk(move |to: &mut Tui|{ for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) }; let y = (2 * index) as u16; let h = 2; y_push(y, &h_exact(h, w_full(bg(b, origin_w(fg(f, name)))))).draw(to); } Ok(to.area().into()) }))) } pub fn view_browse_title (state: &App) -> impl Draw { w_full(origin_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:", }, h_exact(1, fg(g(96), x_repeat("🭻"))) ))) } pub fn view_device (state: &App) -> impl Draw { let selected = state.dialog.device_kind().unwrap(); south(bold(true, "Add device"), iter_south( move||device_kinds().iter(), move|_label: &&'static str, i|{ let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; let l = if i == selected { "[ " } else { " " }; let r = if i == selected { " ]" } else { " " }; w_full(bg(b, east(l, west(r, "FIXME device name")))) })) }