diff --git a/src/core/songs.rs b/src/core/songs.rs index e459cf9..d04d242 100644 --- a/src/core/songs.rs +++ b/src/core/songs.rs @@ -76,6 +76,70 @@ pub enum VerseName { } impl VerseName { + pub fn from_string(name: String) -> Self { + match name.as_str() { + "Verse" => Self::Verse { number: 1 }, + "Pre-Chorus" => Self::PreChorus { number: 1 }, + "Chorus" => Self::Chorus { number: 1 }, + "Post-Chorus" => Self::PostChorus { number: 1 }, + "Bridge" => Self::Bridge { number: 1 }, + "Intro" => Self::Intro { number: 1 }, + "Outro" => Self::Outro { number: 1 }, + "Instrumental" => Self::Instrumental { number: 1 }, + "Other" => Self::Other { number: 1 }, + "Blank" => Self::Blank, + _ => Self::Blank, + } + } + + pub fn all_names() -> Vec { + vec![ + "Verse".into(), + "Pre-Chorus".into(), + "Chorus".into(), + "Post-Chorus".into(), + "Bridge".into(), + "Intro".into(), + "Outro".into(), + "Instrumental".into(), + "Other".into(), + "Blank".into(), + ] + } + + pub fn next(&self) -> Self { + match self { + Self::Verse { number } => { + Self::Verse { number: number + 1 } + } + Self::PreChorus { number } => { + Self::PreChorus { number: number + 1 } + } + Self::Chorus { number } => { + Self::Chorus { number: number + 1 } + } + Self::PostChorus { number } => { + Self::PostChorus { number: number + 1 } + } + Self::Bridge { number } => { + Self::Bridge { number: number + 1 } + } + Self::Intro { number } => { + Self::Intro { number: number + 1 } + } + Self::Outro { number } => { + Self::Outro { number: number + 1 } + } + Self::Instrumental { number } => { + Self::Instrumental { number: number + 1 } + } + Self::Other { number } => { + Self::Other { number: number + 1 } + } + Self::Blank => Self::Blank, + } + } + #[must_use] pub fn get_name(&self) -> String { match self { @@ -1037,11 +1101,10 @@ impl Song { VerseName::Bridge { number: 1 } } else { if let Some(last_verse) = verses.iter().last() - && let VerseName::Verse { number } = last_verse { - return VerseName::Verse { - number: number + 1, - }; - } + && let VerseName::Verse { number } = last_verse + { + return VerseName::Verse { number: number + 1 }; + } VerseName::Verse { number: 1 } } } else { @@ -1062,6 +1125,40 @@ impl Song { self.verses = Some(vec![verse]); }; } + + pub(crate) fn verse_name_from_str( + &self, + verse_name: String, // chorus 2 + old_verse_name: VerseName, // v4 + ) -> VerseName { + if old_verse_name.get_name() == verse_name { + return old_verse_name; + }; + if let Some(verses) = + self.verse_map.clone().map(|verse_map| { + verse_map.into_keys().collect::>() + }) + { + verses + .into_iter() + .filter(|verse| { + verse + .get_name() + .split_whitespace() + .next() + .unwrap() + == &verse_name + }) + .sorted() + .last() + .map_or_else( + || VerseName::from_string(verse_name), + |verse_name| verse_name.next(), + ) + } else { + VerseName::from_string(verse_name) + } + } } #[cfg(test)] diff --git a/src/ui/song_editor.rs b/src/ui/song_editor.rs index 5a0b540..d3a6168 100644 --- a/src/ui/song_editor.rs +++ b/src/ui/song_editor.rs @@ -441,6 +441,25 @@ impl SongEditor { }, )); } + verse_editor::Action::UpdateVerseName( + verse_name, + ) => { + if let Some(mut song) = self.song.clone() + { + let verse_name = song + .verse_name_from_str( + verse_name, + verse.verse_name.clone(), + ); + let lyric = verse.lyric.clone(); + + song.update_verse( + index, verse_name, lyric, + ); + + return self.update_song(song); + } + } verse_editor::Action::UpdateVerse(verse) => { if let Some(mut song) = self.song.clone() { diff --git a/src/ui/widgets/verse_editor.rs b/src/ui/widgets/verse_editor.rs index c40308d..22b8134 100644 --- a/src/ui/widgets/verse_editor.rs +++ b/src/ui/widgets/verse_editor.rs @@ -1,28 +1,38 @@ use cosmic::{ Element, Task, - iced_widget::column, + cosmic_theme::palette::WithAlpha, + iced::{Background, Border, Color}, + iced_widget::{column, row}, theme, - widget::{container, text, text_editor}, + widget::{ + button, combo_box, container, horizontal_space, icon, text, + text_editor, text_input, + }, }; use crate::core::songs::VerseName; #[derive(Debug)] pub struct VerseEditor { - verse_name: VerseName, - lyric: String, + pub verse_name: VerseName, + pub lyric: String, content: text_editor::Content, + editing_verse_name: bool, + verse_name_combo: combo_box::State, } #[derive(Debug, Clone)] pub enum Message { - ChangeText(text_editor::Action), + UpdateLyric(text_editor::Action), + UpdateVerseName(String), + EditVerseName, None, } pub enum Action { Task(Task), UpdateVerse((VerseName, String)), + UpdateVerseName(String), None, } @@ -33,31 +43,98 @@ impl VerseEditor { verse_name: verse, lyric: lyric.clone(), content: text_editor::Content::with_text(&lyric), + editing_verse_name: false, + verse_name_combo: combo_box::State::new( + VerseName::all_names(), + ), } } pub fn update(&mut self, message: Message) -> Action { match message { - Message::ChangeText(action) => { + Message::UpdateLyric(action) => { self.content.perform(action); let lyrics = self.content.text(); self.lyric = lyrics.clone(); let verse = self.verse_name; Action::UpdateVerse((verse, lyrics)) } + Message::UpdateVerseName(verse_name) => { + Action::UpdateVerseName(verse_name) + } + Message::EditVerseName => { + self.editing_verse_name = !self.editing_verse_name; + Action::None + } Message::None => Action::None, } } pub fn view(&self) -> Element { let cosmic::cosmic_theme::Spacing { - space_s, space_m, .. + space_xxs, + space_s, + space_m, + .. } = theme::spacing(); + let delete_button = button::text("Delete") + .trailing_icon(icon::from_name("view-close")) + .class(theme::Button::Destructive) + .on_press(Message::None); + let combo = combo_box( + &self.verse_name_combo, + "Verse 1", + Some(&self.verse_name.get_name()), + Message::UpdateVerseName, + ); + let verse_title = - text::heading(self.verse_name.get_name()).size(space_m); + row![combo, horizontal_space(), delete_button]; + let lyric = text_editor(&self.content) - .on_action(Message::ChangeText) - .padding(space_s) + .on_action(Message::UpdateLyric) + .padding(space_m) + .class(cosmic::theme::iced::TextEditor::Custom(Box::new( + move |t, s| { + let neutral = t.cosmic().palette.neutral_9; + let mut base_style = text_editor::Style { + background: Background::Color( + t.cosmic() + .background + .small_widget + .with_alpha(0.25) + .into(), + ), + border: Border::default() + .rounded(space_s) + .width(2) + .color(t.cosmic().bg_component_divider()), + icon: t + .cosmic() + .primary_component_color() + .into(), + placeholder: neutral.with_alpha(0.7).into(), + value: neutral.into(), + selection: t.cosmic().accent.base.into(), + }; + let hovered_border = Border::default() + .rounded(space_s) + .width(3) + .color(t.cosmic().accent.hover); + match s { + text_editor::Status::Active => base_style, + text_editor::Status::Hovered => { + base_style.border = hovered_border; + base_style + } + text_editor::Status::Focused => { + base_style.border = hovered_border; + base_style + } + text_editor::Status::Disabled => base_style, + } + }, + ))) // .style(|theme, status| { // let mut style = // text_editor::default(theme, status);