use crate::*; pub struct PhrasePoolView { _engine: PhantomData, pub state: PhrasePool, /// 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 PhrasePoolMode { /// Renaming a pattern Rename(usize, String), /// Editing the length of a pattern Length(usize, usize, PhraseLengthFocus), } impl PhrasePoolView { pub fn new (state: PhrasePool) -> Self { Self { _engine: Default::default(), scroll: 0, phrase: 0, mode: None, focused: false, entered: false, state, } } pub fn len (&self) -> usize { self.state.phrases.len() } pub fn phrase (&self) -> &Arc> { &self.state.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.state.phrases.len() { if *self.state.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.state.phrases.remove(self.phrase); self.phrase = self.phrase.min(self.state.phrases.len().saturating_sub(1)); } } pub fn append_new (&mut self, name: Option<&str>, color: Option) { self.state.phrases.push(Self::new_phrase(name, color)); self.phrase = self.state.phrases.len() - 1; } pub fn insert_new (&mut self, name: Option<&str>, color: Option) { self.state.phrases.insert(self.phrase + 1, Self::new_phrase(name, color)); self.phrase += 1; } pub fn insert_dup (&mut self) { let mut phrase = self.state.phrases[self.phrase].read().unwrap().duplicate(); phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25); self.state.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); self.phrase += 1; } pub fn begin_rename (&mut self) { self.mode = Some(PhrasePoolMode::Rename( self.phrase, self.state.phrases[self.phrase].read().unwrap().name.clone() )); } pub fn begin_length (&mut self) { self.mode = Some(PhrasePoolMode::Length( self.phrase, self.state.phrases[self.phrase].read().unwrap().length, PhraseLengthFocus::Bar )); } pub fn move_up (&mut self) { if self.phrase > 1 { self.state.phrases.swap(self.phrase - 1, self.phrase); self.phrase -= 1; } } pub fn move_down (&mut self) { if self.phrase < self.state.phrases.len().saturating_sub(1) { self.state.phrases.swap(self.phrase + 1, self.phrase); self.phrase += 1; } } } // TODO: Display phrases always in order of appearance impl Content for PhrasePoolView { type Engine = Tui; fn content (&self) -> impl Widget { let Self { focused, state, mode, .. } = self; let content = col!( (i, phrase) in state.iter().enumerate() => Layers::new(|add|{ let Phrase { ref name, color, length, .. } = *phrase.read().unwrap(); let mut length = PhraseLength::new(length, None); if let Some(PhrasePoolMode::Length(phrase, new_length, focus)) = mode { if *focused && i == *phrase { length.pulses = *new_length; length.focus = Some(*focus); } } 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); add(&col!(row1, row2).fill_x().bg(color.base.rgb))?; Ok(if *focused && i == self.phrase { add(&CORNERS)?; }) }) ); let border_color = if *focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)}; let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color)); let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border); let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; let upper_left = format!("[{}] Phrases", if self.entered {"■"} else {" "}); let upper_right = format!("({})", state.len()); lay!( content, TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(), TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(), ) } }