lumina/src/ui/widgets/verse_editor.rs
Chris Cochrun 3685670ff7
Some checks failed
/ clippy (push) Successful in 4m43s
/ test (push) Failing after 5m26s
bugfix: fixing scrolling issues with the verse editors
2026-02-18 11:54:07 -06:00

175 lines
5.8 KiB
Rust

use cosmic::{
Element, Task,
cosmic_theme::palette::WithAlpha,
iced::{Background, Border},
iced_widget::{column, row},
theme,
widget::{
button, combo_box, container, horizontal_space, icon,
text_editor,
},
};
use crate::core::songs::VerseName;
#[derive(Debug)]
pub struct VerseEditor {
pub verse_name: VerseName,
pub lyric: String,
content: text_editor::Content,
editing_verse_name: bool,
verse_name_combo: combo_box::State<String>,
}
#[derive(Debug, Clone)]
pub enum Message {
UpdateLyric(text_editor::Action),
UpdateVerseName(String),
EditVerseName,
DeleteVerse(VerseName),
None,
}
pub enum Action {
Task(Task<Message>),
UpdateVerse((VerseName, String)),
UpdateVerseName(String),
DeleteVerse(VerseName),
ScrollVerses(f32),
None,
}
impl VerseEditor {
#[must_use]
pub fn new(verse: VerseName, lyric: &str) -> Self {
Self {
verse_name: verse,
lyric: lyric.to_string(),
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::UpdateLyric(action) => match action {
text_editor::Action::Edit(ref _edit) => {
self.content.perform(action);
let lyrics = self.content.text();
self.lyric.clone_from(&lyrics);
let verse = self.verse_name;
Action::UpdateVerse((verse, lyrics))
}
text_editor::Action::Scroll { pixels } => {
if self.content.line_count() > 6 {
self.content.perform(action);
Action::None
} else {
Action::ScrollVerses(pixels)
}
}
_ => {
self.content.perform(action);
Action::None
}
},
Message::UpdateVerseName(verse_name) => {
Action::UpdateVerseName(verse_name)
}
Message::EditVerseName => {
self.editing_verse_name = !self.editing_verse_name;
Action::None
}
Message::DeleteVerse(verse) => Action::DeleteVerse(verse),
Message::None => Action::None,
}
}
pub fn view(&self) -> Element<Message> {
let cosmic::cosmic_theme::Spacing {
space_xxs: _,
space_s,
space_m,
..
} = theme::spacing();
let delete_button = button::text("Delete")
.trailing_icon(
icon::from_name("view-close").symbolic(true),
)
.class(theme::Button::Destructive)
.on_press(Message::DeleteVerse(self.verse_name));
let combo = combo_box(
&self.verse_name_combo,
"Verse 1",
Some(&self.verse_name.get_name()),
Message::UpdateVerseName,
);
let verse_title =
row![combo, horizontal_space(), delete_button];
let lyric: Element<Message> = if self.verse_name
== VerseName::Blank
{
horizontal_space().into()
} else {
text_editor(&self.content)
.on_action(Message::UpdateLyric)
.padding(space_m)
.class(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
| text_editor::Status::Focused => {
base_style.border = hovered_border;
base_style
}
text_editor::Status::Disabled => {
base_style
}
}
},
)))
.height(150)
.into()
};
container(column![verse_title, lyric].spacing(space_s))
.padding(space_s)
.class(theme::Container::Card)
.into()
}
}