mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
388 lines
17 KiB
Rust
388 lines
17 KiB
Rust
use crate::*;
|
|
|
|
impl Arrangement {
|
|
pub fn view_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> + 'a {
|
|
let mut h = 1u16;
|
|
for track in self.tracks().iter() {
|
|
h = h.max(track.sequencer.midi_ins.len() as u16);
|
|
}
|
|
let h = h + 1;
|
|
self.view_track_row_section(
|
|
theme,
|
|
Bsp::s(
|
|
Fixed::y(1, Fill::x(Align::w(button_3("i", "nput ", format!("{}", self.midi_ins.len()), false)))),
|
|
Fixed::y(h - 1, Fill::x(Align::nw(Stack::south(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
|
for (index, port) in self.midi_ins.iter().enumerate() {
|
|
add(&Fill::x(Align::w(format!("·i{index:02} {}", port.name()))));
|
|
}
|
|
}))))),
|
|
button_2("I", "+", false),
|
|
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() {
|
|
add(&Fixed::xy(self.track_width(index, track), h,
|
|
Align::nw(Bsp::s(
|
|
Tui::bg(track.color.base.rgb,
|
|
Fill::x(Align::w(row!(
|
|
Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "),
|
|
Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "),
|
|
Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·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:02} {}", port.name())))))))));
|
|
}})))))
|
|
}
|
|
pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> + 'a {
|
|
let mut h = 1u16;
|
|
for track in self.tracks().iter() {
|
|
h = h.max(track.sequencer.midi_outs.len() as u16);
|
|
}
|
|
let h = h + 1;
|
|
self.view_track_row_section(
|
|
theme,
|
|
Bsp::s(
|
|
Fixed::y(1, Fill::x(Align::w(button_3("o", "utput", format!("{}", self.midi_outs.len()), false)))),
|
|
Fixed::y(h - 1, Fill::xy(Align::nw(Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
|
for (index, port) in self.midi_outs().iter().enumerate() {
|
|
add(&Fill::x(Align::w(format!("·o{index:02} {}", port.name()))));
|
|
}
|
|
}))))),
|
|
button_2("O", "+", false),
|
|
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() {
|
|
add(&Fixed::x(self.track_width(index, track),
|
|
Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(),
|
|
|port, index|Tui::fg(Rgb(255, 255, 255),
|
|
Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w(
|
|
format!("·o{index:02} {}", port.name())))))))))));
|
|
}
|
|
})))))
|
|
}
|
|
pub fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> + 'a {
|
|
let mut h = 2u16;
|
|
for track in self.tracks().iter() {
|
|
h = h.max(track.devices.len() as u16);
|
|
}
|
|
self.view_track_row_section(
|
|
theme,
|
|
button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false),
|
|
button_2("D", "+", false),
|
|
Fixed::y(h, 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() {
|
|
add(&Fixed::xy(self.track_width(index, track), h + 1,
|
|
Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h,
|
|
|_, index|format!("·d{index:02} {}", "--------"))))));
|
|
}
|
|
}))))))
|
|
}
|
|
}
|
|
|
|
pub trait HasClipsSize {
|
|
fn clips_size (&self) -> &Measure<TuiOut>;
|
|
}
|
|
impl HasClipsSize for Arrangement {
|
|
fn clips_size (&self) -> &Measure<TuiOut> { &self.inner_size }
|
|
}
|
|
|
|
impl TracksView for Arrangement {}
|
|
|
|
impl<T: TracksView + ScenesView + Send + Sync> ClipsView for T {}
|
|
|
|
pub trait TracksView:
|
|
ScenesView +
|
|
HasMidiIns +
|
|
HasMidiOuts +
|
|
HasSize<TuiOut> +
|
|
HasTrackScroll +
|
|
HasSelection +
|
|
HasEditor +
|
|
HasClipsSize
|
|
{
|
|
fn tracks_width_available (&self) -> u16 {
|
|
(self.width() as u16).saturating_sub(40)
|
|
}
|
|
/// Iterate over tracks with their corresponding sizes.
|
|
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
|
let editor_width = self.editor().map(|e|e.width());
|
|
let active_track = self.selection().track();
|
|
let mut x = 0;
|
|
self.tracks().iter().enumerate().map_while(move |(index, track)|{
|
|
let width = active_track.and_then(|_|editor_width).unwrap_or(track.width.max(8));
|
|
if x + width < self.clips_size().w() {
|
|
let data = (index, track, x, x + width);
|
|
x += width + Self::TRACK_SPACING;
|
|
Some(data)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
fn view_track_row_section <'a> (
|
|
&'a self,
|
|
theme: ItemTheme,
|
|
button: impl Content<TuiOut>,
|
|
button_add: impl Content<TuiOut>,
|
|
content: impl Content<TuiOut>
|
|
) -> impl Content<TuiOut> {
|
|
Bsp::w(
|
|
Fill::y(Fixed::x(4, Align::nw(button_add))),
|
|
Bsp::e(
|
|
Fixed::x(20, Fill::y(Align::nw(button))),
|
|
Fill::xy(Align::c(content))
|
|
)
|
|
)
|
|
}
|
|
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> {
|
|
self.view_track_row_section(
|
|
theme,
|
|
button_2("t", "rack ", false),
|
|
button_2("T", "+", false),
|
|
Tui::bg(theme.darker.rgb, Fixed::y(2, Fill::x(
|
|
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
|
for (index, track, x1, x2) in self.tracks_with_sizes() {
|
|
add(&Fixed::x(self.track_width(index, track),
|
|
Tui::bg(if self.selection().track() == Some(index) {
|
|
track.color.light.rgb
|
|
} else {
|
|
track.color.base.rgb
|
|
}, Bsp::s(Fill::x(Align::nw(Bsp::e(
|
|
format!("·t{index:02} "),
|
|
Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name))
|
|
))), Tui::bg(if self.selection().track() == Some(index) {
|
|
track.color.light.rgb
|
|
} else {
|
|
track.color.base.rgb
|
|
}, Fill::x(Align::w(format!("·mute ·solo"))))))));
|
|
}
|
|
})))))
|
|
}
|
|
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, h: u16) -> impl Content<TuiOut> {
|
|
self.view_track_row_section(theme,
|
|
Bsp::s(
|
|
Fill::x(Align::w(button_2("o", "utput", false))),
|
|
Fill::xy(Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
|
for port in self.midi_outs().iter() {
|
|
add(&Fill::x(Align::w(port.name())));
|
|
}
|
|
}))),
|
|
button_2("O", "+", false),
|
|
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() {
|
|
add(&Fixed::x(self.track_width(index, track),
|
|
Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(),
|
|
|port, index|Tui::fg(Rgb(255, 255, 255),
|
|
Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w(
|
|
format!("·o{index:02} {}", port.name())))))))))));
|
|
}
|
|
})))))
|
|
}
|
|
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);
|
|
}
|
|
self.view_track_row_section(
|
|
theme,
|
|
button_2("i", "nput", false),
|
|
button_2("I", "+", false),
|
|
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() {
|
|
add(&Fixed::xy(self.track_width(index, track), h + 1,
|
|
Align::nw(Bsp::s(
|
|
Tui::bg(track.color.base.rgb,
|
|
Fill::x(Align::w(row!(
|
|
Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "),
|
|
Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "),
|
|
Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·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:02} {}", port.name())))))))));
|
|
}})))))
|
|
}
|
|
fn track_width (&self, index: usize, track: &Track) -> u16 {
|
|
(if self.selection().track() == Some(index)
|
|
&& let Some(editor) = self.editor()
|
|
{
|
|
editor.width().max(24).max(track.width)
|
|
} else {
|
|
track.width
|
|
}) as u16
|
|
}
|
|
}
|
|
|
|
pub trait ScenesView:
|
|
HasEditor +
|
|
HasSelection +
|
|
HasSceneScroll +
|
|
HasClipsSize +
|
|
Send +
|
|
Sync
|
|
{
|
|
/// Default scene height.
|
|
const H_SCENE: usize = 2;
|
|
/// Default editor height.
|
|
const H_EDITOR: usize = 15;
|
|
fn h_scenes (&self) -> u16;
|
|
fn w_side (&self) -> u16;
|
|
fn w_mid (&self) -> u16;
|
|
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
|
|
let editing = self.editor().is_some();
|
|
let height = Self::H_SCENE;
|
|
let larger = self.editor().map(|e|e.height()).unwrap_or(Self::H_SCENE);
|
|
let selected_track = self.selection().track();
|
|
let selected_scene = self.selection().scene();
|
|
let mut y = 0;
|
|
self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{
|
|
let active = editing && selected_track.is_some() && selected_scene == Some(s);
|
|
let height = if active { larger } else { height };
|
|
if y + height < self.clips_size().h() {
|
|
let data = (s, scene, y, y + height);
|
|
y += height;
|
|
Some(data)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
fn view_scenes_names (&self) -> impl Content<TuiOut> {
|
|
Stack::south(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
|
for (index, scene, ..) in self.scenes_with_sizes() {
|
|
add(&self.view_scene_name(index, scene));
|
|
}
|
|
})
|
|
}
|
|
fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content<TuiOut> {
|
|
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
|
|
};
|
|
let bg = if self.selection().scene() == Some(index) {
|
|
scene.color.light.rgb
|
|
} else {
|
|
scene.color.base.rgb
|
|
};
|
|
Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s(
|
|
Fill::x(Align::w(Bsp::e(
|
|
format!("·s{index:02} "),
|
|
Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &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())))))))))
|
|
}
|
|
}
|
|
|
|
pub trait ClipsView:
|
|
TracksView +
|
|
ScenesView +
|
|
HasClipsSize +
|
|
Send +
|
|
Sync
|
|
{
|
|
fn view_scenes_clips <'a> (&'a self)
|
|
-> impl Content<TuiOut> + 'a
|
|
{
|
|
self.clips_size().of(Fill::xy(Bsp::a(
|
|
Fill::xy(Align::se(Tui::fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))),
|
|
Stack::<TuiOut, _>::east(move|column: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
|
for (track_index, track, _, _) in self.tracks_with_sizes() {
|
|
//column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp"))));
|
|
column(&Fixed::x(
|
|
if self.selection().track() == Some(track_index)
|
|
&& let Some(editor) = self.editor()
|
|
{
|
|
editor.width().max(24).max(track.width)
|
|
} else {
|
|
track.width
|
|
} as u16,
|
|
Fill::y(self.view_track_clips(track_index, track))
|
|
))
|
|
}
|
|
}))))
|
|
}
|
|
|
|
fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content<TuiOut> + 'a {
|
|
Stack::south(move|cell: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
|
for (scene_index, scene, ..) in self.scenes_with_sizes() {
|
|
let (name, theme): (Arc<str>, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
|
let clip = clip.read().unwrap();
|
|
(clip.name.clone(), clip.color)
|
|
} else {
|
|
(" ---- ".into(), ItemTheme::G[32])
|
|
};
|
|
let fg = theme.lightest.rgb;
|
|
let mut outline = theme.base.rgb;
|
|
let bg = if self.selection().track() == Some(track_index)
|
|
&& self.selection().scene() == Some(scene_index)
|
|
{
|
|
outline = theme.lighter.rgb;
|
|
theme.light.rgb
|
|
} else if self.selection().track() == Some(track_index)
|
|
|| self.selection().scene() == Some(scene_index)
|
|
{
|
|
outline = theme.darkest.rgb;
|
|
theme.base.rgb
|
|
} else {
|
|
theme.dark.rgb
|
|
};
|
|
let w = if self.selection().track() == Some(track_index)
|
|
&& let Some(editor) = self.editor ()
|
|
{
|
|
editor.width().max(24).max(track.width)
|
|
} else {
|
|
track.width
|
|
} as u16;
|
|
let y = if self.selection().scene() == Some(scene_index)
|
|
&& let Some(editor) = self.editor ()
|
|
{
|
|
editor.height().max(12)
|
|
} else {
|
|
Self::H_SCENE
|
|
} as u16;
|
|
cell(&Fixed::xy(w, y, Bsp::b(
|
|
Fill::xy(Outer(true, Style::default().fg(outline))),
|
|
Fill::xy(Bsp::b(
|
|
Bsp::b(
|
|
Tui::fg_bg(outline, bg, Fill::xy("")),
|
|
Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))),
|
|
),
|
|
Fill::xy(When(self.selection().track() == Some(track_index)
|
|
&& self.selection().scene() == Some(scene_index)
|
|
&& self.is_editing(), self.editor())))))));
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
pub trait HasWidth {
|
|
const MIN_WIDTH: usize;
|
|
/// Increment track width.
|
|
fn width_inc (&mut self);
|
|
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
|
fn width_dec (&mut self);
|
|
}
|
|
|
|
impl HasWidth for Track {
|
|
const MIN_WIDTH: usize = 9;
|
|
fn width_inc (&mut self) {
|
|
self.width += 1;
|
|
}
|
|
fn width_dec (&mut self) {
|
|
if self.width > Track::MIN_WIDTH {
|
|
self.width -= 1;
|
|
}
|
|
}
|
|
}
|