implement controller traits
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
i do not exist 2026-08-11 17:41:51 +03:00
parent a4931d8e4f
commit def7a1b210
17 changed files with 2209 additions and 1790 deletions

View file

@ -239,8 +239,8 @@ fn run_new_plain (config: Config) -> Usually<()> {
connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter()
));
//&jack, Clock::new(&jack, *bpm)?, &lf, &lt, &rf, &rt, &mf, &mt, &mfr, &mtr)?;
proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?;
proj.scenes_add(scenes.unwrap_or(0))?;
proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?;
proj.scenes_add_many(scenes.unwrap_or(0))?;
//if matches!(self, Action::Status) {
//// Show status and exit
//tek_print_status(&proj);
@ -298,49 +298,6 @@ mod config {
/// Collection of custom view definitions.
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
/// Collection of input bindings.
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
///
/// ```
/// let lang = "(@x (nop)) (@y (nop) (nop))";
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
/// ```
#[derive(Debug)]
pub struct Bind<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// A sequence of zero or more commands (e.g. [AppCommand]),
/// optionally filtered by [Condition] to form layers.
///
/// ```
/// //FIXME: Why does it overflow?
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// Condition that must evaluate to true in order to enable an input layer.
///
/// ```
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
/// ```
#[derive(Clone)]
pub struct Condition(
pub Arc<Box<dyn Fn()->bool + Send + Sync>>
);
/// Collection of UI modes.
///
/// ```
@ -651,14 +608,10 @@ mod app {
primitive!(usize: try_to_usize);
primitive!(isize: try_to_isize);
impl_has!(Clock: |self: App|self.project.clock);
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
impl_has!(Dialog: |self: App|self.dialog);
impl_has!(Jack<'static>: |self: App|self.jack);
impl_has!(Pool: |self: App|self.pool);
impl_has!(Selection: |self: App|self.project.selection);
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
impl_as_mut!(Vec<Scene>: |self: App|self.project.as_mut());
impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt());
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
impl_has_clips!( |self: App|self.pool.clips);
@ -700,6 +653,7 @@ mod app {
#[namespace(Option<usize> App::get_opt_usize)]
#[namespace(Option<Arc<RwLock<MidiClip>>> App::get_clip)]
#[namespace(Dialog App::get_dialog)]
#[namespace(ControlAxis App::get_axis)]
pub struct App {
/// Exit flag
pub exit: Exit,
@ -815,7 +769,8 @@ mod app {
if let Some(expr) = src.expr()? {
match (expr.head()?, expr.tail()?) {
(Some("g"), Some(tail)) => {
let n = try_to_u8(tail.head().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?;
let n = try_to_u8(tail.head().map_err(Into::into))?
.ok_or(LanguageError::Domain("not gray"))?;
Ok(Some(Color::Rgb(n, n, n)))
},
(Some("rgb"), Some(tail)) => {
@ -870,6 +825,14 @@ mod app {
})).transpose()
}
fn get_axis (&self, src: impl Language) -> Perhaps<ControlAxis> {
Ok(src.word()?.map(|word|Ok(match word {
"x" => ControlAxis::X,
"y" => ControlAxis::Y,
_ => return Err(format!("unknown axis {word}"))
})).transpose()?)
}
}
}
@ -877,6 +840,49 @@ pub use self::bind::*;
mod bind {
use crate::*;
/// Collection of input bindings.
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
///
/// ```
/// let lang = "(@x (nop)) (@y (nop) (nop))";
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
/// ```
#[derive(Debug)]
pub struct Bind<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// A sequence of zero or more commands (e.g. [AppCommand]),
/// optionally filtered by [Condition] to form layers.
///
/// ```
/// //FIXME: Why does it overflow?
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// Condition that must evaluate to true in order to enable an input layer.
///
/// ```
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
/// ```
#[derive(Clone)]
pub struct Condition(
pub Arc<Box<dyn Fn()->bool + Send + Sync>>
);
tui_keys!(self: App, input {
let commands = collect_commands(self, input)?;
let results = execute_commands(self, commands)?;
@ -888,24 +894,21 @@ mod bind {
-> Usually<Vec<AppCommand>>
{
let mut commands = vec![];
app.mode
.as_ref()
.and_then(|m|app.config.get_mode(m))
.map(|mode|{
for id in mode.keys.iter() {
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
&& let Some(bindings) = event_map.query(input) {
for binding in bindings {
for command in binding.commands.iter() {
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
commands.push(command)
}
app.mode.as_ref().and_then(|m|app.config.get_mode(m)).map(|mode|{
for id in mode.keys.iter() {
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
&& let Some(bindings) = event_map.query(input) {
for binding in bindings {
for command in binding.commands.iter() {
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
commands.push(command)
}
}
}
}
Ok::<_, Box<dyn Error>>(())
}).transpose()?;
}
Ok::<_, Box<dyn Error>>(())
}).transpose()?;
Ok(commands)
}
@ -914,9 +917,16 @@ mod bind {
{
let mut history = vec![];
for command in commands.into_iter() {
let result = command.act(app);
match result { Err(err) => { history.push((command, None)); return Err(err) }
Ok(undo) => { history.push((command, undo)); } };
let result = command.clone().act(app);
match result {
Err(err) => {
history.push((command, None));
return Err(err)
},
Ok(undo) => {
history.push((command, undo));
}
};
}
Ok(history)
}
@ -1002,45 +1012,62 @@ mod bind {
impl_debug!(Condition |self, w| { write!(w, "*") });
#[tek_proc::command(App)]
#[tek_proc::keyword(App)]
#[derive(Debug, Default)]
pub enum AppCommand {
#[default]
#[command(App::nop)]
#[keyword("nop")]
Nop,
#[command(App::cancel)]
#[keyword("cancel")]
Cancel,
#[command(App::confirm)]
#[keyword("confirm")]
Confirm,
#[command(App::inc)]
#[keyword("x/inc", ControlAxis::X)]
#[keyword("y/inc", ControlAxis::Y)]
Inc(ControlAxis),
#[command(App::dec)]
#[keyword("x/dec", ControlAxis::X)]
#[keyword("y/dec", ControlAxis::Y)]
Dec(ControlAxis),
#[command(App::set_dialog | Self::SetDialog)]
#[keyword("dialog")]
SetDialog(Dialog),
}
#[tek_proc::commands(AppCommand = "app")]
impl App {
fn nop (&mut self) -> Perhaps<AppCommand> {
#[command(Nop = "nop")]
pub fn nop (&mut self) -> Perhaps<AppCommand> {
Ok(None)
}
fn cancel (&mut self) -> Perhaps<AppCommand> {
#[command(Cancel = "cancel")]
pub fn cancel (&mut self) -> Perhaps<AppCommand> {
todo!()
}
#[command(Inc = "inc")]
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!()
})
}
#[command(Dec = "dec")]
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!()
})
}
#[command(Confirm = "confirm")]
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!(),
})
}
/// Swap currently active modal dialog.
///
/// ```
/// let _ = tek::App::default().set_dialog(tek::Dialog::welcome());
/// ```
#[command(SetDialog = "dialog")]
pub fn set_dialog (&mut self, dialog: Dialog) -> Perhaps<AppCommand> {
let mut dialog = dialog.clone();
std::mem::swap(&mut self.dialog, &mut dialog);
Ok(Some(AppCommand::SetDialog { dialog }))
}
}
//impl<'a> Namespace<'a, AppCommand> for App {
@ -1135,7 +1162,7 @@ mod device {
editor.set_note_pos(pitch.as_int() as usize);
}
}
let result = state.project.process_tracks(client, scope);
let result = state.project.tracks_jack_process(client, scope);
state.perf.update_from_jack_scope(t0, scope);
result
}
@ -1279,46 +1306,6 @@ mod device {
#[cfg(feature = "plugin")] pub mod plugin;
#[cfg(feature = "plugin")] pub use self::plugin::*;
pub struct Junction<T: JackPort>(T);
impl<T: JackPort> View<Tui> for Junction<T> {
fn view (&self) -> impl Draw<Tui> {
T::KIND
}
}
#[tek_proc::command(AudioInput)]
#[tek_proc::keyword(AudioInput)]
#[derive(Debug)]
pub enum AudioInputCommand {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(AudioOutput)]
#[tek_proc::keyword(AudioOutput)]
#[derive(Debug)]
pub enum AudioOutputCommand {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(MidiInput)]
#[tek_proc::keyword(MidiInput)]
#[derive(Debug)]
pub enum MidiInputCommand {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(MidiOutput)]
#[tek_proc::keyword(MidiOutput)]
#[derive(Debug)]
pub enum MidiOutputCommand {
Close,
Connect(Arc<str>),
}
}
//pub fn tui (
@ -1374,57 +1361,61 @@ mod draw {
fn view (&self) -> impl Draw<Tui> {
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)?;
}
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)?;
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("<source error>")).unwrap_or("<no source>");
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;
Ok(())
});
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))
self.draw_error(to)?;
self.draw_mode(to)?;
self.draw_debug(to)?;
Ok(Some(to.area().into()))
})
})
}
}
impl App {
fn draw_error (&self, to: &mut Tui) -> Usually<()> {
if let Some(e) = self.error.read().unwrap().as_ref() {
e.as_ref().align_c().draw(to)?;
}
Ok(())
}
fn draw_mode (&self, to: &mut Tui) -> Usually<()> {
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)?;
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("<source error>")).unwrap_or("<no source>");
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;
Ok(())
});
Ok(())
}
fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> {
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)
}
}
impl Interpret<Tui, Option<XYWH<u16>>> for App {
fn interpret_expr <'a> (&'a self, to: &mut Tui, lang: &'a impl Expression) -> Drawn<u16> {
self.keyword(to, lang)
@ -1433,12 +1424,12 @@ mod draw {
let mut frags = lang.src()?.unwrap().split("/");
match frags.next() {
//Some(":logo") => view_logo().draw(to),
Some(":meters") => match frags.next() {
Some(":meters") => match frags.next() {
Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to),
Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to),
_ => panic!()
},
Some(":tracks") => match frags.next() {
Some(":tracks") => match frags.next() {
None => "TODO tracks".draw(to),
Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))),
Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to),
@ -1446,7 +1437,7 @@ mod draw {
Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to),
_ => panic!()
},
Some(":scenes") => match frags.next() {
Some(":scenes") => match frags.next() {
None => "TODO Scenes".draw(to),
Some(":scenes/names") => "TODO Scene Names".draw(to),
_ => panic!()
@ -1506,27 +1497,6 @@ mod draw {
}).min_w(30).exact_h(height)
}
//pub fn per_track <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
//tracks: impl Fn() -> U + Send + Sync + 'a,
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
//) -> impl Draw<Tui> + 'a {
//per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y())
//}
//pub fn per_track_top <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
//tracks: impl Fn() -> U + Send + Sync + 'a,
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
//) -> impl Draw<Tui> + 'a {
//bg(Reset, iter_east(||tracks()
//.map(move|(index, track, x1, x2): (usize, &'a Track, usize, usize)|{
//fg_bg(
//track.color.lightest.term,
//track.color.base.term,
//callback(index, track)
//).exact_w((x2 - x1) as u16)
//})).align_x())
//}
pub fn field_h <T: Screen> (
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
) -> impl Draw<T> {
@ -1552,17 +1522,6 @@ mod draw {
}).min_w(w).exact_h(h)
}
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
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<Tui> {
let selected = state.dialog.device_kind().unwrap();
south(
@ -1629,21 +1588,6 @@ mod draw {
)
}
#[cfg(feature = "track")] pub fn view_track_row_section (
_theme: ItemTheme,
button: impl Draw<Tui>,
button_add: impl Draw<Tui>,
content: impl Draw<Tui>,
) -> impl Draw<Tui> {
west(
button_add.align_nw().exact_w(4).full_h(),
east(
button.align_nw().full_h().exact_w(20),
content.align_c().full_wh()
)
)
}
/// ```
/// let bg = tengri::ratatui::style::Color::Red;
/// let fg = tengri::ratatui::style::Color::Green;
@ -1687,211 +1631,10 @@ mod draw {
south(left, right)
}
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
when(sample.is_some(), draw(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<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
let a = draw(move|to: &mut Tui|{
let sample = sample.unwrap().read().unwrap();
let theme = sample.color;
south!(
field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(),
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())).align_w().full_w(),
field_h(theme, "Start ", format!("{:<8}", sample.start)) .align_w().full_w(),
field_h(theme, "End ", format!("{:<8}", sample.end)) .align_w().full_w(),
field_h(theme, "Trans ", "0") .align_w().full_w(),
field_h(theme, "Gain ", format!("{}", sample.gain)) .align_w().full_w(),
).exact_w(20).draw(to)
});
let b = draw(|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<RwLock<Sample>>>) -> impl Draw<Tui> {
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<Tui>) -> impl Draw<Tui> {
pub fn view_track_header (theme: ItemTheme, content: impl Draw<Tui>) -> impl Draw<Tui> {
bg(theme.darker.term, content.align_e().full_w()).exact_w(12)
}
pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
-> impl Draw<Tui> + use<'a, T>
{
let ins = ports.len() as u16;
let frame = Outer(true, Style::default().fg(g(96)));
let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!(
" {index} {}", port.port_name()
).align_w().full_h()));
let field = field_v(theme, title, names);
border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins)
}
pub fn view_io_ports <'a, T: PortsSizes<'a>> (
fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a
) -> impl Draw<Tui> + 'a {
type Item<'a> = (usize, &'a Arc<str>, &'a [Connect], usize, usize);
iter(items,
move|(_index, name, connections, y, y2): Item<'a>, _| south(
bold(true, fg_bg(fg, bg, east(" 󰣲 ", name).align_w())).full_h(),
iter(||connections.iter(), move|connect: &'a Connect, index|{
bold(false, fg_bg(fg, bg, &connect.info)).exact_h(1).align_w().push_y(index as u16)
})
).exact_h((y2 - y) as u16).push_y(y as u16))
}
pub fn view_scenes_clips <'a, S: ScenesSizes<'a>> (
scenes: impl Fn()->S,
tracks: impl TracksSizes<'a>,
select: &Selection,
editor: Option<&MidiEditor>,
size: &Sizer,
editing: bool,
) -> impl Draw<Tui> {
let status = fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh();
let tracks = iter_once(tracks, move|(track_index, track, _, _), _| {
let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| {
let (name, theme): (Arc<str>, 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);
below(
Outer(true, Style::default().fg(o)).full_wh(),
below(
below(
fg_bg(o, b, "".full_wh()),
fg_bg(f, b, bold(true, name)).align_nw().full_wh(),
),
when(is_selected, editor.map(|e|e.view())).full_wh()
).full_wh()
).exact_wh(w, y)
});
scenes.full_h().exact_w(track.width as u16)
});
return size.of(above(status, tracks).full_wh());
fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, 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_scene_name (
select: &Selection,
editor: Option<&MidiEditor>,
index: usize,
scene: &Scene,
editing: bool
) -> impl Draw<Tui> {
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
7
} else {
H_SCENE as u16
};
let a = east(format!("·s{index:02} "),
fg(g(255), bold(true, &scene.name))).align_w().full_w();
let b = when(select.scene() == Some(index) && editing, south(
editor.as_ref().map(|e|e.clip_status()),
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
let c = if select.scene() == Some(index) {
scene.color.light.term
} else {
scene.color.base.term
};
bg(c, south(a, b).align_nw()).exact_wh(20, h)
}
pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
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<Tui> {
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<Tui> {
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<Tui> {
track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs()))
}
//pub fn view_track_per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
//tracks: impl Fn() -> U + Send + Sync + 'a,
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
//) -> impl Draw<Tui> {
//}
/// ```
/// let _ = tek::button_2("", "", true);
/// let _ = tek::button_2("", "", false);