diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index b2c8c6db..21757721 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -66,6 +66,10 @@ pub struct Arrangement { pub focused: bool, /// Background color of arrangement pub color: Color, + /// Width of arrangement area at last render + pub width: AtomicUsize, + /// Height of arrangement area at last render + pub height: AtomicUsize, } /// Represents a track in the arrangement pub struct ArrangementTrack { @@ -234,6 +238,8 @@ impl Arrangement { tracks: vec![], focused: false, color: Color::Rgb(28, 35, 25), + width: 0.into(), + height: 0.into(), } } pub fn activate (&mut self) { @@ -534,18 +540,11 @@ impl ArrangementTrack { } } pub fn longest_name (tracks: &[Self]) -> usize { - tracks.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) - } - pub fn width_inc (&mut self) { - self.width += 1; - } - pub fn width_dec (&mut self) { - if self.width > 3 { - self.width -= 1; - } + tracks.iter().map(|s|s.name.read().unwrap().len()).fold(0, usize::max) } + pub const MIN_WIDTH: usize = 3; + pub fn width_inc (&mut self) { self.width += 1; } + pub fn width_dec (&mut self) { if self.width > Self::MIN_WIDTH { self.width -= 1; } } } /// Focus identification methods impl ArrangementFocus { @@ -580,24 +579,12 @@ impl ArrangementFocus { } }) } - pub fn is_mix (&self) -> bool { - match self { Self::Mix => true, _ => false } - } - pub fn is_track (&self) -> bool { - match self { Self::Track(_) => true, _ => false } - } - pub fn is_scene (&self) -> bool { - match self { Self::Scene(_) => true, _ => false } - } - pub fn is_clip (&self) -> bool { - match self { Self::Clip(_, _) => true, _ => false } - } + pub fn is_mix (&self) -> bool { match self { Self::Mix => true, _ => false } } + pub fn is_track (&self) -> bool { match self { Self::Track(_) => true, _ => false } } + pub fn is_scene (&self) -> bool { match self { Self::Scene(_) => true, _ => false } } + pub fn is_clip (&self) -> bool { match self { Self::Clip(_, _) => true, _ => false } } pub fn track (&self) -> Option { - match self { - Self::Clip(t, _) => Some(*t), - Self::Track(t) => Some(*t), - _ => None - } + match self { Self::Clip(t, _) => Some(*t), Self::Track(t) => Some(*t), _ => None } } pub fn track_next (&mut self, last_track: usize) { *self = match self { @@ -624,11 +611,7 @@ impl ArrangementFocus { } } pub fn scene (&self) -> Option { - match self { - Self::Clip(_, s) => Some(*s), - Self::Scene(s) => Some(*s), - _ => None - } + match self { Self::Clip(_, s) => Some(*s), Self::Scene(s) => Some(*s), _ => None } } pub fn scene_next (&mut self, last_scene: usize) { *self = match self { @@ -682,7 +665,9 @@ impl Scene { } /// Returns the pulse length of the longest phrase in the scene pub fn pulses (&self) -> usize { - self.clips.iter().fold(0, |a, p|a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))) + self.clips.iter().fold(0, |a, p|{ + a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) + }) } /// Returns true if all phrases in the scene are currently playing pub fn is_playing (&self, tracks: &[ArrangementTrack]) -> bool { @@ -714,15 +699,9 @@ impl Scene { } } pub fn longest_name (scenes: &[Self]) -> usize { - scenes.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) + scenes.iter().map(|s|s.name.read().unwrap().len()).fold(0, usize::max) } pub fn clip (&self, index: usize) -> Option<&Arc>> { - if let Some(Some(clip)) = self.clips.get(index) { - Some(clip) - } else { - None - } + match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } } } diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index 58572deb..31460b77 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -40,10 +40,10 @@ impl Content for ArrangerStatusBar { .bold(true); let commands = match self { Self::ArrangementMix => command(&[ - ["", "c", "olor"], - ["", ",.", "scale rows"], - ["", "+-", "resize view"], - ["", "Enter", " stop all"], + ["", "c", "olor"], + ["", ",.", "resize"], + ["", "+-", "zoom"], + ["", "Enter", " stop all"], ]), Self::ArrangementClip => command(&[ ["", "g", "et"], @@ -142,12 +142,21 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let border = Lozenge(Style::default().bg(border_bg).fg(border_fg)); let track_title_h = 3u16; let scene_title_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track - Layers::new(move |add|{ + let content = Layers::new(move |add|{ let rows: &[(usize, usize)] = rows.as_ref(); let cols: &[(usize, usize)] = cols.as_ref(); + let any_size = |_|Ok(Some([0,0])); + + // store render area + add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ + let [x, y, w, h] = to.area(); + self.0.width.store(w as usize, Ordering::Relaxed); + self.0.height.store(h as usize, Ordering::Relaxed); + Ok(()) + }))?; // column separators - add(&CustomWidget::new(|_|Ok(Some([0,0])), move|to: &mut TuiOutput|{ + add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ let area = to.area(); let style = Some(Style::default().fg(COLOR_SEPARATOR)); for x in cols.iter().map(|col|col.1) { @@ -158,7 +167,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { }))?; // row separators - add(&CustomWidget::new(|_|Ok(Some([0,0])), move|to: &mut TuiOutput|{ + add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ let area = to.area(); for y in rows.iter().map(|row|row.1) { let y = area.y() + (y / PPQ) as u16 + 1; @@ -175,7 +184,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { }))?; // track titles - let track_titles = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ + let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ let name = track.name.read().unwrap(); let max_w = w.saturating_sub(1).min(name.len()).max(2); let name = format!("▎{}", &name[0..max_w]); @@ -188,9 +197,16 @@ impl<'a> Content for VerticalArranger<'a, Tui> { .map(|t|format!("▎{t:>}")) .unwrap_or(String::from("▎")); let until_next = player.next_phrase.as_ref() - .map(|(t, _)|format!("▎{:>}", clock.format_beats(t.load(Ordering::Relaxed)))) + .map(|(t, _)|format!("▎-{:>}", clock.format_beats(t.load(Ordering::Relaxed)))) .unwrap_or(String::from("▎")); - col!(name, elapsed, until_next) + col!( + name, + "▎> 12345", + "▎< 12345", + "▎m s r o", + elapsed, + until_next + ) .min_xy(w as u16, track_title_h) .bg(track.color) .push_x(scene_title_w) @@ -218,25 +234,28 @@ impl<'a> Content for VerticalArranger<'a, Tui> { add(&Background(color)) }).fixed_xy(w, h); - add(&col!(track_titles, col!( - // scenes: - (scene, pulses) in scenes.iter().zip(rows.iter().map(|row|row.0)) => { - let height = 1.max((pulses / PPQ) as u16); - let playing = scene.is_playing(tracks); - Stack::right(move |add| { - // scene title: - add(&scene_name(scene, playing, height).bg(scene.color))?; - // clip per track: - for (track, w) in cols.iter().map(|col|col.0).enumerate() { - add(&scene_clip(scene, track, w as u16, height))?; - } - Ok(()) - }).fixed_y(height) - } - )))?; + add(&col!( + header, + col!( + // scenes: + (scene, pulses) in scenes.iter().zip(rows.iter().map(|row|row.0)) => { + let height = 1.max((pulses / PPQ) as u16); + let playing = scene.is_playing(tracks); + Stack::right(move |add| { + // scene title: + add(&scene_name(scene, playing, height).bg(scene.color))?; + // clip per track: + for (track, w) in cols.iter().map(|col|col.0).enumerate() { + add(&scene_clip(scene, track, w as u16, height))?; + } + Ok(()) + }).fixed_y(height) + } + ), + ))?; // cursor - add(&CustomWidget::new(|_|Ok(Some([0,0])), move|to: &mut TuiOutput|{ + add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ let area = to.area(); let focused = state.focused; let selected = state.selected; @@ -301,7 +320,18 @@ impl<'a> Content for VerticalArranger<'a, Tui> { }))?; Ok(()) - }).bg(bg).grow_y(1).border(border) + }).bg(bg).grow_y(1).border(border); + + let title_color = if self.0.focused { + Color::Rgb(150, 160, 90) + } else { + Color::Rgb(120, 130, 100) + }; + let w = self.0.width.load(Ordering::Relaxed); + let h = self.0.height.load(Ordering::Relaxed); + let lower_right = TuiStyle::fg(format!("{w}x{h}"), title_color) + .pull_x(1).align_se().fill_xy(); + lay!(content, lower_right) } } impl<'a> Content for HorizontalArranger<'a, Tui> { diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index 1db06d39..cc4f03c2 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -25,14 +25,14 @@ impl Content for PhrasePool { length.focus = Some(*focus); } } - let length = length.align_e().fill_x(); - let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x(); + let length = length.align_e().fill_x(); + let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x(); let mut row2 = format!(" {name}"); if let Some(PhrasePoolMode::Rename(phrase, _)) = mode { if *focused && i == *phrase { row2 = format!("{row2}▄"); } }; let row2 = TuiStyle::bold(row2, true); - let bg = if i == self.phrase { color } else { color }; + let bg = if i == self.phrase { color } else { color }; add(&col!(row1, row2).fill_x().bg(bg))?; if *focused && i == self.phrase { add(&CORNERS)?; } Ok(())