mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
266 lines
11 KiB
Rust
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]))
|
|
}
|
|
}
|