use crate::*; pub struct PhrasePoolView { _engine: PhantomData, /// 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 PhrasePoolMode { /// Renaming a pattern Rename(usize, String), /// Editing the length of a pattern Length(usize, usize, PhraseLengthFocus), } impl PhrasePoolView { pub fn new (phrases: Vec>>) -> Self { Self { _engine: Default::default(), 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; } } } // TODO: Display phrases always in order of appearance impl Content for PhrasePoolView { type Engine = Tui; fn content (&self) -> impl Widget { let Self { focused, phrases, mode, .. } = self; let content = col!( (i, phrase) in 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!("({})", 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, Debug)] 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)?.map(Self::Edit)) } Self::Rename(command) => match command { Rename::Begin => { view.mode = Some(PhrasePoolMode::Rename( view.phrase, view.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.phrases[view.phrase].read().unwrap().length, PhraseLengthFocus::Bar )) }, _ => { return Ok(command.execute(view)?.map(Self::Length)) } }, } Ok(None) } } #[derive(Copy, Clone, Debug, PartialEq)] pub enum PhraseLengthCommand { Begin, Next, Prev, Inc, Dec, Set(usize), Cancel, } impl InputToCommand> for PhraseLengthCommand { fn input_to_command (view: &PhrasePoolView, from: &TuiInput) -> Option { if let Some(PhrasePoolMode::Length(_, length, _)) = view.mode { Some(match from.event() { key!(KeyCode::Up) => Self::Inc, key!(KeyCode::Down) => Self::Dec, key!(KeyCode::Right) => Self::Next, key!(KeyCode::Left) => Self::Prev, key!(KeyCode::Enter) => Self::Set(length), key!(KeyCode::Esc) => Self::Cancel, _ => return None }) } else { unreachable!() } } } impl Command> for PhraseLengthCommand { fn execute (self, view: &mut PhrasePoolView) -> Perhaps { use PhraseLengthFocus::*; use PhraseLengthCommand::*; if let Some(PhrasePoolMode::Length(phrase, ref mut length, ref mut focus)) = view.mode { match self { Self::Cancel => { view.mode = None; }, Self::Prev => { focus.prev() }, Self::Next => { focus.next() }, Self::Inc => match focus { Bar => { *length += 4 * PPQ }, Beat => { *length += PPQ }, Tick => { *length += 1 }, }, Self::Dec => match focus { Bar => { *length = length.saturating_sub(4 * PPQ) }, Beat => { *length = length.saturating_sub(PPQ) }, Tick => { *length = length.saturating_sub(1) }, }, Self::Set(length) => { let mut phrase = view.phrases[phrase].write().unwrap(); let old_length = phrase.length; phrase.length = length; view.mode = None; return Ok(Some(Self::Set(old_length))) }, _ => unreachable!() } Ok(None) } else if self == Begin { view.mode = Some(PhrasePoolMode::Length( view.phrase, view.phrases[view.phrase].read().unwrap().length, PhraseLengthFocus::Bar )); Ok(None) } else { unreachable!() } } } /// Displays and edits phrase length. pub struct PhraseLength { _engine: PhantomData, /// 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, } } } #[derive(Clone, Debug, PartialEq)] pub enum PhraseRenameCommand { Begin, Set(String), Confirm, Cancel, } impl InputToCommand> for PhraseRenameCommand { fn input_to_command (view: &PhrasePoolView, from: &TuiInput) -> Option { if let Some(PhrasePoolMode::Rename(_, ref old_name)) = view.mode { Some(match from.event() { key!(KeyCode::Char(c)) => { let mut new_name = old_name.clone(); new_name.push(*c); Self::Set(new_name) }, key!(KeyCode::Backspace) => { let mut new_name = old_name.clone(); new_name.pop(); Self::Set(new_name) }, key!(KeyCode::Enter) => Self::Confirm, key!(KeyCode::Esc) => Self::Cancel, _ => return None }) } else { unreachable!() } } } impl Command> for PhraseRenameCommand { fn execute (self, view: &mut PhrasePoolView) -> Perhaps { use PhraseRenameCommand::*; if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = view.mode { match self { Set(s) => { view.phrases[phrase].write().unwrap().name = s.into(); return Ok(Some(Self::Set(old_name.clone()))) }, Confirm => { let old_name = old_name.clone(); view.mode = None; return Ok(Some(Self::Set(old_name))) }, Cancel => { let mut phrase = view.phrases[phrase].write().unwrap(); phrase.name = old_name.clone(); }, _ => unreachable!() }; Ok(None) } else if self == Begin { view.mode = Some(PhrasePoolMode::Rename( view.phrase, view.phrases[view.phrase].read().unwrap().name.clone() )); Ok(None) } else { unreachable!() } } }