use crate::*; pub struct PhrasesTui { /// Collection of phrases pub phrases: Vec>>, /// Selected phrase pub phrase: usize, /// Scroll offset pub scroll: usize, /// Mode switch pub mode: Option, /// Whether this widget is focused pub focused: bool, /// Whether this widget is entered pub entered: bool, } /// Modes for phrase pool pub enum PhrasesMode { /// Renaming a pattern Rename(usize, String), /// Editing the length of a pattern Length(usize, usize, PhraseLengthFocus), } impl PhrasesTui { pub fn new (phrases: Vec>>) -> Self { Self { scroll: 0, phrase: 0, mode: None, focused: false, entered: false, phrases, } } pub fn len (&self) -> usize { self.phrases.len() } pub fn phrase (&self) -> &Arc> { &self.phrases[self.phrase] } pub fn index_before (&self, index: usize) -> usize { index.overflowing_sub(1).0.min(self.len() - 1) } pub fn index_after (&self, index: usize) -> usize { (index + 1) % self.len() } pub fn index_of (&self, phrase: &Phrase) -> Option { for i in 0..self.phrases.len() { if *self.phrases[i].read().unwrap() == *phrase { return Some(i) } } return None } fn new_phrase (name: Option<&str>, color: Option) -> Arc> { Arc::new(RwLock::new(Phrase::new( String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color ))) } pub fn delete_selected (&mut self) { if self.phrase > 0 { self.phrases.remove(self.phrase); self.phrase = self.phrase.min(self.phrases.len().saturating_sub(1)); } } pub fn append_new (&mut self, name: Option<&str>, color: Option) { self.phrases.push(Self::new_phrase(name, color)); self.phrase = self.phrases.len() - 1; } pub fn insert_new (&mut self, name: Option<&str>, color: Option) { self.phrases.insert(self.phrase + 1, Self::new_phrase(name, color)); self.phrase += 1; } pub fn insert_dup (&mut self) { let mut phrase = self.phrases[self.phrase].read().unwrap().duplicate(); phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25); self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); self.phrase += 1; } pub fn move_up (&mut self) { if self.phrase > 1 { self.phrases.swap(self.phrase - 1, self.phrase); self.phrase -= 1; } } pub fn move_down (&mut self) { if self.phrase < self.phrases.len().saturating_sub(1) { self.phrases.swap(self.phrase + 1, self.phrase); self.phrase += 1; } } } /// Displays and edits phrase length. pub struct PhraseLength { /// Pulses per beat (quaver) pub ppq: usize, /// Beats per bar pub bpb: usize, /// Length of phrase in pulses pub pulses: usize, /// Selected subdivision pub focus: Option, } impl PhraseLength { pub fn new (pulses: usize, focus: Option) -> Self { Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus } } pub fn bars (&self) -> usize { self.pulses / (self.bpb * self.ppq) } pub fn beats (&self) -> usize { (self.pulses % (self.bpb * self.ppq)) / self.ppq } pub fn ticks (&self) -> usize { self.pulses % self.ppq } pub fn bars_string (&self) -> String { format!("{}", self.bars()) } pub fn beats_string (&self) -> String { format!("{}", self.beats()) } pub fn ticks_string (&self) -> String { format!("{:>02}", self.ticks()) } } impl Content for PhraseLength { type Engine = Tui; fn content (&self) -> impl Widget { Layers::new(move|add|{ match self.focus { None => add(&row!( " ", self.bars_string(), ".", self.beats_string(), ".", self.ticks_string(), " " )), Some(PhraseLengthFocus::Bar) => add(&row!( "[", self.bars_string(), "]", self.beats_string(), ".", self.ticks_string(), " " )), Some(PhraseLengthFocus::Beat) => add(&row!( " ", self.bars_string(), "[", self.beats_string(), "]", self.ticks_string(), " " )), Some(PhraseLengthFocus::Tick) => add(&row!( " ", self.bars_string(), ".", self.beats_string(), "[", self.ticks_string(), "]" )), } }) } } /// Focused field of `PhraseLength` #[derive(Copy, Clone)] pub enum PhraseLengthFocus { /// Editing the number of bars Bar, /// Editing the number of beats Beat, /// Editing the number of ticks Tick, } impl PhraseLengthFocus { pub fn next (&mut self) { *self = match self { Self::Bar => Self::Beat, Self::Beat => Self::Tick, Self::Tick => Self::Bar, } } pub fn prev (&mut self) { *self = match self { Self::Bar => Self::Tick, Self::Beat => Self::Bar, Self::Tick => Self::Beat, } } }