diff --git a/src/main.rs b/src/main.rs index bc0f3d9..e667b26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -135,6 +135,7 @@ struct App { fontdb: Arc, menu_keys: HashMap, context_menu: Option, + modifiers_pressed: Option, } #[derive(Debug, Clone)] @@ -176,6 +177,7 @@ enum Message { Save(Option), SaveAs, OpenSettings, + ModifiersPressed(Modifiers), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -341,6 +343,7 @@ impl cosmic::Application for App { menu_keys, hovered_item: None, context_menu: None, + modifiers_pressed: None, }; let mut batch = vec![]; @@ -594,6 +597,9 @@ impl cosmic::Application for App { modifiers, .. } => Some(Message::Key(key, modifiers)), + iced::keyboard::Event::ModifiersChanged( + modifiers, + ) => Some(Message::ModifiersPressed(modifiers)), _ => None, }, iced::Event::Mouse(_event) => None, @@ -1251,6 +1257,20 @@ impl cosmic::Application for App { debug!("Opening settings"); Task::none() } + Message::ModifiersPressed(modifiers) => { + if modifiers.is_empty() { + self.modifiers_pressed = None; + if let Some(library) = self.library.as_mut() { + library.set_modifiers(None); + } + return Task::none(); + } + if let Some(library) = self.library.as_mut() { + library.set_modifiers(Some(modifiers)); + } + self.modifiers_pressed = Some(modifiers); + Task::none() + } } } diff --git a/src/ui/library.rs b/src/ui/library.rs index e8168d4..9e9630c 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -4,7 +4,8 @@ use cosmic::{ dialog::file_chooser::open::Dialog, iced::{ alignment::Vertical, clipboard::dnd::DndAction, - futures::FutureExt, Background, Border, Color, Length, + futures::FutureExt, keyboard::Modifiers, Background, Border, + Color, Length, }, iced_core::widget::tree::State, iced_widget::{column, row as rowm, text as textm}, @@ -18,9 +19,9 @@ use cosmic::{ }, Element, Task, }; -use miette::{IntoDiagnostic, Result}; +use miette::{miette, IntoDiagnostic, Result}; use rapidfuzz::distance::levenshtein; -use sqlx::{migrate, pool::PoolConnection, Sqlite, SqlitePool}; +use sqlx::{migrate, SqlitePool}; use tracing::{debug, error, warn}; use crate::core::{ @@ -41,12 +42,13 @@ pub struct Library { presentation_library: Model, library_open: Option, library_hovered: Option, - selected_item: Option<(LibraryKind, i32)>, + selected_items: Option>, hovered_item: Option<(LibraryKind, i32)>, editing_item: Option<(LibraryKind, i32)>, db: SqlitePool, menu_keys: std::collections::HashMap, context_menu: Option, + modifiers_pressed: Option, } #[derive(Debug, Clone, Eq, PartialEq, Copy)] @@ -116,12 +118,13 @@ impl<'a> Library { .await, library_open: None, library_hovered: None, - selected_item: None, + selected_items: None, hovered_item: None, editing_item: None, db, menu_keys: HashMap::new(), context_menu: None, + modifiers_pressed: None, } } @@ -133,154 +136,7 @@ impl<'a> Library { match message { Message::None => (), Message::DeleteItem((kind, index)) => { - match kind { - LibraryKind::Song => { - let Some(song) = - self.song_library.get_item(index) - else { - error!( - "There appears to not be a song here" - ); - return Action::None; - }; - let song = song.clone(); - if let Err(e) = - self.song_library.remove_item(index) - { - error!(?e); - } else { - let task = - Task::future(self.db.acquire()) - .and_then(move |db| { - Task::perform( - songs::remove_from_db( - db, song.id, - ), - |r| { - match r { - Err(e) => { - error!(?e) - } - _ => (), - } - Message::None - }, - ) - }); - return Action::Task(task); - } - } - LibraryKind::Video => { - let Some(video) = - self.video_library.get_item(index) - else { - error!( - "There appears to not be a video here" - ); - return Action::None; - }; - let video = video.clone(); - if let Err(e) = - self.video_library.remove_item(index) - { - error!(?e); - } else { - let task = - Task::future(self.db.acquire()) - .and_then(move |db| { - Task::perform( - videos::remove_from_db( - db, video.id, - ), - |r| { - match r { - Err(e) => { - error!(?e) - } - _ => (), - } - Message::None - }, - ) - }); - return Action::Task(task); - } - } - LibraryKind::Image => { - let Some(image) = - self.image_library.get_item(index) - else { - error!( - "There appears to not be a image here" - ); - return Action::None; - }; - let image = image.clone(); - if let Err(e) = - self.image_library.remove_item(index) - { - error!(?e); - } else { - let task = - Task::future(self.db.acquire()) - .and_then(move |db| { - Task::perform( - images::remove_from_db( - db, image.id, - ), - |r| { - match r { - Err(e) => { - error!(?e) - } - _ => (), - } - Message::None - }, - ) - }); - return Action::Task(task); - } - } - LibraryKind::Presentation => { - let Some(presentation) = - self.presentation_library.get_item(index) - else { - error!( - "There appears to not be a presentation here" - ); - return Action::None; - }; - let presentation = presentation.clone(); - if let Err(e) = self - .presentation_library - .remove_item(index) - { - error!(?e); - } else { - let task = - Task::future(self.db.acquire()) - .and_then(move |db| { - Task::perform( - presentations::remove_from_db( - db, - presentation.id, - ), - |r| { - match r { - Err(e) => { - error!(?e) - } - _ => (), - } - Message::None - }, - ) - }); - return Action::Task(task); - } - } - }; + return self.delete_items(); } Message::AddItem => { let kind = @@ -374,7 +230,63 @@ impl<'a> Library { self.hovered_item = item; } Message::SelectItem(item) => { - self.selected_item = item; + let Some(modifiers) = self.modifiers_pressed else { + let Some(item) = item else { + return Action::None; + }; + self.selected_items = Some(vec![item]); + return Action::None; + }; + if modifiers.is_empty() { + let Some(item) = item else { + return Action::None; + }; + self.selected_items = Some(vec![item]); + return Action::None; + } + if modifiers.shift() { + let Some(first_item) = self + .selected_items + .as_ref() + .map(|items| { + items + .iter() + .next() + .map(|(_, index)| index) + }) + .flatten() + else { + let Some(item) = item else { + return Action::None; + }; + self.selected_items = Some(vec![item]); + return Action::None; + }; + let Some((kind, index)) = item else { + return Action::None; + }; + if first_item < &index { + for id in *first_item..=index { + self.selected_items = self + .selected_items + .clone() + .and_then(|mut items| { + items.push((kind, id)); + Some(items) + }); + } + } else if first_item > &index { + for id in index..*first_item { + self.selected_items = self + .selected_items + .clone() + .and_then(|mut items| { + items.push((kind, id)); + Some(items) + }); + } + } + } } Message::DragItem(item) => { debug!(?item); @@ -795,11 +707,8 @@ impl<'a> Library { .center_x(Length::Fill); let subtext = container(responsive(move |size| { let color: Color = if item.background().is_some() { - if let Some((library, selected)) = self.selected_item - { - if model.kind == library - && selected == index as i32 - { + if let Some(items) = &self.selected_items { + if items.contains(&(model.kind, index as i32)) { theme::active().cosmic().control_0().into() } else { theme::active() @@ -813,10 +722,8 @@ impl<'a> Library { .accent_text_color() .into() } - } else if let Some((library, selected)) = - self.selected_item - { - if model.kind == library && selected == index as i32 { + } 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() @@ -851,11 +758,8 @@ impl<'a> Library { .style(move |t| { container::Style::default() .background(Background::Color( - if let Some((library, selected)) = - self.selected_item - { - if model.kind == library - && selected == index as i32 + if let Some(items) = &self.selected_items { + if items.contains(&(model.kind, index as i32)) { t.cosmic().accent.selected.into() } else if let Some((library, hovered)) = @@ -965,6 +869,169 @@ impl<'a> Library { pub fn get_image(&self, index: i32) -> Option<&Image> { self.image_library.get_item(index) } + + pub fn set_modifiers(&mut self, modifiers: Option) { + self.modifiers_pressed = modifiers; + } + + fn delete_items(&mut self) -> Action { + // Need to make this function collect tasks to be run off of + // who should be deleted + let Some(ref items) = self.selected_items else { + return Action::None; + }; + let tasks: Vec> = items + .iter() + .map(|(kind, index)| match kind { + LibraryKind::Song => { + if let Some(song) = + self.song_library.get_item(*index) + { + let song = song.clone(); + if let Err(e) = + self.song_library.remove_item(*index) + { + error!(?e); + Task::none() + } else { + let task = + Task::future(self.db.acquire()) + .and_then(move |db| { + Task::perform( + songs::remove_from_db( + db, song.id, + ), + |r| { + match r { + Err(e) => { + error!(?e) + } + _ => (), + } + Message::None + }, + ) + }); + task + } + } else { + Task::none() + } + } + LibraryKind::Video => { + if let Some(video) = + self.video_library.get_item(*index) + { + let video = video.clone(); + if let Err(e) = + self.video_library.remove_item(*index) + { + error!(?e); + Task::none() + } else { + let task = + Task::future(self.db.acquire()) + .and_then(move |db| { + Task::perform( + videos::remove_from_db( + db, video.id, + ), + |r| { + match r { + Err(e) => { + error!(?e) + } + _ => (), + } + Message::None + }, + ) + }); + task + } + } else { + Task::none() + } + } + LibraryKind::Image => { + if let Some(image) = + self.image_library.get_item(*index) + { + let image = image.clone(); + if let Err(e) = + self.image_library.remove_item(*index) + { + error!(?e); + Task::none() + } else { + let task = + Task::future(self.db.acquire()) + .and_then(move |db| { + Task::perform( + images::remove_from_db( + db, image.id, + ), + |r| { + match r { + Err(e) => { + error!(?e) + } + _ => (), + } + Message::None + }, + ) + }); + task + } + } else { + Task::none() + } + } + LibraryKind::Presentation => { + if let Some(presentation) = + self.presentation_library.get_item(*index) + { + let presentation = presentation.clone(); + if let Err(e) = self + .presentation_library + .remove_item(*index) + { + error!(?e); + Task::none() + } else { + let task = + Task::future(self.db.acquire()) + .and_then(move |db| { + Task::perform( + presentations::remove_from_db( + db, + presentation.id, + ), + |r| { + match r { + Err(e) => { + error!(?e) + } + _ => (), + } + Message::None + }, + ) + }); + task + } + } else { + Task::none() + } + } + }) + .collect(); + if tasks.len() > 0 { + self.selected_items = None; + } + Action::Task(Task::batch(tasks)) + } } async fn add_images() -> Option> {