use crate::*; pub struct PhrasePoolView { _engine: PhantomData, /// Collection of phrases pub model: PhrasePoolModel, /// 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 (model: PhrasePoolModel) -> Self { Self { _engine: Default::default(), scroll: 0, phrase: 0, mode: None, focused: false, entered: false, model, } } pub fn len (&self) -> usize { self.model.phrases.len() } pub fn phrase (&self) -> &Arc> { &self.model.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.model.phrases.len() { if *self.model.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.model.phrases.remove(self.phrase); self.phrase = self.phrase.min(self.model.phrases.len().saturating_sub(1)); } } pub fn append_new (&mut self, name: Option<&str>, color: Option) { self.model.phrases.push(Self::new_phrase(name, color)); self.phrase = self.model.phrases.len() - 1; } pub fn insert_new (&mut self, name: Option<&str>, color: Option) { self.model.phrases.insert(self.phrase + 1, Self::new_phrase(name, color)); self.phrase += 1; } pub fn insert_dup (&mut self) { let mut phrase = self.model.phrases[self.phrase].read().unwrap().duplicate(); phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25); self.model.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); self.phrase += 1; } pub fn move_up (&mut self) { if self.phrase > 1 { self.model.phrases.swap(self.phrase - 1, self.phrase); self.phrase -= 1; } } pub fn move_down (&mut self) { if self.phrase < self.model.phrases.len().saturating_sub(1) { self.model.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, model, mode, .. } = self; let content = col!( (i, phrase) in model.phrases.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!("({})", model.phrases.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(), ) } } #[derive(Clone, PartialEq)] pub enum PhrasePoolViewCommand { Select(usize), Edit(PhrasePoolCommand), Rename(PhraseRenameCommand), Length(PhraseLengthCommand), } impl Handle for PhrasePoolView { fn handle (&mut self, from: &TuiInput) -> Perhaps { PhrasePoolViewCommand::execute_with_state(self, from) } } impl InputToCommand> for PhrasePoolViewCommand { fn input_to_command (state: &PhrasePoolView, input: &TuiInput) -> Option { use PhrasePoolViewCommand as Cmd; use PhrasePoolCommand as Edit; use PhraseRenameCommand as Rename; use PhraseLengthCommand as Length; match input.event() { key!(KeyCode::Up) => Some(Cmd::Select(0)), key!(KeyCode::Down) => Some(Cmd::Select(0)), key!(KeyCode::Char(',')) => Some(Cmd::Edit(Edit::Swap(0, 0))), key!(KeyCode::Char('.')) => Some(Cmd::Edit(Edit::Swap(0, 0))), key!(KeyCode::Delete) => Some(Cmd::Edit(Edit::Delete(0))), key!(KeyCode::Char('a')) => Some(Cmd::Edit(Edit::Add(0))), key!(KeyCode::Char('i')) => Some(Cmd::Edit(Edit::Add(0))), key!(KeyCode::Char('d')) => Some(Cmd::Edit(Edit::Duplicate(0))), key!(KeyCode::Char('c')) => Some(Cmd::Edit(Edit::RandomColor(0))), key!(KeyCode::Char('n')) => Some(Cmd::Rename(Rename::Begin)), key!(KeyCode::Char('t')) => Some(Cmd::Length(Length::Begin)), _ => match state.mode { Some(PhrasePoolMode::Rename(..)) => { Rename::input_to_command(state, input).map(Cmd::Rename) }, Some(PhrasePoolMode::Length(..)) => { Length::input_to_command(state, input).map(Cmd::Length) }, _ => None } } } } impl Command> for PhrasePoolViewCommand { fn execute (self, view: &mut PhrasePoolView) -> Perhaps { use PhraseRenameCommand as Rename; use PhraseLengthCommand as Length; match self { Self::Select(phrase) => { view.phrase = phrase }, Self::Edit(command) => { return Ok(command.execute(&mut view.model)?.map(Self::Edit)) } Self::Rename(command) => match command { Rename::Begin => { view.mode = Some(PhrasePoolMode::Rename( view.phrase, view.model.phrases[view.phrase].read().unwrap().name.clone() )) }, _ => { return Ok(command.execute(view)?.map(Self::Rename)) } }, Self::Length(command) => match command { Length::Begin => { view.mode = Some(PhrasePoolMode::Length( view.phrase, view.model.phrases[view.phrase].read().unwrap().length, PhraseLengthFocus::Bar )) }, _ => { return Ok(command.execute(view)?.map(Self::Length)) } }, } Ok(None) } }