tek/crates/device/src/arranger/arranger_scenes.rs

266 lines
11 KiB
Rust

use crate::*;
impl<T: Has<Vec<Scene>> + Send + Sync> HasScenes for T {}
pub trait HasScenes: Has<Vec<Scene>> + Send + Sync {
fn scenes (&self) -> &Vec<Scene> {
Has::<Vec<Scene>>::get(self)
}
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
Has::<Vec<Scene>>::get_mut(self)
}
fn scenes_with_sizes (
&self,
editing: bool,
height: usize,
larger: usize,
selected_track: Option<usize>,
selected_scene: Option<usize>,
) -> impl ScenesSizes<'_> {
let mut y = 0;
self.scenes().iter().enumerate().map(move|(s, scene)|{
let active = editing && selected_track.is_some() && selected_scene == Some(s);
let height = if active { larger } else { height };
let data = (s, scene, y, y + height);
y += height;
data
})
}
/// Generate the default name for a new scene
fn scene_default_name (&self) -> Arc<str> {
format!("Sc{:3>}", self.scenes().len() + 1).into()
}
fn scene_longest_name (&self) -> usize {
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
}
}
pub trait HasSceneScroll: HasScenes {
fn scene_scroll (&self) -> usize;
}
impl HasSceneScroll for Arrangement {
fn scene_scroll (&self) -> usize { self.scene_scroll }
}
impl<'a> HasSceneScroll for ArrangerView<'a> {
fn scene_scroll (&self) -> usize { self.arrangement.scene_scroll() }
}
pub type SceneWith<'a, T: Send + Sync> = (usize, &'a Scene, usize, usize, T);
pub trait ScenesView: HasSceneScroll + Send + Sync {
/// Default scene height.
const H_SCENE: usize = 2;
/// Default editor height.
const H_EDITOR: usize = 15;
/// Render scenes with clips
fn scenes_view <'a> (&'a self, editor: &'a Option<MidiEditor>) -> impl Content<TuiOut> + 'a {
Tryptich::center(self.scenes_height())
.left(self.width_side(), self.scenes_names())
.middle(self.width_mid(), self.scenes_clips(editor))
}
fn is_editing (&self) -> bool;
fn arrangement (&self) -> &Arrangement;
fn scene_last (&self) -> usize;
fn scene_selected (&self) -> Option<usize>;
fn track_selected (&self) -> Option<usize>;
fn scenes_height (&self) -> u16;
fn width_side (&self) -> u16;
fn width_mid (&self) -> u16;
fn scenes_names (&self) -> impl Content<TuiOut> {
let h = self.scenes_with_prev_color().last().map(|(_,_,_,h,_)|h as u16).unwrap_or(0);
Fixed::y(h, Map::new(move||self.scenes_with_prev_color(),
move|(s, scene, y1, y2, previous): SceneWith<'_, Option<ItemTheme>>, _|{
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.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]
})))
}))
}
fn scenes_names_2 (&self, theme: ItemTheme) -> impl Content<TuiOut> {
let h = self.scenes().len() as u16 * 2;
let bg = theme.darker.rgb;
Fixed::y(h, Tui::bg(bg, Align::w(Fill::x(Map::new(
||self.scenes().iter().skip(self.scene_scroll()),
move|scene: &Scene, index|
Push::y(index as u16 * 2u16, Fixed::xy(20, 2,
Tui::bg(scene.color.dark.rgb, Align::nw(Bsp::e(
format!(" {index:2} "),
Tui::fg(Rgb(255, 255, 255),
Tui::bold(true, format!("{}", scene.name)))))))))))))
}
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_clips <'a> (&'a self, editor: &'a Option<MidiEditor>)
-> impl Content<TuiOut> + 'a
{
let h = self.scenes_with_prev_color().last().map(|(_,_,_,h,_)|h as u16).unwrap_or(0);
Fixed::y(h, Self::per_track(||self.tracks_with_sizes_scrolled(),
move|track_index, track|Map::new(move||self.scenes_with_clip(track_index),
move|(s, scene, y1, y2, previous): SceneWith<'_, Option<ItemTheme>>, _|{
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 content = Fill::x(Align::w(Tui::bold(true, Bsp::e("", name))));
let same_track = self.track_selected() == Some(track_index);
let selected = same_track && self.scene_selected() == Some(s);
let neighbor = same_track && s > 0 && self.scene_selected() == Some(s - 1);
let is_last = self.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
};
let height = (1 + y2 - y1) as u16;
map_south(y1 as u16, height, Bsp::b(Fixed::y(height, Phat {
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
}), When(
self.is_editing() && same_track && self.scene_selected() == Some(s),
editor
)))
})))
}
fn scenes_clips_2 (&self, theme: ItemTheme) -> impl Content<TuiOut> {
let h = self.scenes().len() as u16 * 2;
let bg = theme.darker.rgb;
Fixed::y(h, Tui::bg(bg, Align::w(Fill::x(Map::new(
||self.scenes().iter().skip(self.scene_scroll()),
move|scene: &Scene, index1|
Push::y(index1 as u16 * 2u16, Fixed::xy(20, 2,
Map::new(
move||scene.clips.iter().skip(self.track_scroll()),
move|clip: &Option<Arc<RwLock<MidiClip>>>, index2|{
let (theme, text) = if let Some(clip) = clip {
let clip = clip.read().unwrap();
(clip.color, clip.name.clone())
} else {
(scene.color, Default::default())
};
Push::x(index2 as u16 * 14, Tui::bg(theme.dark.rgb, Bsp::e(
format!(" {index1:2} {index2:2} "),
Tui::fg(Rgb(255, 255, 255),
Tui::bold(true, format!("{}", text))))))
}))))))))
}
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(
self.is_editing(),
Self::H_SCENE, Self::H_EDITOR,
selection.track(), selection.scene(),
).map_while(|(s, scene, y1, y2)|(y2<=self.scenes_height() as usize)
.then_some((s, scene, y1, y2)))
}
fn tracks_with_sizes_scrolled <'t> (&'t self) -> impl TracksSizes<'t> {
self.arrangement()
.tracks_with_sizes(
&self.arrangement().selection(),
self.is_editing().then_some(20/*FIXME*/)
)
.map_while(move|(t, track, x1, x2)|{
(self.width_mid() > x2 as u16).then_some((t, track, x1, x2))
})
}
}
impl<'a> ScenesView for ArrangerView<'a> {
fn arrangement (&self) -> &Arrangement {
self.arrangement
}
fn scenes_height (&self) -> u16 {
self.scenes_height
}
fn width_side (&self) -> u16 {
self.width_side
}
fn width_mid (&self) -> u16 {
self.width_mid
}
fn scene_selected (&self) -> Option<usize> {
self.arrangement.selection.scene()
}
fn scene_last (&self) -> usize {
self.scene_last
}
fn track_selected (&self) -> Option<usize> {
self.arrangement.selection.track()
}
fn is_editing (&self) -> bool {
self.is_editing
}
}
impl<T: HasScenes + HasTracks> AddScene for T {}
pub trait AddScene: HasScenes + HasTracks {
/// Add multiple scenes
fn scenes_add (&mut self, n: usize) -> Usually<()> {
let scene_color_1 = ItemColor::random();
let scene_color_2 = ItemColor::random();
for i in 0..n {
let _ = self.scene_add(None, Some(
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
))?;
}
Ok(())
}
/// Add a scene
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
-> Usually<(usize, &mut Scene)>
{
let scene = Scene {
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
clips: vec![None;self.tracks().len()],
color: color.unwrap_or_else(ItemTheme::random),
};
self.scenes_mut().push(scene);
let index = self.scenes().len() - 1;
Ok((index, &mut self.scenes_mut()[index]))
}
}