mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-09-18 04:46:43 +02:00
301 lines
9 KiB
Rust
301 lines
9 KiB
Rust
use super::*;
|
|
|
|
/// A scene consists of a set of clips to play together.
|
|
///
|
|
/// ```
|
|
/// let scene: tek::Scene = Default::default();
|
|
/// let _ = scene.pulses();
|
|
/// let _ = scene.is_playing(&[]);
|
|
/// ```
|
|
#[derive(Debug, Default)] pub struct Scene {
|
|
/// Name of scene
|
|
pub name: Arc<str>,
|
|
/// Identifying color of scene
|
|
pub color: ItemTheme,
|
|
/// Clips in scene, one per track
|
|
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
|
}
|
|
|
|
impl Scene {
|
|
pub const DEFAULT_HEIGHT: usize = 2;
|
|
|
|
/// Get currently playing clip, if any
|
|
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
|
|
if let Some(Some(clip)) = self.clips.get(index) {
|
|
Some(clip)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Get pulse length of the longest clip in the scene
|
|
pub fn pulses (&self) -> usize {
|
|
self.clips.iter().fold(0, |a, p|{
|
|
a.max(p.as_ref().map(|q|q.try_read().unwrap().length).unwrap_or(0))
|
|
})
|
|
}
|
|
|
|
/// True if all clips in scene are currently playing on given tracks.
|
|
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
|
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
|
|
.all(|(track_index, clip)|match clip {
|
|
Some(c) => tracks
|
|
.get(track_index)
|
|
.map(|track|{
|
|
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
|
*clip.try_read().unwrap() == *c.try_read().unwrap()
|
|
} else {
|
|
false
|
|
}
|
|
})
|
|
.unwrap_or(false),
|
|
None => true
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
|
|
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
|
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
|
|
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut());
|
|
|
|
impl<T: AsRefOpt<Scene> + AsMutOpt<Scene> + Send + Sync> HasScene for T {}
|
|
|
|
pub trait HasScene: AsRefOpt<Scene> + AsMutOpt<Scene> {
|
|
fn scene (&self) -> Option<&Scene> {
|
|
self.as_ref_opt()
|
|
}
|
|
fn scene_mut (&mut self) -> Option<&mut Scene> {
|
|
self.as_mut_opt()
|
|
}
|
|
}
|
|
|
|
impl<T: HasScene
|
|
+ Namespace<usize>
|
|
+ Namespace<Arc<str>>
|
|
+ Namespace<ItemTheme>
|
|
> SceneController for T {}
|
|
|
|
#[tek_proc::commands(SceneCommand)]
|
|
pub trait SceneController: HasScene
|
|
+ Namespace<usize>
|
|
+ Namespace<Arc<str>>
|
|
+ Namespace<ItemTheme>
|
|
{
|
|
#[command(SetSize = "size")]
|
|
fn scene_set_size (&mut self, _size: usize) -> Perhaps<SceneCommand>
|
|
where Self: Namespace<usize>
|
|
{
|
|
todo!()
|
|
}
|
|
#[command(SetZoom = "zoom")]
|
|
fn scene_set_zoom (&mut self, _size: usize) -> Perhaps<SceneCommand>
|
|
where Self: Namespace<usize>
|
|
{
|
|
todo!()
|
|
}
|
|
#[command(SetName = "name")]
|
|
fn scene_set_name (&mut self, name: Arc<str>) -> Perhaps<SceneCommand>
|
|
where Self: Namespace<Arc<str>>
|
|
{
|
|
Ok(self.scene_mut().map(|scene|swap_value(
|
|
&mut scene.name,
|
|
&name,
|
|
|name|SceneCommand::SetName { name }
|
|
)).transpose()?.flatten())
|
|
}
|
|
#[command(SetColor = "color")]
|
|
fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps<SceneCommand>
|
|
where Self: Namespace<ItemTheme>
|
|
{
|
|
Ok(self.scene_mut().map(|scene|swap_value(
|
|
&mut scene.color,
|
|
&color,
|
|
|color|SceneCommand::SetColor { color }
|
|
)).transpose()?.flatten())
|
|
}
|
|
}
|
|
|
|
pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T);
|
|
|
|
def_sizes_iter!(ScenesSizes => Scene);
|
|
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
|
impl_as_ref!(Vec<Scene>: |self: App| self.project.as_ref());
|
|
impl_as_mut!(Vec<Scene>: |self: App| self.project.as_mut());
|
|
|
|
impl<T: AsRef<Vec<Scene>> + AsMut<Vec<Scene>>> HasScenes for T {}
|
|
|
|
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
|
|
fn scenes (&self) -> &Vec<Scene> {
|
|
self.as_ref()
|
|
}
|
|
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
|
|
self.as_mut()
|
|
}
|
|
/// Generate the default name for a new scene
|
|
fn scenes_default_name (&self) -> Arc<str> {
|
|
format!("s{:3>}", self.scenes().len() + 1).into()
|
|
}
|
|
fn scenes_longest_name (&self) -> usize {
|
|
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
|
}
|
|
/// Add multiple scenes
|
|
fn scenes_add_many (&mut self, n: usize)
|
|
-> Usually<()> where Self: HasTracks
|
|
{
|
|
let scene_color_1 = ItemColor::random();
|
|
let scene_color_2 = ItemColor::random();
|
|
for i in 0..n {
|
|
let _ = self.scenes_add_one(None, Some(
|
|
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
|
))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
/// Add a scene
|
|
fn scenes_add_one (&mut self, name: Option<Arc<str>>, color: Option<ItemTheme>)
|
|
-> Usually<(usize, &mut Scene)>
|
|
where
|
|
Self: HasTracks
|
|
{
|
|
let scene = Scene {
|
|
name: name.map_or_else(||self.scenes_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]))
|
|
}
|
|
}
|
|
|
|
impl<T: HasScenes
|
|
+ HasTracks
|
|
+ HasTrackScroll
|
|
+ HasJack<'static>
|
|
+ Namespace<usize>
|
|
+ Namespace<Option<ItemTheme>>
|
|
+ Namespace<Option<Arc<str>>>
|
|
> ScenesController for T {}
|
|
|
|
#[tek_proc::commands(ScenesCommand)]
|
|
pub trait ScenesController: HasScenes
|
|
+ HasTracks
|
|
+ HasTrackScroll
|
|
+ HasJack<'static>
|
|
+ Namespace<usize>
|
|
+ Namespace<Option<ItemTheme>>
|
|
+ Namespace<Option<Arc<str>>>
|
|
{
|
|
#[command(Add = "add")]
|
|
fn scenes_add (&mut self) -> Perhaps<ScenesCommand>
|
|
where Self: HasScenes + HasJack<'static>
|
|
{
|
|
let (_index, _) = self.scenes_add_one(None, None)?;
|
|
Ok(None)
|
|
}
|
|
// TODO
|
|
}
|
|
|
|
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync {
|
|
fn h_scenes (&self) -> u16;
|
|
fn w_side (&self) -> u16;
|
|
fn w_mid (&self) -> u16;
|
|
|
|
fn view_scenes_names (&self) -> impl Draw<Tui> {
|
|
let select = self.selection();
|
|
let editor = self.editor();
|
|
let editing = self.is_editing();
|
|
draw(move |to: &mut Tui|{
|
|
for (index, scene, ..) in self.scenes_with_sizes() {
|
|
view_scene_name(select, editor, index, scene, editing).draw(to)?;
|
|
}
|
|
Ok(Some(XYWH(1, 1, 1, 1)))
|
|
})
|
|
.exact_w(20)
|
|
}
|
|
|
|
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
|
|
let mut y = 0;
|
|
self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{
|
|
let height = if self.selection().scene() == Some(s) && self.editor().is_some() {
|
|
8
|
|
} else {
|
|
Scene::DEFAULT_HEIGHT
|
|
};
|
|
if y + height <= self.clips_size().h() as usize {
|
|
let data = (s, scene, y, y + height);
|
|
y += height;
|
|
Some(data)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ScenesView for App {
|
|
fn w_mid (&self) -> u16 {
|
|
(self.size.w() as u16).saturating_sub(self.w_side())
|
|
}
|
|
fn w_side (&self) -> u16 {
|
|
20
|
|
}
|
|
fn h_scenes (&self) -> u16 {
|
|
(self.size.h() as u16).saturating_sub(20)
|
|
}
|
|
}
|
|
|
|
impl ScenesView for Arrangement {
|
|
fn h_scenes (&self) -> u16 {
|
|
(self.size.h() as u16).saturating_sub(20)
|
|
}
|
|
fn w_side (&self) -> u16 {
|
|
(self.size.w() as u16 * 2 / 10).max(20)
|
|
}
|
|
fn w_mid (&self) -> u16 {
|
|
(self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40)
|
|
}
|
|
}
|
|
|
|
pub trait HasSceneScroll: HasScenes {
|
|
fn scene_scroll (&self) -> usize;
|
|
}
|
|
|
|
impl HasSceneScroll for Arrangement {
|
|
fn scene_scroll (&self) -> usize {
|
|
self.scene_scroll
|
|
}
|
|
}
|
|
|
|
impl HasSceneScroll for App {
|
|
fn scene_scroll (&self) -> usize {
|
|
self.project.scene_scroll()
|
|
}
|
|
}
|
|
|
|
pub fn view_scene_name <'a> (
|
|
select: &Selection,
|
|
editor: Option<&'a MidiEditor>,
|
|
index: usize,
|
|
scene: &Scene,
|
|
editing: bool
|
|
) -> impl Draw<Tui> {
|
|
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
|
|
7
|
|
} else {
|
|
Scene::DEFAULT_HEIGHT as u16
|
|
};
|
|
let a = east(format!("·s{index:02} "),
|
|
fg(g(255), bold(true, &scene.name))).align_w().full_w();
|
|
let b = when(select.scene() == Some(index) && editing, south(
|
|
editor.as_ref().map(|e|e.clip_status()),
|
|
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
|
|
let c = if select.scene() == Some(index) {
|
|
scene.color.light.term
|
|
} else {
|
|
scene.color.base.term
|
|
};
|
|
bg(c, south(a, b).align_nw()).exact_wh(20, h)
|
|
}
|