From a8027ec7b9c8a231ffac93670a42ac03e062af5d Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Tue, 13 Jan 2026 15:23:21 -0600 Subject: [PATCH] adding the beginning of a better verse editor for clarity --- src/core/songs.rs | 24 ++++++++++ src/ui/song_editor.rs | 87 ++++++++++++++++++++++++---------- src/ui/widgets/mod.rs | 1 + src/ui/widgets/verse_editor.rs | 81 +++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 24 deletions(-) create mode 100644 src/ui/widgets/verse_editor.rs diff --git a/src/core/songs.rs b/src/core/songs.rs index bebf3fc..88d6e14 100644 --- a/src/core/songs.rs +++ b/src/core/songs.rs @@ -115,6 +115,22 @@ impl Verse { Verse::Other { lyric, .. } => lyric.clone(), } } + + pub fn set_lyrics(&mut self, lyrics: String) { + match self { + Verse::Verse { number: _, lyric } => *lyric = lyrics, + Verse::PreChorus { number: _, lyric } => *lyric = lyrics, + Verse::Chorus { number: _, lyric } => *lyric = lyrics, + Verse::PostChorus { number: _, lyric } => *lyric = lyrics, + Verse::Bridge { number: _, lyric } => *lyric = lyrics, + Verse::Intro { number: _, lyric } => *lyric = lyrics, + Verse::Outro { number: _, lyric } => *lyric = lyrics, + Verse::Instrumental { number: _, lyric } => { + *lyric = lyrics + } + Verse::Other { number: _, lyric } => *lyric = lyrics, + } + } } impl Default for Verse { @@ -784,6 +800,14 @@ impl Song { Err(miette!("There are no lyrics")) } } + + pub fn update_verse(&mut self, index: usize, verse: Verse) { + if let Some(verses) = self.verses.as_mut() { + if let Some(old_verse) = verses.get_mut(index) { + *old_verse = verse; + } + } + } } #[cfg(test)] diff --git a/src/ui/song_editor.rs b/src/ui/song_editor.rs index 25a1630..a87a3b1 100644 --- a/src/ui/song_editor.rs +++ b/src/ui/song_editor.rs @@ -26,7 +26,10 @@ use crate::{ songs::{Song, Verse}, }, ui::{ - presenter::slide_view, slide_editor::SlideEditor, text_svg, + presenter::slide_view, + slide_editor::SlideEditor, + text_svg, + widgets::verse_editor::{self, VerseEditor}, }, }; @@ -52,6 +55,7 @@ pub struct SongEditor { stroke_sizes: combo_box::State, stroke_size: i32, stroke_open: bool, + verses: Option>, } pub enum Action { @@ -79,6 +83,7 @@ pub enum Message { UpdateStrokeSize(i32), OpenStroke, CloseStroke, + VerseEditorMessage((usize, verse_editor::Message)), } impl SongEditor { @@ -144,6 +149,7 @@ impl SongEditor { stroke_sizes: combo_box::State::new(stroke_sizes), stroke_size: 0, stroke_open: false, + verses: None, } } pub fn update(&mut self, message: Message) -> Action { @@ -202,6 +208,11 @@ impl SongEditor { }, |slides| Message::UpdateSlides(slides), ); + self.verses = song.verses.map(|vec| { + vec.into_iter() + .map(|verse| VerseEditor::new(verse)) + .collect() + }); return Action::Task(task); } Message::ChangeFont(font) => { @@ -306,7 +317,33 @@ impl SongEditor { Message::CloseStroke => { self.stroke_open = false; } - _ => (), + Message::VerseEditorMessage((index, message)) => { + if let Some(verses) = self.verses.as_mut() { + if let Some(verse) = verses.get_mut(index) { + match verse.update(message) { + verse_editor::Action::Task(task) => { + return Action::Task(task.map( + move |m| { + Message::VerseEditorMessage(( + index, m, + )) + }, + )); + } + verse_editor::Action::UpdateVerse( + verse, + ) => { + if let Some(song) = self.song.as_mut() + { + song.update_verse(index, verse); + } + } + verse_editor::Action::None => (), + } + } + } + } + Message::None => (), } Action::None } @@ -395,6 +432,17 @@ impl SongEditor { } fn left_column(&self) -> Element { + let cosmic::cosmic_theme::Spacing { + space_xxs, + space_xs, + space_s, + space_m, + space_l, + space_xl, + space_xxl, + .. + } = theme::spacing(); + let title_input = text_input("song", &self.title) .on_input(Message::ChangeTitle) .label("Song Title"); @@ -420,22 +468,19 @@ order", ] .spacing(5); - let verse_list = self.song.clone().map(|song| { - if let Some(verses) = song.verses.map(|verses| { - verses - .iter() - .map(|verse| text(verse.get_name())) - .collect() - }) { - verses - } else { - vec![] - } - }); - let verse_list = if let Some(verse_list) = verse_list { - Element::from(row(verse_list - .into_iter() - .map(|v| Element::from(v)))) + let verse_list = if let Some(verse_list) = &self.verses { + Element::from( + column(verse_list.into_iter().enumerate().map( + |(index, v)| { + v.view().map(move |message| { + Message::VerseEditorMessage(( + index, message, + )) + }) + }, + )) + .spacing(space_l), + ) } else { Element::from(horizontal_space()) }; @@ -669,12 +714,6 @@ order", } } -fn verses_editor<'a>(verse: Verse) -> Element<'a, Message> { - let verse_title = text(verse.get_name()); - let lyric = text(verse.get_lyric()); - todo!() -} - impl Default for SongEditor { fn default() -> Self { let mut fontdb = fontdb::Database::new(); diff --git a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs index f13c9e9..eabd3fd 100644 --- a/src/ui/widgets/mod.rs +++ b/src/ui/widgets/mod.rs @@ -1,2 +1,3 @@ // pub mod slide_text; pub mod draggable; +pub mod verse_editor; diff --git a/src/ui/widgets/verse_editor.rs b/src/ui/widgets/verse_editor.rs new file mode 100644 index 0000000..1a72df4 --- /dev/null +++ b/src/ui/widgets/verse_editor.rs @@ -0,0 +1,81 @@ +use cosmic::{ + Apply, Element, Task, + iced::{Length, alignment::Vertical}, + iced_widget::{column, row}, + theme, + widget::{container, icon, text, text_editor}, +}; + +use crate::core::songs::Verse; + +#[derive(Debug)] +pub struct VerseEditor { + verse: Verse, + content: text_editor::Content, +} + +#[derive(Debug, Clone)] +pub enum Message { + ChangeText(text_editor::Action), + None, +} + +pub enum Action { + Task(Task), + UpdateVerse(Verse), + None, +} + +impl VerseEditor { + pub fn new(verse: Verse) -> Self { + let text = verse.get_lyric(); + Self { + verse, + content: text_editor::Content::with_text(&text), + } + } + pub fn update(&mut self, message: Message) -> Action { + match message { + Message::ChangeText(action) => { + self.content.perform(action); + let lyrics = self.content.text(); + self.verse.set_lyrics(lyrics); + let verse = self.verse.clone(); + Action::UpdateVerse(verse) + } + Message::None => Action::None, + } + } + + pub fn view(&self) -> Element { + let cosmic::cosmic_theme::Spacing { + space_xxs, + space_xs, + space_s, + space_m, + space_l, + space_xl, + space_xxl, + .. + } = theme::spacing(); + + let verse_title = + text::heading(self.verse.get_name()).size(space_m); + let editor = text_editor(&self.content) + .on_action(Message::ChangeText) + .padding(space_s) + .height(Length::Fill); + + let drag_handle = icon::from_name("object-rows") + .prefer_svg(true) + .size(space_xxl); + + let row = row![drag_handle, editor] + .spacing(space_s) + .align_y(Vertical::Center); + container(column![verse_title, row].spacing(space_s)) + .padding(space_s) + .class(theme::Container::Card) + .into() + } +}