mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
289 lines
12 KiB
Rust
289 lines
12 KiB
Rust
use crate::*;
|
|
|
|
pub struct ArrangerView<'a> {
|
|
pub arrangement: &'a Arrangement,
|
|
|
|
pub is_editing: bool,
|
|
|
|
pub width: u16,
|
|
pub width_mid: u16,
|
|
pub width_side: u16,
|
|
|
|
pub inputs_height: u16,
|
|
pub outputs_height: u16,
|
|
|
|
pub scene_last: usize,
|
|
pub scene_scroll: Fill<Fixed<u16, ScrollbarV>>,
|
|
pub scene_selected: Option<usize>,
|
|
pub scenes_height: u16,
|
|
|
|
pub track_scroll: Fill<Fixed<u16, ScrollbarH>>,
|
|
pub track_selected: Option<usize>,
|
|
pub tracks_height: u16,
|
|
|
|
pub show_debug_info: bool,
|
|
}
|
|
|
|
impl<'a> ArrangerView<'a> {
|
|
pub fn new (
|
|
arrangement: &'a Arrangement,
|
|
editor: Option<&'a MidiEditor>
|
|
) -> Self {
|
|
let is_editing = editor.is_some();
|
|
let h_tracks_area = arrangement.h_tracks_area();
|
|
let h_scenes_area = arrangement.h_scenes_area();
|
|
let h_scenes = arrangement.h_scenes(is_editing);
|
|
Self {
|
|
arrangement,
|
|
is_editing,
|
|
|
|
width: arrangement.w(),
|
|
width_mid: arrangement.w_tracks_area(is_editing),
|
|
width_side: arrangement.w_sidebar(is_editing),
|
|
|
|
inputs_height: arrangement.h_inputs(),
|
|
outputs_height: arrangement.h_outputs(),
|
|
|
|
scenes_height: h_scenes_area,
|
|
scene_selected: arrangement.selection().scene(),
|
|
scene_last: arrangement.scenes.len().saturating_sub(1),
|
|
scene_scroll: Fill::y(Fixed::x(1, ScrollbarV {
|
|
offset: arrangement.scene_scroll,
|
|
length: h_scenes_area as usize,
|
|
total: h_scenes as usize,
|
|
})),
|
|
|
|
tracks_height: h_tracks_area,
|
|
track_selected: arrangement.selection().track(),
|
|
track_scroll: Fill::x(Fixed::y(1, ScrollbarH {
|
|
offset: arrangement.track_scroll,
|
|
length: h_tracks_area as usize,
|
|
total: h_scenes as usize,
|
|
})),
|
|
|
|
show_debug_info: false
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Content<TuiOut> for ArrangerView<'a> {
|
|
fn content (&self) -> impl Render<TuiOut> {
|
|
let ins = |x|Bsp::n(self.inputs(), x);
|
|
let tracks = |x|Bsp::s(self.tracks(), x);
|
|
let devices = |x|Bsp::s(self.devices(), x);
|
|
let outs = |x|Bsp::s(self.outputs(), x);
|
|
let bg = |x|Tui::bg(Reset, x);
|
|
//let track_scroll = |x|Bsp::s(&self.track_scroll, x);
|
|
//let scene_scroll = |x|Bsp::e(&self.scene_scroll, x);
|
|
outs(tracks(devices(ins(bg(self.scenes(&None))))))
|
|
}
|
|
}
|
|
|
|
impl<'a> ArrangerView<'a> {
|
|
/// Render input matrix.
|
|
pub(crate) fn inputs (&'a self) -> impl Content<TuiOut> + 'a {
|
|
Tui::bg(Reset, Bsp::s(
|
|
self.input_intos(),
|
|
Bsp::s(self.input_routes(), self.input_ports()),
|
|
))
|
|
}
|
|
|
|
/// Render output matrix.
|
|
pub(crate) fn outputs (&'a self) -> impl Content<TuiOut> + 'a {
|
|
Tui::bg(Reset, Align::n(Bsp::s(
|
|
Bsp::s(self.output_ports(), self.output_conns()),
|
|
Bsp::s(self.output_nexts(), self.output_froms()),
|
|
)))
|
|
}
|
|
|
|
/// Render track headers
|
|
pub(crate) fn tracks (&'a self) -> impl Content<TuiOut> + 'a {
|
|
let Self { width_side, width_mid, track_selected, is_editing, .. } = self;
|
|
Tryptich::center(3)
|
|
.left(*width_side, button_3("t", "track", format!("{}", self.arrangement.tracks.len()), *is_editing))
|
|
.right(*width_side, button_2("T", "add track", *is_editing))
|
|
.middle(*width_mid, per_track(*width_mid, ||self.tracks_with_sizes_scrolled(),
|
|
|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.
|
|
pub(crate) fn devices (&'a self) -> impl Content<TuiOut> + 'a {
|
|
let Self { width_side, width_mid, track_selected, is_editing, .. } = self;
|
|
Tryptich::top(1)
|
|
.left(*width_side, button_3("d", "devices", format!("{}", 0), *is_editing))
|
|
.right(*width_side, button_2("D", "add device", *is_editing))
|
|
.middle(*width_mid, per_track_top(*width_mid, ||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()))
|
|
}))
|
|
}
|
|
|
|
/// Default scene height.
|
|
pub(crate) const H_SCENE: usize = 2;
|
|
|
|
/// Default editor height.
|
|
pub(crate) const H_EDITOR: usize = 15;
|
|
|
|
/// Render scenes with clips
|
|
pub(crate) fn scenes (&'a self, editor: &'a Option<MidiEditor>) -> impl Content<TuiOut> + 'a {
|
|
/// A scene with size and color.
|
|
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
|
|
let Self {
|
|
arrangement,
|
|
width, width_side, width_mid,
|
|
scenes_height, scene_last, scene_selected,
|
|
track_selected, is_editing, ..
|
|
} = self;
|
|
|
|
let selection = Has::<Selection>::get(self.arrangement);
|
|
let selected_track = selection.track();
|
|
let selected_scene = selection.scene();
|
|
Tryptich::center(*scenes_height)
|
|
|
|
.left(*width_side, Map::new(
|
|
move||arrangement.scenes_with_sizes(
|
|
*is_editing, Self::H_SCENE, Self::H_EDITOR, selected_track, selected_scene,
|
|
).map_while(|(s, scene, y1, y2)|if y2 as u16 > *scenes_height {
|
|
None
|
|
} else {
|
|
Some((s, scene, y1, y2, if s == 0 {
|
|
None
|
|
} else {
|
|
Some(arrangement.scenes()[s-1].color)
|
|
}))
|
|
}),
|
|
move |(s, scene, y1, y2, previous): SceneWithColor, _|{
|
|
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 same_track = true;
|
|
let selected = same_track && *scene_selected == Some(s);
|
|
let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1);
|
|
let is_last = *scene_last == 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
|
|
};
|
|
Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat {
|
|
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
|
})))
|
|
}))
|
|
|
|
.middle(*width_mid, per_track(
|
|
*width_mid,
|
|
||self.tracks_with_sizes_scrolled(),
|
|
move|track_index, track|Map::new(
|
|
move||arrangement.scenes_with_sizes(
|
|
self.is_editing,
|
|
Self::H_SCENE,
|
|
Self::H_EDITOR,
|
|
selected_track,
|
|
selected_scene,
|
|
).map_while(move|(s, scene, y1, y2)|if y2 as u16 > self.scenes_height {
|
|
None
|
|
} else {
|
|
Some((s, scene, y1, y2, if s == 0 {
|
|
None
|
|
} else {
|
|
Some(self.arrangement.scenes[s-1].clips[track_index].as_ref()
|
|
.map(|c|c.read().unwrap().color)
|
|
.unwrap_or(ItemTheme::G[32]))
|
|
}))
|
|
}),
|
|
move|(s, scene, y1, y2, previous): SceneWithColor<'a>, _|{
|
|
let (name, theme) = if let Some(clip) = &scene.clips[track_index] {
|
|
let clip = clip.read().unwrap();
|
|
(Some(clip.name.clone()), clip.color)
|
|
} else {
|
|
(None, ItemTheme::G[32])
|
|
};
|
|
let height = (1 + y2 - y1) as u16;
|
|
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name))));
|
|
let same_track = *track_selected == Some(track_index);
|
|
let selected = same_track && *scene_selected == Some(s);
|
|
let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1);
|
|
let is_last = *scene_last == s;
|
|
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
|
|
};
|
|
map_south(y1 as u16, height, Bsp::b(Fixed::y(height, Phat {
|
|
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
|
}), When(
|
|
*is_editing && same_track && *scene_selected == Some(s),
|
|
editor
|
|
)))
|
|
})))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub(crate) fn per_track_top <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
|
width: u16,
|
|
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
) -> impl Content<TuiOut> + 'a {
|
|
Align::x(Tui::bg(Reset, Map::new(tracks,
|
|
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
|
let width = (x2 - x1) as u16;
|
|
map_east(x1 as u16, width, Fixed::x(width, Tui::fg_bg(
|
|
track.color.lightest.rgb,
|
|
track.color.base.rgb,
|
|
callback(index, track))))})))
|
|
}
|
|
|
|
pub(crate) fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
|
width: u16,
|
|
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
) -> impl Content<TuiOut> + 'a {
|
|
per_track_top(
|
|
width,
|
|
tracks,
|
|
move|index, track|Fill::y(Align::y(callback(index, track)))
|
|
)
|
|
}
|