diff --git a/.forgejo/workflows/test.yaml b/.forgejo/workflows/test.yaml index 4fae544..f5190f3 100644 --- a/.forgejo/workflows/test.yaml +++ b/.forgejo/workflows/test.yaml @@ -7,13 +7,15 @@ jobs: - name: checkout uses: actions/checkout@v4 - run: nix --extra-experimental-features nix-command --extra-experimental-features flakes develop --command just ci-test - # clippy: - # runs-on: rust-latest + # lint: + # runs-on: nixos-latest # steps: # - name: checkout # uses: actions/checkout@v4 - # - run: | - # apt update - # apt upgrade - # apt install just mupdf gstreamer - # - run: just test + # - run: nix --extra-experimental-features nix-command --extra-experimental-features flakes develop --command just clippy + # build: + # runs-on: nixos-latest + # steps: + # - name: checkout + # uses: actions/checkout@v4 + # - run: nix --extra-experimental-features nix-command --extra-experimental-features flakes develop --command just build-release diff --git a/src/main.rs b/src/main.rs index 0bc2eee..32b9810 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#![allow(clippy::option_if_let_else)] +#![allow(clippy::missing_panics_doc)] use clap::Parser; use core::service_items::ServiceItem; use core::slide::{ @@ -299,18 +299,13 @@ impl cosmic::Application for App { let (config_handler, settings) = (input.1, input.2); - let items = if let Some(file) = input.0.file { + let items = input.0.file.map_or_else(Vec::new, |file| { match read_to_string(file) { Ok(lisp) => { let mut service_items = vec![]; let lisp = crisp::reader::read(&lisp); match lisp { Value::List(vec) => { - // let items = vec - // .into_par_iter() - // .map(|value| parse_lisp(value)) - // .collect(); - // slide_vector.append(items); for value in vec { let mut inner_vector = parse_lisp(value); @@ -327,9 +322,7 @@ impl cosmic::Application for App { vec![] } } - } else { - vec![] - }; + }); let items: Vec = items .into_par_iter() @@ -1101,14 +1094,11 @@ impl cosmic::Application for App { } else { // debug!("Change slide to previous items slides"); let previous_item_slides_length = - if let Some(item) = self - .service + self.service .get(item_index - 1) - { - item.slides.len() - } else { - 0 - }; + .map_or(0, |item| { + item.slides.len() + }); self.current_item = ( item_index - 1, previous_item_slides_length - 1, @@ -1229,11 +1219,7 @@ impl cosmic::Application for App { }) } Message::CloseWindow(id) => { - if let Some(id) = id { - window::close(id) - } else { - Task::none() - } + id.map_or_else(Task::none, window::close) } Message::WindowOpened(id) => { debug!(?id, "Window opened"); @@ -1454,7 +1440,7 @@ impl cosmic::Application for App { Task::none() } Message::Search(query) => { - self.search_query = query.clone(); + self.search_query.clone_from(&query); self.search(query) } Message::UpdateSearchResults(items) => { @@ -1616,24 +1602,27 @@ impl cosmic::Application for App { ); let video_button_icon = - if let Some(video) = &self.presenter.video { - let (icon_name, tooltip) = if video.paused() { - ("media-play", "Play") - } else { - ("media-pause", "Pause") - }; - button::icon(icon::from_name(icon_name)) - .tooltip(tooltip) - .on_press(Message::Present( - presenter::Message::StartVideo, - )) - } else { - button::icon(icon::from_name("media-play")) - .tooltip("Play") - .on_press(Message::Present( - presenter::Message::StartVideo, - )) - }; + self.presenter.video.as_ref().map_or_else( + || { + button::icon(icon::from_name("media-play")) + .tooltip("Play") + .on_press(Message::Present( + presenter::Message::StartVideo, + )) + }, + |video| { + let (icon_name, tooltip) = if video.paused() { + ("media-play", "Play") + } else { + ("media-pause", "Pause") + }; + button::icon(icon::from_name(icon_name)) + .tooltip(tooltip) + .on_press(Message::Present( + presenter::Message::StartVideo, + )) + }, + ); let slide_preview = column![ Space::with_height(Length::Fill), @@ -1675,13 +1664,10 @@ impl cosmic::Application for App { let library = if self.library_open { Container::new( - Container::new( - if let Some(library) = &self.library { - library.view().map(Message::Library) - } else { - Element::from(Space::new(0, 0)) - }, - ) + Container::new(self.library.as_ref().map_or_else( + || Element::from(Space::new(0, 0)), + |library| library.view().map(Message::Library), + )) .style(nav_bar_style), ) .padding(space_s) @@ -1822,7 +1808,7 @@ where .map(|id| cosmic::Action::App(Message::WindowOpened(id))) } - fn search(&mut self, query: String) -> Task { + fn search(&self, query: String) -> Task { self.library.clone().map_or_else(Task::none, |library| { Task::perform( async move { library.search_items(query).await }, diff --git a/src/ui/library.rs b/src/ui/library.rs index fee41d9..f04e9ae 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -137,6 +137,9 @@ impl<'a> Library { self.song_library.get_item(index) } + #[allow(clippy::cast_possible_wrap)] + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::too_many_lines)] pub fn update(&'a mut self, message: Message) -> Action { match message { Message::None => (), @@ -652,6 +655,7 @@ impl<'a> Library { Message::AddFiles(items) => { let mut tasks = Vec::new(); let last_item = &items.last(); + let after_task = match last_item { Some(ServiceItemKind::Image(_image)) => { Task::done(Message::OpenItem(Some(( @@ -896,6 +900,7 @@ impl<'a> Library { container(library_dnd).padding(2).into() } + #[allow(clippy::too_many_lines)] pub fn library_item( &'a self, model: &'a Model, @@ -953,18 +958,28 @@ impl<'a> Library { .style(|t| { container::Style::default() .background({ - match self.library_hovered { - Some(lib) => Background::Color( - if lib == model.kind { - t.cosmic().button.hover.into() - } else { - t.cosmic().button.base.into() - }, - ), - None => Background::Color( - t.cosmic().button.base.into(), - ), - } + self.library_hovered.map_or_else( + || { + Background::Color( + t.cosmic().button.base.into(), + ) + }, + |library| { + Background::Color( + if library == model.kind { + t.cosmic() + .button + .hover + .into() + } else { + t.cosmic() + .button + .base + .into() + }, + ) + }, + ) }) .border(Border::default().rounded( t.cosmic().corner_radii.radius_s, @@ -987,6 +1002,7 @@ impl<'a> Library { column({ model.items.iter().enumerate().map( |(index, item)| { + let i32_index = i32::try_from(index).expect("shouldn't be negative"); let kind = model.kind; let visual_item = self .single_item(index, item, model) @@ -997,21 +1013,21 @@ impl<'a> Library { let mouse_area = mouse_area.on_enter(Message::HoverItem( Some(( model.kind, - index as i32, + i32_index , )), )) .on_double_click( Message::OpenItem(Some(( model.kind, - index as i32, + i32_index, ))), ) - .on_right_press(Message::OpenContext(index as i32)) + .on_right_press(Message::OpenContext(i32_index )) .on_exit(Message::HoverItem(None)) .on_press(Message::SelectItem( Some(( model.kind, - index as i32, + i32_index, )), )); @@ -1038,7 +1054,7 @@ impl<'a> Library { ) }}) .drag_content(move || { - KindWrapper((kind, index as i32)) + KindWrapper((kind, i32_index)) }) .into() }, @@ -1065,6 +1081,7 @@ impl<'a> Library { column![library_button, lib_container].into() } + #[allow(clippy::too_many_lines)] fn single_item( &'a self, index: usize, @@ -1084,30 +1101,28 @@ impl<'a> Library { .center_x(Length::Fill); let subtext = container(responsive(move |size| { let color: Color = if item.background().is_some() { - if let Some(items) = &self.selected_items { - if items.contains(&(model.kind, index as i32)) { - theme::active().cosmic().control_0().into() - } else { - theme::active() - .cosmic() - .accent_text_color() - .into() - } + if let Some(items) = &self.selected_items + && items.contains(&( + model.kind, + i32::try_from(index) + .expect("Should never be negative"), + )) + { + theme::active().cosmic().control_0().into() } else { theme::active() .cosmic() .accent_text_color() .into() } - } else if let Some(items) = &self.selected_items { - if items.contains(&(model.kind, index as i32)) { - theme::active().cosmic().control_0().into() - } else { - theme::active() - .cosmic() - .destructive_text_color() - .into() - } + } else if let Some(items) = &self.selected_items + && items.contains(&( + model.kind, + i32::try_from(index) + .expect("Should never be negative"), + )) + { + theme::active().cosmic().control_0().into() } else { theme::active() .cosmic() @@ -1135,15 +1150,16 @@ impl<'a> Library { .style(move |t| { container::Style::default() .background(Background::Color( - if let Some(items) = &self.selected_items { - if items.contains(&(model.kind, index as i32)) - { + if let Some(items) = &self.selected_items + && let Ok(index) = i32::try_from(index) + { + if items.contains(&(model.kind, index)) { t.cosmic().accent.selected.into() } else if let Some((library, hovered)) = self.hovered_item { if model.kind == library - && hovered == index as i32 + && hovered == index { t.cosmic().button.hover.into() } else { @@ -1154,10 +1170,9 @@ impl<'a> Library { } } else if let Some((library, hovered)) = self.hovered_item + && let Ok(index) = i32::try_from(index) { - if model.kind == library - && hovered == index as i32 - { + if model.kind == library && hovered == index { t.cosmic().button.hover.into() } else { t.cosmic().button.base.into() @@ -1209,45 +1224,36 @@ impl<'a> Library { query: String, ) -> Vec { let query = query.to_lowercase(); - let mut items: Vec = self + let items = self .song_library .items - .clone() - .into_iter() + .iter() .filter(|song| song.title.to_lowercase().contains(&query)) - .map(ServiceItemKind::Song) - .collect(); - let videos: Vec = self + .map(|song| ServiceItemKind::Song(song.clone())); + let videos = self .video_library .items - .clone() - .into_iter() + .iter() .filter(|vid| vid.title.to_lowercase().contains(&query)) - .map(ServiceItemKind::Video) - .collect(); - let images: Vec = self + .map(|video| ServiceItemKind::Video(video.clone())); + let images = self .image_library .items - .clone() - .into_iter() + .iter() .filter(|image| { image.title.to_lowercase().contains(&query) }) - .map(ServiceItemKind::Image) - .collect(); - let presentations: Vec = self + .map(|image| ServiceItemKind::Image(image.clone())); + let presentations = self .presentation_library .items - .clone() - .into_iter() + .iter() .filter(|pres| pres.title.to_lowercase().contains(&query)) - .map(ServiceItemKind::Presentation) - .collect(); - items.extend(videos); - items.extend(images); - items.extend(presentations); + .map(|pres| ServiceItemKind::Presentation(pres.clone())); + let items = items.chain(videos); + let items = items.chain(images); + let items = items.chain(presentations); let mut items: Vec<(usize, ServiceItemKind)> = items - .into_iter() .map(|item| { ( levenshtein::distance( @@ -1288,6 +1294,7 @@ impl<'a> Library { self.modifiers_pressed = modifiers; } + #[allow(clippy::too_many_lines)] fn delete_items(&mut self) -> Action { // Need to make this function collect tasks to be run off of // who should be deleted @@ -1482,14 +1489,20 @@ async fn add_presentations() -> Option> { } async fn add_db() -> Result { - let mut data = dirs::data_local_dir().unwrap(); + let mut data = dirs::data_local_dir() + .expect("Should always find a data dir"); data.push("lumina"); data.push("library-db.sqlite3"); let mut db_url = String::from("sqlite://"); - db_url.push_str(data.to_str().unwrap()); + db_url.push_str( + data.to_str().expect("Should always be a file here"), + ); SqlitePool::connect(&db_url).await.into_diagnostic() } +#[allow(clippy::cast_sign_loss)] +#[allow(clippy::cast_precision_loss)] +#[allow(clippy::cast_possible_truncation)] pub fn elide_text(text: impl AsRef, width: f32) -> String { const CHAR_SIZE: f32 = 8.0; let text: String = text.as_ref().to_owned(); diff --git a/src/ui/presentation_editor.rs b/src/ui/presentation_editor.rs index df04ffa..1bb2ec4 100644 --- a/src/ui/presentation_editor.rs +++ b/src/ui/presentation_editor.rs @@ -95,6 +95,7 @@ impl PresentationEditor { context_menu_id: None, } } + #[allow(clippy::too_many_lines)] pub fn update(&mut self, message: Message) -> Action { match message { Message::ChangePresentation(presentation) => { @@ -122,7 +123,7 @@ impl PresentationEditor { } } Message::ChangeTitle(title) => { - self.title = title.clone(); + self.title.clone_from(&title); if let Some(presentation) = &self.presentation { let mut presentation = presentation.clone(); presentation.title = title; @@ -147,17 +148,17 @@ impl PresentationEditor { let task = Task::perform( pick_presentation(), move |presentation_result| { - if let Ok(presentation) = presentation_result - { - let mut presentation = - Presentation::from(presentation); - presentation.id = presentation_id; - Message::ChangePresentationFile( - presentation, - ) - } else { - Message::None - } + presentation_result.map_or( + Message::None, + |presentation| { + let mut presentation = + Presentation::from(presentation); + presentation.id = presentation_id; + Message::ChangePresentationFile( + presentation, + ) + }, + ) }, ); return Action::Task(task); @@ -198,14 +199,16 @@ impl PresentationEditor { Message::NextPage => { let next_index = self.current_slide_index.unwrap_or_default() + 1; - let mut last_index = - self.page_count.unwrap_or_default(); - if let Some(presentation) = self.presentation.as_ref() + + let last_index = if let Some(presentation) = + self.presentation.as_ref() && let PresKind::Pdf { ending_index, .. } = presentation.kind { - last_index = ending_index; - } + ending_index + } else { + self.page_count.unwrap_or_default() + }; if next_index > last_index { return Action::None; @@ -241,14 +244,16 @@ impl PresentationEditor { Message::PrevPage => { let previous_index = self.current_slide_index.unwrap_or_default() - 1; - let mut first_index = - self.page_count.unwrap_or_default(); - if let Some(presentation) = self.presentation.as_ref() + + let first_index = if let Some(presentation) = + self.presentation.as_ref() && let PresKind::Pdf { starting_index, .. } = presentation.kind { - first_index = starting_index; - } + starting_index + } else { + self.page_count.unwrap_or_default() + }; if previous_index < first_index { return Action::None; @@ -279,8 +284,9 @@ impl PresentationEditor { Message::ChangeSlide(index) => { self.current_slide = self.document.as_ref().and_then(|doc| { - let page = - doc.load_page(index as i32).ok()?; + let page = doc + .load_page(i32::try_from(index).ok()?) + .ok()?; let matrix = Matrix::IDENTITY; let colorspace = Colorspace::device_rgb(); let pixmap = page @@ -298,13 +304,13 @@ impl PresentationEditor { pixmap.samples().to_vec(), )) }); - self.current_slide_index = Some(index as i32); + self.current_slide_index = i32::try_from(index).ok(); } Message::HoverSlide(slide) => { self.hovered_slide = slide; } Message::ContextMenu(index) => { - self.context_menu_id = Some(index as i32); + self.context_menu_id = i32::try_from(index).ok(); } Message::SplitBefore => { if let Ok((first, second)) = self.split_before() { @@ -327,66 +333,74 @@ impl PresentationEditor { } pub fn view(&self) -> Element { - let presentation = if let Some(slide) = &self.current_slide { - container( - widget::image(slide) - .content_fit(ContentFit::ScaleDown), - ) - .style(|_| { - container::background(Background::Color( - cosmic::iced::Color::WHITE, - )) - }) - } else { - container(Space::new(0, 0)) - }; - let pdf_pages: Vec> = if let Some(pages) = - &self.slides - { - pages - .iter() - .enumerate() - .map(|(index, page)| { - let image = widget::image(page) - .height(theme::spacing().space_xxxl * 3) - .content_fit(ContentFit::ScaleDown); - let slide = container(image).style(|_| { - container::background(Background::Color( - cosmic::iced::Color::WHITE, - )) - }); - let clickable_slide = container( - mouse_area(slide) - .on_enter(Message::HoverSlide(Some( - index as i32, - ))) - .on_exit(Message::HoverSlide(None)) - .on_right_press(Message::ContextMenu( - index, - )) - .on_press(Message::ChangeSlide(index)), - ) - .padding(theme::spacing().space_m) - .clip(true) - .class( - if let Some(hovered_index) = - self.hovered_slide - { - if index as i32 == hovered_index { - theme::Container::Primary - } else { - theme::Container::Card - } - } else { - theme::Container::Card - }, - ); - clickable_slide.into() + let presentation = self.current_slide.as_ref().map_or_else( + || container(Space::new(0, 0)), + |slide| { + container( + widget::image(slide) + .content_fit(ContentFit::ScaleDown), + ) + .style(|_| { + container::background(Background::Color( + cosmic::iced::Color::WHITE, + )) }) - .collect() - } else { - vec![horizontal_space().into()] - }; + }, + ); + let pdf_pages: Vec> = + self.slides.as_ref().map_or_else( + || vec![horizontal_space().into()], + |pages| { + pages + .iter() + .enumerate() + .map(|(index, page)| { + let image = widget::image(page) + .height( + theme::spacing().space_xxxl * 3, + ) + .content_fit(ContentFit::ScaleDown); + let slide = container(image).style(|_| { + container::background(Background::Color( + cosmic::iced::Color::WHITE, + )) + }); + let clickable_slide = container( + mouse_area(slide) + .on_enter(Message::HoverSlide( + i32::try_from(index).ok(), + )) + .on_exit(Message::HoverSlide( + None, + )) + .on_right_press( + Message::ContextMenu(index), + ) + .on_press(Message::ChangeSlide( + index, + )), + ) + .padding(theme::spacing().space_m) + .clip(true) + .class(self.hovered_slide.map_or( + theme::Container::Card, + |hovered_index| { + if i32::try_from(index).is_ok_and( + |index| { + index == hovered_index + }, + ) { + theme::Container::Primary + } else { + theme::Container::Card + } + }, + )); + clickable_slide.into() + }) + .collect() + }, + ); let pages_column = container( self.context_menu( scrollable( @@ -485,7 +499,7 @@ impl PresentationEditor { presentation: &Presentation, ) { self.presentation = Some(presentation.clone()); - self.title = presentation.title.clone(); + self.title.clone_from(&presentation.title); self.document = Document::open(&presentation.path.as_path()).ok(); self.page_count = self @@ -615,7 +629,9 @@ fn get_pages( pages .enumerate() .filter_map(|(index, page)| { - if !range.contains(&(index as i32)) { + if !range.contains(&i32::try_from(index).expect( + "looking for a pdf index that is way too large", + )) { return None; } let page = page.ok()?; @@ -649,7 +665,9 @@ async fn pick_presentation() -> Result { error!(?e); PresentationError::DialogClosed }) - .map(|file| file.url().to_file_path().unwrap()) + .map(|file| { + file.url().to_file_path().expect("Should be a file here") + }) // rfd::AsyncFileDialog::new() // .set_title("Choose a background...") // .add_filter( diff --git a/src/ui/presenter.rs b/src/ui/presenter.rs index ec6c934..89de4ed 100644 --- a/src/ui/presenter.rs +++ b/src/ui/presenter.rs @@ -255,35 +255,29 @@ impl Presenter { pub fn with_items(items: Vec) -> Self { let video = { - if let Some(item) = items.first() { - if let Some(slide) = item.slides.first() { + items.first().and_then(|item| { + item.slides.first().and_then(|slide| { let path = slide.background().path.clone(); - if path.exists() { - let url = Url::from_file_path(path).expect( - "There should be a video file here", - ); - let result = Video::new(&url); - match result { - Ok(mut v) => { - v.set_paused(true); - Some(v) - } - Err(e) => { - error!( - "Had an error creating the video object: {e}, likely the first slide isn't a video" - ); - None - } - } - } else { - None + if !path.exists() { + return None; } - } else { - None - } - } else { - None - } + let url = Url::from_file_path(path).expect( + "There should be a video file here", + ); + match Video::new(&url) { + Ok(mut v) => { + v.set_paused(true); + Some(v) + } + Err(e) => { + error!( + "Had an error creating the video object: {e}, likely the first slide isn't a video" + ); + None + } + } + }) + }) }; let total_slides: usize = items.iter().fold(0, |a, item| a + item.slides.len()); @@ -470,11 +464,9 @@ impl Presenter { // self.current_slide_index = slide; debug!("cloning slide..."); self.current_slide = slide.clone(); - let font = if let Some(font) = slide.font() { - font.get_name() - } else { - String::new() - }; + let font = slide + .font() + .map_or_else(String::new, |font| font.get_name()); let _ = self.update(Message::ChangeFont(font)); debug!("changing video now..."); if !backgrounds_match { @@ -508,6 +500,7 @@ impl Presenter { debug!(target_item); + #[allow(clippy::cast_precision_loss)] let offset = AbsoluteOffset { x: { if target_item > 2 { @@ -541,25 +534,8 @@ impl Presenter { } debug!("{:?}", new_audio); if new_audio.exists() { - let old_audio = self.audio.clone(); - if let Some(current_audio) = old_audio - && current_audio != *new_audio - { - debug!( - ?new_audio, - ?current_audio, - "audio needs to change" - ); - self.audio = Some(new_audio); - tasks.push(self.start_audio()); - } else { - debug!( - ?new_audio, - "could not find audio, need to change" - ); - self.audio = Some(new_audio); - tasks.push(self.start_audio()); - } + self.audio = Some(new_audio); + tasks.push(self.start_audio()); } else { self.audio = None; self.update(Message::EndAudio); diff --git a/src/ui/song_editor.rs b/src/ui/song_editor.rs index a8ef51c..3084f6d 100644 --- a/src/ui/song_editor.rs +++ b/src/ui/song_editor.rs @@ -381,7 +381,10 @@ impl SongEditor { } } if let Some(font_size) = song.font_size { - self.font_size = font_size as usize; + self.font_size = usize::try_from(font_size) + .expect( + "There shouldn't be a negative font_size", + ); } if let Some(verse_order) = song.verse_order { self.verse_order = verse_order @@ -876,29 +879,35 @@ impl SongEditor { } pub fn view(&self) -> Element { - let video_elements = if let Some(video) = &self.video { - let play_button = button::icon(if video.paused() { - icon::from_name("media-playback-start") - } else { - icon::from_name("media-playback-pause") - }) - .on_press(Message::PauseVideo); - let video_track = progress_bar( - 0.0..=video.duration().as_secs_f32(), - video.position().as_secs_f32(), - ) - .height(cosmic::theme::spacing().space_s) - .width(Length::Fill); - container( - row![play_button, video_track] - .align_y(Vertical::Center) - .spacing(cosmic::theme::spacing().space_m), - ) - .padding(cosmic::theme::spacing().space_s) - .center_x(Length::FillPortion(2)) - } else { - container(horizontal_space()) - }; + let video_elements: Element = + self.video.as_ref().map_or_else( + || horizontal_space().into(), + |video| { + let play_button = + button::icon(if video.paused() { + icon::from_name("media-playback-start") + } else { + icon::from_name("media-playback-pause") + }) + .on_press(Message::PauseVideo); + let video_track = progress_bar( + 0.0..=video.duration().as_secs_f32(), + video.position().as_secs_f32(), + ) + .height(cosmic::theme::spacing().space_s) + .width(Length::Fill); + container( + row![play_button, video_track] + .align_y(Vertical::Center) + .spacing( + cosmic::theme::spacing().space_m, + ), + ) + .padding(cosmic::theme::spacing().space_s) + .center_x(Length::FillPortion(2)) + .into() + }, + ); let slide_preview = container(self.slide_preview()) .width(Length::FillPortion(2)); @@ -918,44 +927,42 @@ impl SongEditor { } fn slide_preview(&self) -> Element { - if let Some(slides) = &self.song_slides { - let slides: Vec> = slides - .iter() - .enumerate() - .map(|(index, slide)| { - container( - slide_view( - slide, - if index == 0 { - self.video.as_ref() - } else { - None - }, - false, - false, + self.song_slides.as_ref().map_or_else( + || horizontal_space().into(), + |slides| { + let slides: Vec> = slides + .iter() + .enumerate() + .map(|(index, slide)| { + container( + slide_view( + slide, + if index == 0 { + self.video.as_ref() + } else { + None + }, + false, + false, + ) + .map(|_| Message::None), ) - .map(|_| Message::None), - ) - .height(250) // need to find out how to do this differently - .center_x(Length::Fill) - .padding([0, 20]) - .clip(true) - .into() - }) - .collect(); - scrollable( - cosmic::widget::column::with_children(slides) - .spacing(theme::active().cosmic().space_l()), - ) - .height(Length::Fill) - .width(Length::Fill) - .into() - } else { - horizontal_space().into() - } - // self.slide_state - // .view(Font::with_name("Quicksand Bold")) - // .map(|_s| Message::None) + .height(250) // need to find out how to do this differently + .center_x(Length::Fill) + .padding([0, 20]) + .clip(true) + .into() + }) + .collect(); + scrollable( + cosmic::widget::column::with_children(slides) + .spacing(theme::active().cosmic().space_l()), + ) + .height(Length::Fill) + .width(Length::Fill) + .into() + }, + ) } fn left_column(&self) -> Element { @@ -987,62 +994,64 @@ impl SongEditor { // .on_input(Message::ChangeVerseOrder); let verse_option_chips: Vec> = - if let Some(song) = &self.song { - if let Some(verse_map) = &song.verse_map { - verse_map - .keys() - .sorted() - .map(|verse| { - let verse = *verse; - let chip = verse_chip(verse, None); - let verse_chip_wrapped = - RcElementWrapper::::new( - chip, - ); - Element::from( - dnd_source::>( - verse_chip_wrapped.clone(), - ) - .on_start(Some( - Message::DraggingChipStart, - )) - .on_finish(Some( - Message::DraggingChipStart, - )) - .on_cancel(Some( - Message::DraggingChipStart, - )) - .drag_content(move || Box::new(verse)) - .drag_icon( - move |_| { - let state: tree::State = - cosmic::widget::Widget::< - Message, - _, - _, - >::state( - &verse_chip_wrapped - ); - ( - Element::from( - verse_chip_wrapped - .clone(), - ) - .map(|_| ()), - state, - Vector::new(-5.0, -15.0), + self.song.as_ref().map_or_else(Vec::new, |song| { + song.verse_map.as_ref().map_or_else( + Vec::new, + |verse_map| { + verse_map + .keys() + .sorted() + .map(|verse| { + let verse = *verse; + let chip = verse_chip(verse, None); + let verse_chip_wrapped = + RcElementWrapper::::new( + chip, + ); + Element::from( + dnd_source::< + Message, + Box, + >( + verse_chip_wrapped.clone() ) - }, - ), - ) - }) - .collect() - } else { - vec![] - } - } else { - vec![] - }; + .on_start(Some( + Message::DraggingChipStart, + )) + .on_finish(Some( + Message::DraggingChipStart, + )) + .on_cancel(Some( + Message::DraggingChipStart, + )) + .drag_content(move || { + Box::new(verse) + }) + .drag_icon(move |_| { + let state: tree::State = + cosmic::widget::Widget::< + Message, + _, + _, + >::state( + &verse_chip_wrapped, + ); + ( + Element::from( + verse_chip_wrapped + .clone(), + ) + .map(|_| ()), + state, + Vector::new(-5.0, -15.0), + ) + }), + ) + }) + .collect() + }, + ) + }); let verse_options = container( scrollable(row(verse_option_chips).spacing(space_s)) @@ -1062,76 +1071,69 @@ impl SongEditor { }) .on_press(Message::EditVerseOrder); - let verse_order_items: Vec> = if let Some( - song, - ) = - &self.song - { - if let Some(verses) = &song.verses { - verses - .iter() - .enumerate() - .map(|(index, verse)| { - let verse = *verse; - let hovered_chip = self.hovered_verse_chip.filter(|hovered_index| hovered_index == &index); - let mut chip = - verse_chip(verse, hovered_chip).apply(mouse_area) - .on_enter(Message::ChipHovered(Some(index))) - .on_exit(Message::ChipHovered(None)) - .into(); - if let Some(hovered_chip) = - self.hovered_dnd_verse_chip - && index == hovered_chip { - let phantom_chip = horizontal_space().width(60).height(19) - .apply(container) - .padding( - Padding::new(space_xxs.into()) - .right(space_s) - .left(space_s), - ) - .class(theme::Container::Custom(Box::new(move |t| { - container::Style::default() - .background(ContainerBackground::Color( - Color::from(t.cosmic().secondary.base).scale_alpha(0.5) - )) - .border(Border::default().rounded(space_m).width(2)) - }))); - chip = row![ - phantom_chip, - chip - ] - .spacing(space_s) + let verse_order_items: Vec> = + self.song.as_ref().map_or_else(Vec::new, |song| { + song.verses.as_ref().map_or_else(Vec::new, |verses| { + verses + .iter() + .enumerate() + .map(|(index, verse)| { + let verse = *verse; + let hovered_chip = self.hovered_verse_chip.filter(|hovered_index| hovered_index == &index); + let mut chip = + verse_chip(verse, hovered_chip).apply(mouse_area) + .on_enter(Message::ChipHovered(Some(index))) + .on_exit(Message::ChipHovered(None)) .into(); - } - let verse_chip_wrapped = - RcElementWrapper::::new(chip); - Element::from( - dnd_destination( - verse_chip_wrapped, - vec!["application/verse".into()], + if let Some(hovered_chip) = + self.hovered_dnd_verse_chip + && index == hovered_chip { + let phantom_chip = horizontal_space().width(60).height(19) + .apply(container) + .padding( + Padding::new(space_xxs.into()) + .right(space_s) + .left(space_s), + ) + .class(theme::Container::Custom(Box::new(move |t| { + container::Style::default() + .background(ContainerBackground::Color( + Color::from(t.cosmic().secondary.base).scale_alpha(0.5) + )) + .border(Border::default().rounded(space_m).width(2)) + }))); + chip = row![ + phantom_chip, + chip + ] + .spacing(space_s) + .into(); + } + let verse_chip_wrapped = + RcElementWrapper::::new(chip); + Element::from( + dnd_destination( + verse_chip_wrapped, + vec!["application/verse".into()], + ) + .on_enter(move |x, y, mimes| { + debug!(x, y, ?mimes); + Message::ChipDndHovered(Some(index)) + }) + .on_leave(move || { + Message::ChipDndHovered(None) + }) + .on_finish( + move |mime, data, action, _x, _y| { + debug!(mime, ?data, ?action); + Message::ChipDropped((index, data, mime)) + }, + ), ) - .on_enter(move |x, y, mimes| { - debug!(x, y, ?mimes); - Message::ChipDndHovered(Some(index)) - }) - .on_leave(move || { - Message::ChipDndHovered(None) - }) - .on_finish( - move |mime, data, action, _x, _y| { - debug!(mime, ?data, ?action); - Message::ChipDropped((index, data, mime)) - }, - ), - ) - }) - .collect() - } else { - vec![] - } - } else { - vec![] - }; + }) + .collect() + }) + }); let verse_order_items = if self.dragging_verse_chip { Element::from(row(verse_order_items).spacing(space_s)) @@ -1214,27 +1216,28 @@ impl SongEditor { ] .spacing(5); - let verse_list = if let Some(verse_list) = &self.verses { - Element::from( - column(verse_list.iter().enumerate().map( - |(index, v)| { - column![ - v.view().map(move |message| { - Message::VerseEditorMessage(( - index, message, - )) - }), - divider::horizontal::heavy() - ] - .spacing(space_m) - .into() - }, - )) - .spacing(space_m), - ) - } else { - Element::from(horizontal_space()) - }; + let verse_list = self.verses.as_ref().map_or_else( + || Element::from(horizontal_space()), + |verse_list| { + Element::from( + column(verse_list.iter().enumerate().map( + |(index, v)| { + column![ + v.view().map(move |message| { + Message::VerseEditorMessage(( + index, message, + )) + }), + divider::horizontal::heavy() + ] + .spacing(space_m) + .into() + }, + )) + .spacing(space_m), + ) + }, + ); let verse_scroller = scrollable( verse_list .apply(container) diff --git a/src/ui/video_editor.rs b/src/ui/video_editor.rs index 742804c..6295ed6 100644 --- a/src/ui/video_editor.rs +++ b/src/ui/video_editor.rs @@ -89,14 +89,12 @@ impl VideoEditor { let task = Task::perform( pick_video(), move |video_result| { - if let Ok(video) = video_result { + video_result.map_or(Message::None, |video| { let mut video = videos::Video::from(video); video.id = video_id; Message::UpdateVideoFile(video) - } else { - Message::None - } + }) }, ); return Action::Task(task); @@ -111,29 +109,30 @@ impl VideoEditor { } pub fn view(&self) -> Element { - let video_elements = if let Some(video) = &self.video { - let play_button = button::icon(if video.paused() { - icon::from_name("media-playback-start") - } else { - icon::from_name("media-playback-pause") - }) - .on_press(Message::PauseVideo); - let video_track = progress_bar( - 0.0..=video.duration().as_secs_f32(), - video.position().as_secs_f32(), - ) - .height(cosmic::theme::spacing().space_s) - .width(Length::Fill); - container( - row![play_button, video_track] - .align_y(Vertical::Center) - .spacing(cosmic::theme::spacing().space_m), - ) - .padding(cosmic::theme::spacing().space_s) - .center_x(Length::FillPortion(2)) - } else { - container(horizontal_space()) - }; + let video_elements = self.video.as_ref().map_or_else( + || container(horizontal_space()), + |video| { + let play_button = button::icon(if video.paused() { + icon::from_name("media-playback-start") + } else { + icon::from_name("media-playback-pause") + }) + .on_press(Message::PauseVideo); + let video_track = progress_bar( + 0.0..=video.duration().as_secs_f32(), + video.position().as_secs_f32(), + ) + .height(cosmic::theme::spacing().space_s) + .width(Length::Fill); + container( + row![play_button, video_track] + .align_y(Vertical::Center) + .spacing(cosmic::theme::spacing().space_m), + ) + .padding(cosmic::theme::spacing().space_s) + .center_x(Length::FillPortion(2)) + }, + ); let video_player = self.video.as_ref().map_or_else( || Element::from(Space::new(0, 0)),