use crate::{message::Message, state::State};
use iced::{
Alignment, Element, Length,
widget::{button, column, container, row, text, text_input},
};
pub(crate) fn clean_clip_name(name: &str) -> String {
let mut cleaned = name.to_string();
if let Some(stripped) = cleaned.strip_prefix("audio/") {
cleaned = stripped.to_string();
}
if let Some(stripped) = cleaned.strip_prefix("midi/") {
cleaned = stripped.to_string();
}
if let Some(stripped) = cleaned.strip_suffix(".wav") {
cleaned = stripped.to_string();
}
if let Some(stripped) = cleaned.strip_suffix(".mid") {
cleaned = stripped.to_string();
}
cleaned
}
#[derive(Debug)]
pub struct ClipRenameView {
state: State,
}
impl ClipRenameView {
pub fn new(state: State) -> Self {
Self { state }
}
pub fn update(&mut self, message: &Message) {
if let Message::ClipRenameInput(input) = message
&& let Some(dialog) = &mut self.state.blocking_write().clip_rename_dialog
{
dialog.new_name = input.clone();
}
}
pub fn view(&self) -> Element<'_, Message> {
let state = self.state.blocking_read();
let Some(dialog) = &state.clip_rename_dialog else {
return container("").into();
};
let current_name_raw = state
.tracks
.iter()
.find(|t| t.name == dialog.track_idx)
.and_then(|t| match dialog.kind {
maolan_engine::kind::Kind::Audio => {
t.audio.clips.get(dialog.clip_idx).map(|c| c.name.as_str())
}
maolan_engine::kind::Kind::MIDI => {
t.midi.clips.get(dialog.clip_idx).map(|c| c.name.as_str())
}
})
.unwrap_or("");
let current_name = clean_clip_name(current_name_raw);
let new_name = &dialog.new_name;
let can_confirm = !new_name.trim().is_empty() && new_name != ¤t_name;
let rename_button = if can_confirm {
button("Rename").on_press(Message::ClipRenameConfirm)
} else {
button("Rename")
};
container(
column![
text(format!("Rename clip: {}", current_name)).size(16),
row![
text("New name:"),
text_input("Enter new name", new_name)
.on_input(Message::ClipRenameInput)
.width(Length::Fixed(300.0)),
]
.spacing(10)
.align_y(Alignment::Center),
row![
rename_button,
button("Cancel")
.on_press(Message::ClipRenameCancel)
.style(button::secondary)
]
.spacing(10),
]
.align_x(Alignment::End)
.spacing(15),
)
.style(|_theme| crate::style::app_background())
.padding(20)
.width(Length::Fill)
.height(Length::Fill)
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use maolan_engine::kind::Kind;
use std::sync::Arc;
use tokio::sync::RwLock;
#[test]
fn update_sets_clip_rename_input_when_dialog_is_open() {
let state = Arc::new(RwLock::new(crate::state::StateData::default()));
state.blocking_write().clip_rename_dialog = Some(crate::state::ClipRenameDialog {
track_idx: "Track".to_string(),
clip_idx: 0,
kind: Kind::Audio,
new_name: "Old".to_string(),
});
let mut view = ClipRenameView::new(state.clone());
view.update(&Message::ClipRenameInput("New".to_string()));
assert_eq!(
state
.blocking_read()
.clip_rename_dialog
.as_ref()
.map(|dialog| dialog.new_name.as_str()),
Some("New")
);
}
#[test]
fn update_ignores_clip_rename_input_when_dialog_is_closed() {
let state = Arc::new(RwLock::new(crate::state::StateData::default()));
let mut view = ClipRenameView::new(state.clone());
view.update(&Message::ClipRenameInput("New".to_string()));
assert!(state.blocking_read().clip_rename_dialog.is_none());
}
#[test]
fn new_creates_view() {
let state = crate::state::State::default();
let view = ClipRenameView::new(state);
let _ = &view;
}
#[test]
fn view_returns_empty_when_no_dialog() {
let state = Arc::new(RwLock::new(crate::state::StateData::default()));
let view = ClipRenameView::new(state);
let element = view.view();
let _ = &element;
}
#[test]
fn clean_clip_name_strips_audio_prefix_and_wav_suffix() {
assert_eq!(clean_clip_name("audio/my_clip.wav"), "my_clip");
}
#[test]
fn clean_clip_name_strips_midi_prefix() {
assert_eq!(clean_clip_name("midi/my_clip"), "my_clip");
}
#[test]
fn clean_clip_name_strips_midi_prefix_and_mid_suffix() {
assert_eq!(clean_clip_name("midi/my_clip.mid"), "my_clip");
}
#[test]
fn clean_clip_name_returns_plain_name() {
assert_eq!(clean_clip_name("my_clip"), "my_clip");
}
}