arranger: tweaks, incl. remove unused rendering code

This commit is contained in:
🪞👃🪞 2025-05-17 15:02:38 +03:00
parent 5ed69edd02
commit ef6aa9ab07
7 changed files with 65 additions and 300 deletions

View file

@ -205,16 +205,13 @@ impl Arrangement {
}
impl ScenesView for Arrangement {
fn arrangement (&self) -> &Arrangement {
self
}
fn h_scenes (&self) -> u16 {
fn h_scenes (&self) -> u16 {
(self.height() as u16).saturating_sub(20)
}
fn w_side (&self) -> u16 {
fn w_side (&self) -> u16 {
(self.width() as u16 * 2 / 10).max(20)
}
fn w_mid (&self) -> u16 {
fn w_mid (&self) -> u16 {
(self.width() as u16).saturating_sub(2 * self.w_side()).max(40)
}
}

View file

@ -6,179 +6,6 @@ where T: ScenesView + HasMidiIns + HasMidiOuts + HasSize<TuiOut> + HasTrackScrol
pub trait TracksView:
ScenesView + HasMidiIns + HasMidiOuts + HasSize<TuiOut> + HasTrackScroll + HasSelection + HasMidiIns + HasEditor
{
/// Height taken by all inputs.
fn h_inputs (&self) -> u16 {
self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all outputs.
fn h_outputs (&self) -> u16 {
self.midi_outs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Render input matrix.
fn view_inputs_0 (&self) -> impl Content<TuiOut> + '_ {
Tui::bg(Reset, Bsp::s(
self.view_input_intos(),
Bsp::s(self.view_input_routes(), self.view_input_ports()),
))
}
fn view_input_ports (&self) -> impl Content<TuiOut> + '_ {
let is_editing = self.is_editing();
Tryptich::top(1)
.left(20, button_3("i", "midi ins", format!("{}",
self.midi_ins().len()), is_editing))
.right(20, button_2("I", "add midi in", is_editing))
.middle(self.width().saturating_sub(40) as u16,
per_track_top(||self.tracks_with_sizes_scrolled(),
move|t, track|{
let rec = track.sequencer.recording;
let mon = track.sequencer.monitoring;
let rec = if rec { White } else { track.color.darkest.rgb };
let mon = if mon { White } else { track.color.darkest.rgb };
let bg = if self.arrangement().selection().track() == Some(t) {
track.color.light.rgb
} else {
track.color.base.rgb
};
//let bg2 = if t > 0 { track.color.base.rgb } else { Reset };
wrap(bg, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(
Tui::fg_bg(rec, bg, "Rec "),
Tui::fg_bg(mon, bg, "Mon "),
))))
}))
}
fn view_input_routes (&self) -> impl Content<TuiOut> + '_ {
Tryptich::top(self.h_inputs())
.left(self.w_side(),
io_ports(Tui::g(224), Tui::g(32), ||self.midi_ins_with_sizes()))
.middle(self.w_mid(),
per_track_top(||self.tracks_with_sizes_scrolled(),
move|_, &Track { color, .. }|io_conns(
color.dark.rgb, color.darker.rgb, ||self.midi_ins_with_sizes())))
}
fn view_input_intos (&self) -> impl Content<TuiOut> + '_ {
Tryptich::top(2)
.left(self.w_side(),
Bsp::s(Align::e("Input:"), Align::e("Into clip:")))
.middle(self.w_mid(),
per_track_top(||self.tracks_with_sizes_scrolled(),
|_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ ")))))
}
/// Render output matrix.
fn view_outputs_0 (&self) -> impl Content<TuiOut> + '_ {
Tui::bg(Reset, Align::n(Bsp::s(
Bsp::s(self.view_output_ports(), self.view_output_conns()),
Bsp::s(self.view_output_nexts(), self.view_output_froms()),
)))
}
fn view_output_ports (&self) -> impl Content<TuiOut> + '_ {
Tryptich::top(1)
.left(self.w_side(), self.view_output_count())
.right(self.w_side(), self.view_output_add())
.middle(self.w_mid(), self.view_output_map())
}
fn view_output_count (&self) -> impl Content<TuiOut> {
button_3("o", "midi outs", format!("{}", self.midi_outs().len()),
self.is_editing()
)
}
fn view_output_add (&self) -> impl Content<TuiOut> {
button_2("O", "add midi out", self.is_editing())
}
fn view_output_map (&self) -> impl Content<TuiOut> + '_ {
per_track_top(||self.tracks_with_sizes_scrolled(), move|i, t|{
let mute = false;
let solo = false;
let mute = if mute { White } else { t.color.darkest.rgb };
let solo = if solo { White } else { t.color.darkest.rgb };
let bg_1 = if self.arrangement().selection().track() == Some(i) {
t.color.light.rgb
} else {
t.color.base.rgb
};
let bg_2 = if i > 0 { t.color.base.rgb } else { Reset };
let mute = Tui::fg_bg(mute, bg_1, "Play ");
let solo = Tui::fg_bg(solo, bg_1, "Solo ");
wrap(bg_1, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(mute, solo))))
})
}
fn view_output_conns (&self) -> impl Content<TuiOut> + '_ {
Tryptich::top(self.h_outputs())
.left(self.w_side(), io_ports(
Tui::g(224), Tui::g(32), ||self.midi_outs_with_sizes()))
.middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(),
|_, t|io_conns(
t.color.dark.rgb,
t.color.darker.rgb,
||self.midi_outs_with_sizes()
)))
}
fn view_output_nexts (&self) -> impl Content<TuiOut> + '_ {
Tryptich::top(2).left(self.w_side(), Align::ne("From clip:"))
.middle(self.w_mid(), per_track_top(||self.tracks_with_sizes_scrolled(),
|_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default())))))
}
fn view_output_froms (&self) -> impl Content<TuiOut> + '_ {
let label = Align::ne("Next clip:");
Tryptich::top(2).left(self.w_side(), label)
.middle(self.w_mid(), per_track_top(
||self.tracks_with_sizes_scrolled(), |t, track|{
let queued = track.sequencer.next_clip.is_some();
let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ "));
let queued_clip = Thunk::new(||{
Tui::bg(Reset, if let Some((_, clip)) = track.sequencer.next_clip.as_ref() {
if let Some(clip) = clip {
clip.read().unwrap().name.clone()
} else {
"Stop".into()
}
} else {
"".into()
})
});
Either(queued, queued_clip, queued_blank)
}))
}
/// Render track headers
fn view_tracks_0 (&self) -> impl Content<TuiOut> + '_ {
let w_side = self.w_side();
let w_mid = self.w_mid();
let is_editing = self.is_editing();
let track_selected = self.arrangement().selection().track();
Tryptich::center(3)
.left(w_side,
button_3("t", "track", format!("{}", self.tracks().len()), is_editing))
.right(w_side, button_2("T", "add track", is_editing))
.middle(w_mid, per_track(||self.tracks_with_sizes_scrolled(),
move|index, track|wrap(
if track_selected == Some(index) {
track.color.light
} else {
track.color.base
}.rgb,
track.color.lightest.rgb,
Tui::bold(true, Fill::xy(Align::nw(&track.name)))
)))
}
/// Render device switches.
fn view_devices_0 (&self) -> impl Content<TuiOut> + '_ {
let w_side = self.w_side();
let w_mid = self.w_mid();
let is_editing = self.is_editing();
let track_selected = self.arrangement().selection().track();
Tryptich::top(1)
.left(w_side, button_3("d", "devices", format!("{}", 0), is_editing))
.right(w_side, button_2("D", "add device", is_editing))
.middle(w_mid, per_track_top(||self.tracks_with_sizes_scrolled(),
move|index, track|{
let bg = if track_selected == Some(index) {
track.color.light
} else {
track.color.base
};
let fg = Tui::g(224);
track.devices.get(0).map(|device|wrap(bg.rgb, fg, device.name()))
}))
}
fn tracks_width_available (&self) -> u16 {
(self.width() as u16).saturating_sub(40)
}
@ -188,6 +15,11 @@ pub trait TracksView:
((x2 as u16) < self.tracks_width_available())
.then_some((t, track, x1, x2)))
}
fn view_track_header <'a, T: Content<TuiOut>> (
&'a self, theme: ItemTheme, content: T
) -> impl Content<TuiOut> {
Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content))))
}
fn view_track_names (&self, theme: ItemTheme) -> impl Content<TuiOut> {
let content = Fixed::y(1, Align::w(Tui::bg(theme.darker.rgb, Align::w(Fill::x(
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
@ -230,11 +62,11 @@ pub trait TracksView:
track.color.light.rgb
} else {
track.color.base.rgb
}, Fill::x(Align::w(format!("[mut] [sol]")))),
}, Fill::x(Align::w(format!("·mute ·solo")))),
Map::south(2, ||track.sequencer.midi_outs.iter(),
|port, index|Tui::fg(Rgb(255, 255, 255),
Fixed::y(2, Tui::bg(track.color.dark.rgb, Fill::x(Align::w(
format!("o{index}: {}", port.name())))))))))));
format!("·o{index}: {}", port.name())))))))))));
}
}))))));
Bsp::w(
@ -245,35 +77,6 @@ pub trait TracksView:
content
)
}
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> {
let mut h = 0u16;
for track in self.tracks().iter() {
h = h.max(track.sequencer.midi_ins.len() as u16);
}
let content = Tui::bg(theme.darker.rgb, Align::w(Fill::x(
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
for (index, track, x1, x2) in self
.tracks_with_sizes(&self.selection(), None)
.skip(self.track_scroll())
{
add(&Fixed::xy(track.width as u16, h + 1,
Align::nw(Bsp::s(
Tui::bg(track.color.base.rgb,
Fill::x(Align::w(format!("[rec] [mon]")))),
Map::south(1, ||track.sequencer.midi_ins.iter(),
|port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb,
Fill::x(Align::w(format!("i{index}: {}", port.name())))))))));
}
}))));
Bsp::w(
self.view_track_header(theme, row!(
Tui::bold(true, button_2("i", "nputs", false)),
button_2("I", "+", false)
)),
Fixed::y(h, Fill::x(Align::w(Fixed::y(h + 1, content)))),
)
}
fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> {
let mut h = 2u16;
for track in self.tracks().iter() {
@ -292,14 +95,38 @@ pub trait TracksView:
{
add(&Fixed::xy(track.width as u16, h + 1,
Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h,
|_, index|format!("d{index}: {}", "--------"))))));
|_, index|format!("·d{index}: {}", "--------"))))));
}
}))))))
}
fn view_track_header <'a, T: Content<TuiOut>> (
&'a self, theme: ItemTheme, content: T
) -> impl Content<TuiOut> {
Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content))))
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> {
let mut h = 0u16;
for track in self.tracks().iter() {
h = h.max(track.sequencer.midi_ins.len() as u16);
}
let content = Tui::bg(theme.darker.rgb, Align::w(Fill::x(
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
for (index, track, x1, x2) in self
.tracks_with_sizes(&self.selection(), None)
.skip(self.track_scroll())
{
add(&Fixed::xy(track.width as u16, h + 1,
Align::nw(Bsp::n(
Tui::bg(track.color.base.rgb,
Fill::x(Align::w(format!("·mon ·rec ·dub")))),
Map::south(1, ||track.sequencer.midi_ins.iter(),
|port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb,
Fill::x(Align::w(format!("·i{index}: {}", port.name())))))))));
}
}))));
Bsp::w(
self.view_track_header(theme, row!(
Tui::bold(true, button_2("i", "nputs", false)),
button_2("I", "+", false)
)),
Fixed::y(h, Fill::x(Align::w(Fixed::y(h + 1, content)))),
)
}
}
@ -308,10 +135,9 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync {
const H_SCENE: usize = 2;
/// Default editor height.
const H_EDITOR: usize = 15;
fn arrangement (&self) -> &Arrangement;
fn h_scenes (&self) -> u16;
fn w_side (&self) -> u16;
fn w_mid (&self) -> u16;
fn h_scenes (&self) -> u16;
fn w_side (&self) -> u16;
fn w_mid (&self) -> u16;
fn view_scenes_names (&self) -> impl Content<TuiOut> {
Stack::south(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
for (index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) {
@ -320,81 +146,25 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + Send + Sync {
})
}
fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content<TuiOut> {
Fixed::xy(20, if self.selection().scene() == Some(index) && let Some(editor) = self.editor () {
let h = if self.selection().scene() == Some(index) && let Some(editor) = self.editor() {
(editor.height() as u16).max(12)
} else {
Self::H_SCENE as u16
}, Tui::bg(if self.selection().scene() == Some(index) {
};
let bg = if self.selection().scene() == Some(index) {
scene.color.light.rgb
} else {
scene.color.base.rgb
}, Align::nw(Bsp::e(
format!(" {index:2} "),
Tui::fg(Rgb(255, 255, 255),
Tui::bold(true, format!("{}", scene.name)))))))
//let height = (1 + y2 - y1 as u16;
//let name = Some(scene.name.clone());
//let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name))));
//let selected = self.scene_selected() == Some(s);
//let neighbor = s > 0 && self.scene_selected() == Some(s - 1);
//let is_last = self.scenes().len().saturating_sub(1) == s;
//let theme = scene.color;
//let fg = theme.lightest.rgb;
//let bg = if selected { theme.light } else { theme.base }.rgb;
//let hi = if let Some(previous) = previous {
//if neighbor { previous.light.rgb } else { previous.base.rgb }
//} else {
//Reset
//};
//let lo = if is_last { Reset } else if selected {
//theme.light.rgb
//} else {
//theme.base.rgb
//};
//add(&Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat {
//width: 0, height: 0, content, colors: [fg, bg, hi, lo]
//}))))
//}
}
fn scenes_with_prev_color (&self) -> impl Iterator<Item=SceneWith<Option<ItemTheme>>> + Send + Sync {
self.scenes_iter().map(|(s, scene, y1, y2)|(s, scene, y1, y2,
(s>0).then(||self.arrangement().scenes()[s-1].color)))
}
fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
tracks: impl Fn() -> U + Send + Sync + 'a,
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
Tui::fg_bg(track.color.lightest.rgb, track.color.darker.rgb,
map_east(x1 as u16, (x2 - x1) as u16, callback(index, track))) })
}
fn scenes_with_clip (&self, track_index: usize) -> impl Iterator<Item=SceneWith<'_, Option<ItemTheme>>> + Send + Sync {
self.scenes_iter().map(move|(s, scene, y1, y2)|(s, scene, y1, y2,
(s>0).then(||self.arrangement().scenes()[s-1].clips[track_index].as_ref()
.map(|c|c.read().unwrap().color)
.unwrap_or(ItemTheme::G[32]))))
}
/// A scene with size and color.
fn scenes_iter (&self) -> impl Iterator<Item=(usize, &Scene, usize, usize,)> + Send + Sync {
let selection = Has::<Selection>::get(self.arrangement());
self.arrangement().scenes_with_sizes(
false, // FIXME self.is_editing(),
Self::H_SCENE, Self::H_EDITOR,
selection.track(), selection.scene(),
).map_while(|(s, scene, y1, y2)|(y2<=self.h_scenes() as usize)
.then_some((s, scene, y1, y2)))
}
/// Height required to display all scenes.
fn h_scenes_total (&self) -> u16 {
self.scenes_with_sizes(
self.is_editing(),
Self::H_SCENE,
Self::H_EDITOR,
self.selection().track(),
self.selection().scene(),
)
.last()
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
};
Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s(
Fill::x(Align::w(Bsp::e(
format!(" {index:2} "),
Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", scene.name)))
))),
When(self.selection().scene() == Some(index) && self.is_editing(),
Fill::xy(Align::nw(Bsp::s(
self.editor().as_ref().map(|e|e.clip_status()),
self.editor().as_ref().map(|e|e.edit_status())))))))))
}
}

