diff --git a/src/ui/components/collatable_media_grid/collatable_film_grid.rs b/src/ui/components/collatable_media_grid/collatable_film_grid.rs index 2a4a661..bca8f53 100644 --- a/src/ui/components/collatable_media_grid/collatable_film_grid.rs +++ b/src/ui/components/collatable_media_grid/collatable_film_grid.rs @@ -38,13 +38,13 @@ impl SimpleComponent for CollatableFilmGrid { let collation_menu = FilmCollationMenu::builder().launch(()).forward( film_grid.sender(), |message| match message { - FilmCollationMenuOutput::SortedBy(sorting, direction) => { + FilmCollationMenuOutput::SortBy(sorting, direction) => { FilmGridInput::SortBy(sorting, direction) } - FilmCollationMenuOutput::WatchFilterApplied(watched_filter) => { - FilmGridInput::ApplyWatchFilter(watched_filter) + FilmCollationMenuOutput::ApplyWatchedFilter(watched_filter) => { + FilmGridInput::ApplyWatchedFilter(watched_filter) } - FilmCollationMenuOutput::NameFilterApplied(name_filter) => { + FilmCollationMenuOutput::ApplyNameFilter(name_filter) => { FilmGridInput::ApplyNameFilter(name_filter) } }, diff --git a/src/ui/components/collatable_media_grid/collatable_series_grid.rs b/src/ui/components/collatable_media_grid/collatable_series_grid.rs index 59df3a0..aa0371d 100644 --- a/src/ui/components/collatable_media_grid/collatable_series_grid.rs +++ b/src/ui/components/collatable_media_grid/collatable_series_grid.rs @@ -39,7 +39,7 @@ impl SimpleComponent for CollatableSeriesGrid { SeriesCollationMenu::builder() .launch(()) .forward(series_grid.sender(), |message| match message { - SeriesCollationMenuOutput::SortedBy(sorting, direction) => { + SeriesCollationMenuOutput::SortBy(sorting, direction) => { SeriesGridInput::SortBy(sorting, direction) } }); diff --git a/src/ui/components/media_collation_menu/film_collation_menu.rs b/src/ui/components/media_collation_menu/film_collation_menu.rs index fc66b53..cd77847 100644 --- a/src/ui/components/media_collation_menu/film_collation_menu.rs +++ b/src/ui/components/media_collation_menu/film_collation_menu.rs @@ -6,7 +6,7 @@ use gtk4::{Align, Button, Entry, Label, ListBox, MenuButton, Orientation, Popove use relm4::{ComponentParts, ComponentSender, SimpleComponent, component}; use crate::ui::components::sorting_popover_entry::sorting_popover_entry; -use crate::ui::filtering::WatchFilter; +use crate::ui::filtering::WatchedFilter; use crate::ui::sorting::{FilmsSorting, SortingDirection}; use crate::ui::widget_extensions::ComponentSenderExt; @@ -15,7 +15,7 @@ use crate::ui::widget_extensions::ComponentSenderExt; pub struct FilmCollationMenu { sorted_by: FilmsSorting, sort_direction: SortingDirection, - watch_filter: Option, + watched_filter: Option, } #[derive(Debug)] @@ -24,14 +24,14 @@ pub enum FilmCollationMenuInput { ToggleSortOrder, ToggleWatchedFilter, ToggleUnwatchedFilter, - SetNameFilter(String), + NameFilterSet(String), } #[derive(Debug)] pub enum FilmCollationMenuOutput { - SortedBy(FilmsSorting, SortingDirection), - WatchFilterApplied(Option), - NameFilterApplied(Option), + SortBy(FilmsSorting, SortingDirection), + ApplyWatchedFilter(Option), + ApplyNameFilter(Option), } #[component(pub)] @@ -106,7 +106,7 @@ impl SimpleComponent for FilmCollationMenu { set_secondary_icon_sensitive: false, connect_changed[sender] => move |entry| { - sender.input(FilmCollationMenuInput::SetNameFilter(entry.text().to_string())); + sender.input(FilmCollationMenuInput::NameFilterSet(entry.text().to_string())); }, }, @@ -118,14 +118,14 @@ impl SimpleComponent for FilmCollationMenu { set_icon_name: "eye-outline-filled-symbolic", #[watch] #[block_signal(watched_toggled)] - set_active: model.watch_filter == Some(WatchFilter::OnlyWatched), + set_active: model.watched_filter == Some(WatchedFilter::OnlyWatched), connect_toggled => FilmCollationMenuInput::ToggleWatchedFilter @watched_toggled, }, ToggleButton { set_icon_name: "eye-closed-symbolic", #[watch] #[block_signal(unwatched_toggled)] - set_active: model.watch_filter == Some(WatchFilter::OnlyUnwatched), + set_active: model.watched_filter == Some(WatchedFilter::OnlyUnwatched), connect_toggled => FilmCollationMenuInput::ToggleUnwatchedFilter @unwatched_toggled, }, }, @@ -140,7 +140,7 @@ impl SimpleComponent for FilmCollationMenu { let model = FilmCollationMenu { sorted_by: FilmsSorting::Name, sort_direction: SortingDirection::Ascending, - watch_filter: None, + watched_filter: None, }; let widgets = view_output!(); ComponentParts { model, widgets } @@ -159,7 +159,7 @@ impl SimpleComponent for FilmCollationMenu { FilmsSorting::ReleaseDate => SortingDirection::Descending, FilmsSorting::Runtime => SortingDirection::Ascending, }; - sender.emit_output(FilmCollationMenuOutput::SortedBy( + sender.emit_output(FilmCollationMenuOutput::SortBy( sorting, self.sort_direction, )); @@ -169,44 +169,42 @@ impl SimpleComponent for FilmCollationMenu { SortingDirection::Ascending => SortingDirection::Descending, SortingDirection::Descending => SortingDirection::Ascending, }; - sender.emit_output(FilmCollationMenuOutput::SortedBy( + sender.emit_output(FilmCollationMenuOutput::SortBy( self.sorted_by, self.sort_direction, )); } FilmCollationMenuInput::ToggleWatchedFilter => { - match self.watch_filter { - Some(WatchFilter::OnlyWatched) => { - self.watch_filter = None; + match self.watched_filter { + Some(WatchedFilter::OnlyWatched) => { + self.watched_filter = None; } _ => { - self.watch_filter = Some(WatchFilter::OnlyWatched); + self.watched_filter = Some(WatchedFilter::OnlyWatched); } } - sender.emit_output(FilmCollationMenuOutput::WatchFilterApplied( - self.watch_filter, + sender.emit_output(FilmCollationMenuOutput::ApplyWatchedFilter( + self.watched_filter, )); } FilmCollationMenuInput::ToggleUnwatchedFilter => { - match self.watch_filter { - Some(WatchFilter::OnlyUnwatched) => { - self.watch_filter = None; + match self.watched_filter { + Some(WatchedFilter::OnlyUnwatched) => { + self.watched_filter = None; } _ => { - self.watch_filter = Some(WatchFilter::OnlyUnwatched); + self.watched_filter = Some(WatchedFilter::OnlyUnwatched); } } - sender.emit_output(FilmCollationMenuOutput::WatchFilterApplied( - self.watch_filter, + sender.emit_output(FilmCollationMenuOutput::ApplyWatchedFilter( + self.watched_filter, )); } - FilmCollationMenuInput::SetNameFilter(name_filter) => { + FilmCollationMenuInput::NameFilterSet(name_filter) => { if name_filter.is_empty() { - sender.emit_output(FilmCollationMenuOutput::NameFilterApplied(None)); + sender.emit_output(FilmCollationMenuOutput::ApplyNameFilter(None)); } else { - sender.emit_output(FilmCollationMenuOutput::NameFilterApplied(Some( - name_filter, - ))); + sender.emit_output(FilmCollationMenuOutput::ApplyNameFilter(Some(name_filter))); } } } diff --git a/src/ui/components/media_collation_menu/series_collation_menu.rs b/src/ui/components/media_collation_menu/series_collation_menu.rs index 520c0ec..0a7eb5e 100644 --- a/src/ui/components/media_collation_menu/series_collation_menu.rs +++ b/src/ui/components/media_collation_menu/series_collation_menu.rs @@ -23,7 +23,7 @@ pub enum SeriesCollationMenuInput { #[derive(Debug)] pub enum SeriesCollationMenuOutput { - SortedBy(SeriesSorting, SortingDirection), + SortBy(SeriesSorting, SortingDirection), } #[component(pub)] @@ -122,7 +122,7 @@ impl SimpleComponent for SeriesCollationMenu { SeriesSorting::Name => SortingDirection::Ascending, SeriesSorting::FirstReleaseDate => SortingDirection::Descending, }; - sender.emit_output(SeriesCollationMenuOutput::SortedBy( + sender.emit_output(SeriesCollationMenuOutput::SortBy( sorting, self.sort_direction, )); @@ -132,7 +132,7 @@ impl SimpleComponent for SeriesCollationMenu { SortingDirection::Ascending => SortingDirection::Descending, SortingDirection::Descending => SortingDirection::Ascending, }; - sender.emit_output(SeriesCollationMenuOutput::SortedBy( + sender.emit_output(SeriesCollationMenuOutput::SortBy( self.sorted_by, self.sort_direction, )); diff --git a/src/ui/components/media_details/film_details.rs b/src/ui/components/media_details/film_details.rs index 586d3d2..0f71aad 100644 --- a/src/ui/components/media_details/film_details.rs +++ b/src/ui/components/media_details/film_details.rs @@ -15,22 +15,22 @@ pub struct FilmDetails { #[derive(Debug)] pub enum FilmDetailsInput { - ToggleWatchStatus, - ToggleDownloadStatus, + ToggleWatchedStatus, + ToggleDownloadedStatus, } #[derive(Debug)] pub enum FilmDetailsOutput { - WatchStatusChanged(bool), - DownloadStatusChanged(bool), + WatchedStatusChanged(bool), + DownloadedStatusChanged(bool), } #[derive(Debug)] pub enum FilmDetailsCmdOutput { - WatchStatusPersistSucceeded, - WatchStatusPersistFailed(DataManagerError), - DownloadStatusPersistSucceeded, - DownloadStatusPersistFailed(DataManagerError), + WatchedStatusPersistSucceeded, + WatchedStatusPersistFailed(DataManagerError), + DownloadedStatusPersistSucceeded, + DownloadedStatusPersistFailed(DataManagerError), } #[component(pub)] @@ -66,14 +66,14 @@ impl Component for FilmDetails { #[watch] set_label: if model.film_overview.watched { "Watched" } else { "Watch" }, set_active: model.film_overview.watched, - connect_toggled => FilmDetailsInput::ToggleWatchStatus, + connect_toggled => FilmDetailsInput::ToggleWatchedStatus, }, ToggleButton { #[watch] set_label: if model.film_overview.downloaded { "Downloaded" } else { "Mark downloaded" }, set_active: model.film_overview.downloaded, - connect_toggled => FilmDetailsInput::ToggleDownloadStatus, + connect_toggled => FilmDetailsInput::ToggleDownloadedStatus, }, }, @@ -108,10 +108,10 @@ impl Component for FilmDetails { _root: >k4::Box, ) { match message { - FilmDetailsInput::ToggleWatchStatus => { + FilmDetailsInput::ToggleWatchedStatus => { let watched = !self.film_overview.watched; self.film_overview.watched = watched; - sender.emit_output(FilmDetailsOutput::WatchStatusChanged(watched)); + sender.emit_output(FilmDetailsOutput::WatchedStatusChanged(watched)); sender.oneshot_command(clone!( #[strong(rename_to = uuid)] @@ -119,16 +119,16 @@ impl Component for FilmDetails { async move { let result = DataManager::set_film_watched_status(uuid.as_str(), watched).await; match result { - Ok(()) => FilmDetailsCmdOutput::WatchStatusPersistSucceeded, - Err(error) => FilmDetailsCmdOutput::WatchStatusPersistFailed(error), + Ok(()) => FilmDetailsCmdOutput::WatchedStatusPersistSucceeded, + Err(error) => FilmDetailsCmdOutput::WatchedStatusPersistFailed(error), } } )); } - FilmDetailsInput::ToggleDownloadStatus => { + FilmDetailsInput::ToggleDownloadedStatus => { let downloaded = !self.film_overview.downloaded; self.film_overview.downloaded = downloaded; - sender.emit_output(FilmDetailsOutput::DownloadStatusChanged(downloaded)); + sender.emit_output(FilmDetailsOutput::DownloadedStatusChanged(downloaded)); sender.oneshot_command(clone!( #[strong(rename_to = uuid)] @@ -136,8 +136,8 @@ impl Component for FilmDetails { async move { let result = DataManager::set_film_downloaded_status(uuid.as_str(), downloaded).await; match result { - Ok(()) => FilmDetailsCmdOutput::DownloadStatusPersistSucceeded, - Err(error) => FilmDetailsCmdOutput::DownloadStatusPersistFailed(error), + Ok(()) => FilmDetailsCmdOutput::DownloadedStatusPersistSucceeded, + Err(error) => FilmDetailsCmdOutput::DownloadedStatusPersistFailed(error), } } )); @@ -152,12 +152,12 @@ impl Component for FilmDetails { _root: >k4::Box, ) { match message { - FilmDetailsCmdOutput::WatchStatusPersistSucceeded => {} - FilmDetailsCmdOutput::WatchStatusPersistFailed(error) => { + FilmDetailsCmdOutput::WatchedStatusPersistSucceeded => {} + FilmDetailsCmdOutput::WatchedStatusPersistFailed(error) => { println!("Watched status persist failed: {error:?}"); } - FilmDetailsCmdOutput::DownloadStatusPersistSucceeded => {} - FilmDetailsCmdOutput::DownloadStatusPersistFailed(error) => { + FilmDetailsCmdOutput::DownloadedStatusPersistSucceeded => {} + FilmDetailsCmdOutput::DownloadedStatusPersistFailed(error) => { println!("Downloaded status persist failed: {error:?}"); } } diff --git a/src/ui/components/media_grid/film_grid.rs b/src/ui/components/media_grid/film_grid.rs index 3a8a505..5c7b52d 100644 --- a/src/ui/components/media_grid/film_grid.rs +++ b/src/ui/components/media_grid/film_grid.rs @@ -6,7 +6,7 @@ use relm4::{Component, ComponentParts, ComponentSender, component}; use crate::persist::data_manager::{DataManager, DataManagerError}; use crate::ui::components::media_grid_item::{FilmGridItem, FilmGridItemInput}; use crate::ui::factory_sorting::sort_factory_vec; -use crate::ui::filtering::WatchFilter; +use crate::ui::filtering::WatchedFilter; use crate::ui::sorting::{ FilmsSorting, SortingDirection, cmp_films_by_name, cmp_films_by_rel_date, cmp_films_by_runtime, }; @@ -16,14 +16,12 @@ use crate::views::overview::FilmOverview; pub struct FilmGrid { items: FactoryVecDeque, - watch_filter: Option, - name_filter: Option, } #[derive(Debug)] pub enum FilmGridInput { SortBy(FilmsSorting, SortingDirection), - ApplyWatchFilter(Option), + ApplyWatchedFilter(Option), ApplyNameFilter(Option), } @@ -66,11 +64,7 @@ impl Component for FilmGrid { let items = FactoryVecDeque::builder() .launch(widgets.grid.clone()) .detach(); - let model = FilmGrid { - items, - watch_filter: None, - name_filter: None, - }; + let model = FilmGrid { items }; sender.oneshot_command(async move { let overview = DataManager::films_overview().await; @@ -89,6 +83,8 @@ impl Component for FilmGrid { _sender: ComponentSender, _root: &ScrolledWindow, ) { + // TODO: Make combining filters work properly or, indeed, at all. + match message { FilmGridInput::SortBy(sorting, direction) => { let mut items = self.items.guard(); @@ -105,14 +101,49 @@ impl Component for FilmGrid { } }); } - FilmGridInput::ApplyWatchFilter(watch_filter) => { - self.watch_filter = watch_filter; - self.apply_filters(); - } - FilmGridInput::ApplyNameFilter(name_filter) => { - self.name_filter = name_filter; - self.apply_filters(); - } + FilmGridInput::ApplyWatchedFilter(watched_filter) => match watched_filter { + Some(WatchedFilter::OnlyWatched) => { + for (index, item) in self.items.iter().enumerate() { + if item.film().watched { + self.items.send(index, FilmGridItemInput::SetVisible(true)); + } else { + self.items.send(index, FilmGridItemInput::SetVisible(false)); + } + } + } + Some(WatchedFilter::OnlyUnwatched) => { + for (index, item) in self.items.iter().enumerate() { + if item.film().watched { + self.items.send(index, FilmGridItemInput::SetVisible(false)); + } else { + self.items.send(index, FilmGridItemInput::SetVisible(true)); + } + } + } + None => { + self.items.broadcast(FilmGridItemInput::SetVisible(true)); + } + }, + FilmGridInput::ApplyNameFilter(name_filter) => match name_filter { + Some(name_filter) => { + let name_filter = name_filter.to_lowercase(); + for (index, item) in self.items.iter().enumerate() { + let name = item.film().name.to_lowercase(); + let name_matches = name.contains(name_filter.as_str()); + let orig_name = item.film().original_name.as_deref().map(str::to_lowercase); + let orig_name_matches = + orig_name.is_some_and(|orig_name| orig_name.contains(name_filter.as_str())); + if name_matches || orig_name_matches { + self.items.send(index, FilmGridItemInput::SetVisible(true)); + } else { + self.items.send(index, FilmGridItemInput::SetVisible(false)); + } + } + } + None => { + self.items.broadcast(FilmGridItemInput::SetVisible(true)); + } + }, } } @@ -141,36 +172,3 @@ impl Component for FilmGrid { } } } - -impl FilmGrid { - fn apply_filters(&mut self) { - for (index, item) in self.items.iter().enumerate() { - let show_item = film_matches_watch_filter(item.film(), self.watch_filter) - && film_matches_name_filter(item.film(), self.name_filter.as_deref()); - self - .items - .send(index, FilmGridItemInput::SetVisible(show_item)); - } - } -} - -fn film_matches_watch_filter(film: &FilmOverview, filter: Option) -> bool { - if let Some(watch_filter) = filter { - match watch_filter { - WatchFilter::OnlyWatched => film.watched, - WatchFilter::OnlyUnwatched => !film.watched, - } - } else { - true - } -} - -fn film_matches_name_filter(film: &FilmOverview, filter: Option<&str>) -> bool { - if let Some(name_filter) = filter { - let name_filter = name_filter.to_lowercase(); - let name = film.name.to_lowercase(); - name.contains(name_filter.as_str()) - } else { - true - } -} diff --git a/src/ui/components/media_grid_item/film_grid_item.rs b/src/ui/components/media_grid_item/film_grid_item.rs index cb18520..d7b9a2c 100644 --- a/src/ui/components/media_grid_item/film_grid_item.rs +++ b/src/ui/components/media_grid_item/film_grid_item.rs @@ -28,15 +28,6 @@ impl FilmGridItem { } } -#[derive(Clone, Copy, Debug)] -pub enum FilmGridItemInput { - ClickOnItem, - CloseDetails, - ChangeWatchStatus(bool), - ChangeDownloadStatus(bool), - SetVisible(bool), -} - #[derive(Debug)] pub enum FilmGridItemCmdOutput { PosterReady(Texture), @@ -44,6 +35,15 @@ pub enum FilmGridItemCmdOutput { PosterFailed(DataManagerError), } +#[derive(Clone, Copy, Debug)] +pub enum FilmGridItemInput { + ItemClicked, + DetailsClosed, + WatchedStatusChanged(bool), + DownloadedStatusChanged(bool), + SetVisible(bool), +} + #[factory(pub)] impl FactoryComponent for FilmGridItem { type Init = FilmOverview; @@ -60,7 +60,7 @@ impl FactoryComponent for FilmGridItem { set_visible: self.visible, Button { - connect_clicked => FilmGridItemInput::ClickOnItem, + connect_clicked => FilmGridItemInput::ItemClicked, set_css_classes: &["flat", "media-grid-item"], gtk4::Box { @@ -150,7 +150,7 @@ impl FactoryComponent for FilmGridItem { }), #[watch] set_child: self.details.as_ref().map(ComponentController::widget), - connect_closed => FilmGridItemInput::CloseDetails, + connect_closed => FilmGridItemInput::DetailsClosed, }, } @@ -183,27 +183,27 @@ impl FactoryComponent for FilmGridItem { fn update(&mut self, message: FilmGridItemInput, sender: FactorySender) { match message { - FilmGridItemInput::ClickOnItem => { + FilmGridItemInput::ItemClicked => { let details_controller = FilmDetails::builder().launch(self.film.clone()).forward( sender.input_sender(), |film_details_output| match film_details_output { - FilmDetailsOutput::WatchStatusChanged(watched) => { - FilmGridItemInput::ChangeWatchStatus(watched) + FilmDetailsOutput::WatchedStatusChanged(watched) => { + FilmGridItemInput::WatchedStatusChanged(watched) } - FilmDetailsOutput::DownloadStatusChanged(downloaded) => { - FilmGridItemInput::ChangeDownloadStatus(downloaded) + FilmDetailsOutput::DownloadedStatusChanged(downloaded) => { + FilmGridItemInput::DownloadedStatusChanged(downloaded) } }, ); self.details = Some(details_controller); } - FilmGridItemInput::CloseDetails => { + FilmGridItemInput::DetailsClosed => { self.details = None; } - FilmGridItemInput::ChangeWatchStatus(watched) => { + FilmGridItemInput::WatchedStatusChanged(watched) => { self.film.watched = watched; } - FilmGridItemInput::ChangeDownloadStatus(downloaded) => { + FilmGridItemInput::DownloadedStatusChanged(downloaded) => { self.film.downloaded = downloaded; } FilmGridItemInput::SetVisible(visible) => { diff --git a/src/ui/components/media_grid_item/series_grid_item.rs b/src/ui/components/media_grid_item/series_grid_item.rs index f4df9db..0a9171f 100644 --- a/src/ui/components/media_grid_item/series_grid_item.rs +++ b/src/ui/components/media_grid_item/series_grid_item.rs @@ -27,12 +27,6 @@ impl SeriesGridItem { } } -#[derive(Debug)] -pub enum SeriesGridItemInput { - ClickOnItem, - CloseDetails, -} - #[derive(Debug)] pub enum SeriesGridItemCmdOutput { PosterReady(Texture), @@ -40,6 +34,12 @@ pub enum SeriesGridItemCmdOutput { PosterFailed(DataManagerError), } +#[derive(Debug)] +pub enum SeriesGridItemInput { + ItemClicked, + DetailsClosed, +} + #[factory(pub)] impl FactoryComponent for SeriesGridItem { type Init = SeriesOverview; @@ -53,7 +53,7 @@ impl FactoryComponent for SeriesGridItem { root = FlowBoxChild { set_focusable: false, Button { - connect_clicked => SeriesGridItemInput::ClickOnItem, + connect_clicked => SeriesGridItemInput::ItemClicked, set_css_classes: &["flat", "media-grid-item"], gtk4::Box { set_orientation: Orientation::Vertical, @@ -113,7 +113,7 @@ impl FactoryComponent for SeriesGridItem { }), #[watch] set_child: self.details.as_ref().map(ComponentController::widget), - connect_closed => SeriesGridItemInput::CloseDetails, + connect_closed => SeriesGridItemInput::DetailsClosed, }, } @@ -145,13 +145,13 @@ impl FactoryComponent for SeriesGridItem { fn update(&mut self, message: SeriesGridItemInput, _sender: FactorySender) { match message { - SeriesGridItemInput::ClickOnItem => { + SeriesGridItemInput::ItemClicked => { let details_controller = SeriesDetails::builder() .launch(self.series.clone()) .detach(); self.details = Some(details_controller); } - SeriesGridItemInput::CloseDetails => { + SeriesGridItemInput::DetailsClosed => { self.details = None; } } diff --git a/src/ui/filtering.rs b/src/ui/filtering.rs index 4067f72..8f00bf5 100644 --- a/src/ui/filtering.rs +++ b/src/ui/filtering.rs @@ -1,5 +1,5 @@ #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum WatchFilter { +pub enum WatchedFilter { OnlyWatched, OnlyUnwatched, }