View file

@ -13,9 +13,9 @@ impl MidiEditor {
(clip.color, clip.name.clone(), clip.length, clip.looped)
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
Fixed::x(20, col!(
Fill::x(Align::w(FieldV(color, "Clip ", format!("{name}")))),
Fill::x(Align::w(FieldH(color, "Length", format!("{length}")))),
Fill::x(Align::w(FieldH(color, "Loop ", looped.to_string()))),
Fill::x(Align::w(Bsp::e(" Clip ", format!("{name}")))),
Fill::x(Align::w(Bsp::e(" Length ", format!("{length}")))),
Fill::x(Align::w(Bsp::e(" Loop ", looped.to_string()))),
))
}
@ -31,9 +31,9 @@ impl MidiEditor {
let note_pos = format!("{:>3}", note_pos);
let note_len = format!("{:>4}", self.get_note_len());
Fixed::x(20, col!(
Fill::x(Align::w(FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos}")))),
Fill::x(Align::w(FieldH(color, "Lock", format!("{time_lock}")))),
Fill::x(Align::w(FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")))),
Fill::x(Align::w(Bsp::e(" Time ", format!("{length}/{time_zoom}+{time_pos}")))),
Fill::x(Align::w(Bsp::e(" Lock ", format!("{time_lock}")))),
Fill::x(Align::w(Bsp::e(" Note ", format!("{note_name} {note_pos} {note_len}")))),
))
}