diff --git a/src/application.css b/src/application.css index 5b26221..72679dd 100644 --- a/src/application.css +++ b/src/application.css @@ -1,15 +1,9 @@ /* TODO: Switch out CSS dynamically on `gtk-application-prefer-dark-theme` property change */ -#film-collation-menu row:selected { - background-color : rgb( 0 0 0 / 0.08 ) ; -} -#series-collation-menu row:selected { +.collation-menu row:selected { background-color : rgb( 0 0 0 / 0.08 ) ; } -#film-collation-menu row:not(:selected) image { - opacity : 0 ; -} -#series-collation-menu row:not(:selected) image { +.collation-menu row:not(:selected) image { opacity : 0 ; } diff --git a/src/data_manager.rs b/src/data_manager.rs index 8abe4fb..619461c 100644 --- a/src/data_manager.rs +++ b/src/data_manager.rs @@ -58,6 +58,15 @@ pub struct CollectionOverview { pub series : Vec , } +pub trait MediaOverview : Clone { + fn get_uuid ( & self ) -> String ; + fn get_name ( & self ) -> String ; + fn get_original_name ( & self ) -> Option ; + fn get_release_date ( & self ) -> String ; + fn get_runtime_minutes ( & self ) -> Option ; + fn get_poster_file_path ( & self ) -> Option ; +} + # [ derive (Clone) ] pub struct FilmOverview { pub uuid : String , pub name : String , @@ -74,6 +83,23 @@ pub struct CollectionOverview { pub poster_file_path : Option , } +impl MediaOverview for FilmOverview { + fn get_uuid ( & self ) -> String { self . uuid . clone () } + fn get_name ( & self ) -> String { self . name . clone () } + fn get_original_name ( & self ) -> Option { self . original_name . clone () } + fn get_release_date ( & self ) -> String { self . release_date . clone () } + fn get_runtime_minutes ( & self ) -> Option { Some ( self . runtime_minutes ) } + fn get_poster_file_path ( & self ) -> Option { self . poster_file_path . clone () } +} +impl MediaOverview for SeriesOverview { + fn get_uuid ( & self ) -> String { self . uuid . clone () } + fn get_name ( & self ) -> String { self . name . clone () } + fn get_original_name ( & self ) -> Option { self . original_name . clone () } + fn get_release_date ( & self ) -> String { self . first_release_date . clone () } + fn get_runtime_minutes ( & self ) -> Option { None } + fn get_poster_file_path ( & self ) -> Option { self . poster_file_path . clone () } +} + fn row_to_film_overview ( row : & Row ) -> rusqlite :: Result { let uuid = row . get (0) ? ; let name = row . get (1) ? ; diff --git a/src/ui/application_header_bar.rs b/src/ui/application_header_bar.rs deleted file mode 100644 index ce28054..0000000 --- a/src/ui/application_header_bar.rs +++ /dev/null @@ -1,23 +0,0 @@ -use libadwaita :: * ; -use libadwaita :: ViewSwitcherPolicy :: * ; - -use crate :: ui :: collection_view_stack :: * ; -use crate :: ui :: component :: * ; -use crate :: ui :: utility :: * ; - - - -pub struct ApplicationHeaderBar { widget : HeaderBar } - -impl ApplicationHeaderBar { - pub fn new ( collection_view_stack : & CollectionViewStack ) -> Self { - let widget = header_bar ! ( - & view_switcher ! ( @ policy : Wide ; collection_view_stack . get_widget () ) , - ) ; - Self { widget } - } -} - -impl Component for ApplicationHeaderBar { - fn get_widget ( & self ) -> & HeaderBar { & self . widget } -} diff --git a/src/ui/collatable_container/collated_grid.rs b/src/ui/collatable_container/collated_grid.rs index c7b34c4..822b8a3 100644 --- a/src/ui/collatable_container/collated_grid.rs +++ b/src/ui/collatable_container/collated_grid.rs @@ -8,7 +8,6 @@ use gtk4 :: pango :: Weight :: * ; use gtk4 :: prelude :: * ; use std :: cell :: * ; use std :: iter :: * ; -use std :: path :: Path ; use std :: path :: PathBuf ; use crate :: ui :: collatable_container :: * ; @@ -16,210 +15,120 @@ use crate :: ui :: component :: * ; -pub struct CollatedFilmsGrid { - film_widget_pairs : RefCell < Vec < ( FilmOverview , Button ) > > , - grid_widget : FlowBox , -} -pub struct CollatedSeriesGrid { - series_widget_pairs : RefCell < Vec < ( SeriesOverview , Button ) > > , +pub struct CollatedMediaGrid < A : MediaAdapter > { + media_widget_pairs : RefCell < Vec < ( A :: Overview , Button ) > > , grid_widget : FlowBox , } -impl CollatedFilmsGrid { +impl < A : MediaAdapter > CollatedMediaGrid { pub fn new () -> Self { let grid_widget = flow_box ! ( @ orientation : Horizontal ; @ homogeneous : true ; @ selection_mode : SelectionMode :: None ; ) ; - let film_widget_pairs = RefCell :: new ( vec ! () ) ; + let media_widget_pairs = RefCell :: new ( vec ! () ) ; - Self { film_widget_pairs , grid_widget } + Self { media_widget_pairs , grid_widget } } - pub async fn set_films ( & self , films : Vec , sorting : FilmsSorting ) { + pub async fn set_media ( & self , media : Vec < A :: Overview > , sorting : A :: Sorting ) { + // TODO: Check if we should use `MainContext :: invoke_local` here + let mut widgets = Vec :: new () ; - for film in films . as_slice () { - widgets . push ( create_film_entry (film) . await ) ; + for media in media . as_slice () { + widgets . push ( create_media_entry (media) . await ) ; } - self . film_widget_pairs . replace ( zip ( films , widgets ) . collect () ) ; + self . media_widget_pairs . replace ( zip ( media , widgets ) . collect () ) ; - for ( _ , film_widget ) in self . sort_film_widget_pairs (sorting) { - self . grid_widget . append ( & film_widget ) ; + for ( _ , widget ) in self . sort_media_widget_pairs (sorting) { + self . grid_widget . append ( & widget ) ; } } - pub fn set_sorting ( & self , sorting : FilmsSorting ) { + pub fn set_sorting ( & self , sorting : A :: Sorting ) { self . grid_widget . remove_all () ; - for ( _ , film_widget ) in self . sort_film_widget_pairs (sorting) { - self . grid_widget . append ( & film_widget ) ; + for ( _ , widget ) in self . sort_media_widget_pairs (sorting) { + self . grid_widget . append ( & widget ) ; } } - fn sort_film_widget_pairs ( & self , sorting : FilmsSorting ) -> Vec < ( FilmOverview , Button ) > { + fn sort_media_widget_pairs ( & self , sorting : A :: Sorting ) -> Vec < ( A :: Overview , Button ) > { let mut sorted = Vec :: from ( - self . film_widget_pairs . borrow () . as_slice () ) ; - - sorted . sort_by ( | ( film_1 , _ ) , ( film_2 , _ ) | match sorting . property { - FilmProperty :: Name => - film_1 . name . cmp ( & film_2 . name ) , - FilmProperty :: ReleaseDate => - film_1 . release_date . cmp ( & film_2 . release_date ) , - FilmProperty :: Runtime => - film_1 . runtime_minutes . cmp ( & film_2 . runtime_minutes ) , - } ) ; - if sorting . direction == SortingDirection :: Descending { sorted . reverse () } - - // See it, say it, ... - sorted - } -} -impl CollatedSeriesGrid { - pub fn new () -> Self { - let grid_widget = flow_box ! ( - @ orientation : Horizontal ; - @ homogeneous : true ; - @ selection_mode : SelectionMode :: None ; + self . media_widget_pairs . borrow () . as_slice () , ) ; - let series_widget_pairs = RefCell :: new ( vec ! () ) ; - Self { series_widget_pairs , grid_widget } - } - - pub async fn set_series ( & self , series : Vec , sorting : SeriesSorting ) { - let mut widgets = Vec :: new () ; - for series in series . as_slice () { - widgets . push ( create_series_entry (series) . await ) ; - } - self . series_widget_pairs . replace ( zip ( series , widgets ) . collect () ) ; - - for ( _ , series_widget ) in self . sort_series_widget_pairs (sorting) { - self . grid_widget . append ( & series_widget ) ; - } - } - - pub fn set_sorting ( & self , sorting : SeriesSorting ) { - self . grid_widget . remove_all () ; - - for ( _ , series_widget ) in self . sort_series_widget_pairs (sorting) { - self . grid_widget . append ( & series_widget ) ; - } - } - - fn sort_series_widget_pairs ( & self , sorting : SeriesSorting ) -> Vec < ( SeriesOverview , Button ) > { - let mut sorted = Vec :: from ( - self . series_widget_pairs . borrow () . as_slice () ) ; - - sorted . sort_by ( | ( series_1 , _ ) , ( series_2 , _ ) | match sorting . property { - SeriesProperty :: Name => - series_1 . name . cmp ( & series_2 . name ) , - SeriesProperty :: FirstReleaseDate => - series_1 . first_release_date . cmp ( & series_2 . first_release_date ) , - } ) ; - if sorting . direction == SortingDirection :: Descending { sorted . reverse () } + sorted . sort_by ( | ( media_1 , _ ) , ( media_2 , _ ) | + A :: compare_by ( media_1 , media_2 , sorting ) , + ) ; // See it, say it, ... sorted } } -impl Component for CollatedFilmsGrid { - fn get_widget ( & self ) -> & FlowBox { & self . grid_widget } -} -impl Component for CollatedSeriesGrid { +impl < A : MediaAdapter > Component for CollatedMediaGrid { fn get_widget ( & self ) -> & FlowBox { & self . grid_widget } } -async fn create_film_entry ( film : & FilmOverview ) -> Button { +async fn create_media_entry < M : MediaOverview > ( media : & M ) -> Button { button ! ( @ css_classes : & [ "flat" , "open-collection-item-button" ] ; @ connect_clicked : |_| todo ! () ; - & create_collection_item ( - film . name . as_str () , - film . original_name . as_deref () , - film . poster_file_path . as_deref () , - & create_film_details (film) , - ) . await , - ) -} -async fn create_series_entry ( series : & SeriesOverview ) -> Button { - button ! ( - @ css_classes : & [ "flat" , "open-collection-item-button" ] ; - @ connect_clicked : |_| todo ! () ; - & create_collection_item ( - series . name . as_str () , - series . original_name . as_deref () , - series . poster_file_path . as_deref () , - & create_series_details (series) , - ) . await , - ) -} + & g_box ! ( + @ option_children ; + @ orientation : Vertical ; + @ margin_top : 20 ; + @ margin_bottom : 20 ; -async fn create_collection_item ( - name : & str , - original_name : Option < & str > , - poster_file_path : Option < & Path > , - details_widget : & Box , -) -> Box { - g_box ! ( - @ option_children ; - @ orientation : Vertical ; - @ margin_top : 20 ; - @ margin_bottom : 20 ; + match media . get_poster_file_path () { + Some (poster_file_path) => { + let poster_file_path = PathBuf :: from (poster_file_path) ; // God forbid `Path` would work with `clone ! ()` + let poster_texture = spawn_blocking ( move || + Texture :: from_filename (poster_file_path) . unwrap () + ) . await . unwrap () ; + Some ( image ! ( + @ paintable : & poster_texture ; + @ width_request : 300 ; + @ height_request : 300 ; + @ margin_bottom : 10 ; + ) ) + } , + None => None , + } . as_ref () , - match poster_file_path { - Some (poster_file_path) => { - let poster_file_path = PathBuf :: from (poster_file_path) ; // God forbid `Path` would work with `clone ! ()` - let poster_texture = spawn_blocking ( move || - Texture :: from_filename (poster_file_path) . unwrap () - ) . await . unwrap () ; - Some ( image ! ( - @ paintable : & poster_texture ; - @ width_request : 300 ; - @ height_request : 300 ; - @ margin_bottom : 10 ; - ) ) - } , - None => None , - } . as_ref () , - - Some ( & label ! ( - @ justify : Justification :: Center ; - @ wrap : true ; - @ max_width_chars : 1 ; // Not the actual limit, used instead to wrap more aggressively - @ attributes : & pango_attributes ! ( @ scale : SCALE_LARGE ; @ weight : Bold ; ) ; - name , - ) ) , - - match original_name { - Some (original_name) => Some ( label ! ( + Some ( & label ! ( @ justify : Justification :: Center ; @ wrap : true ; @ max_width_chars : 1 ; // Not the actual limit, used instead to wrap more aggressively - original_name , + @ attributes : & pango_attributes ! ( @ scale : SCALE_LARGE ; @ weight : Bold ; ) ; + media . get_name () . as_str () , ) ) , - None => None , - } . as_ref () , - Some (details_widget) , - ) -} + match media . get_original_name () { + Some (original_name) => Some ( label ! ( + @ justify : Justification :: Center ; + @ wrap : true ; + @ max_width_chars : 1 ; // Not the actual limit, used instead to wrap more aggressively + original_name . as_str () , + ) ) , + None => None , + } . as_ref () , -fn create_film_details ( film : & FilmOverview ) -> Box { - g_box ! ( - @ orientation : Horizontal ; - @ halign : Center ; - @ spacing : 20 ; - & label ! ( film . release_date . as_str () ) , - & label ! ( format ! ( "{}m" , film . runtime_minutes ) . as_str () ) , - ) -} -fn create_series_details ( series : & SeriesOverview ) -> Box { - g_box ! ( - @ orientation : Horizontal ; - @ halign : Center ; - @ spacing : 20 ; - & label ! ( series . first_release_date . as_str () ) , + Some ( & g_box ! ( + @ option_children ; + @ orientation : Horizontal ; + @ halign : Center ; + @ spacing : 20 ; + Some ( & label ! ( media . get_release_date () . as_str () ) ) , + match media . get_runtime_minutes () { + Some (runtime_minutes) => Some ( + label ! ( format ! ( "{}m" , runtime_minutes ) . as_str () ) , + ) , + None => None , + } . as_ref () , + ) ) , + ) , ) } diff --git a/src/ui/collatable_container/collation_menu/mod.rs b/src/ui/collatable_container/collation_menu/mod.rs index a0cbffb..414373b 100644 --- a/src/ui/collatable_container/collation_menu/mod.rs +++ b/src/ui/collatable_container/collation_menu/mod.rs @@ -13,45 +13,24 @@ use crate :: ui :: collatable_container :: collation_menu :: sort_button :: * ; -pub struct FilmCollationMenu { widget : Box } -pub struct SeriesCollationMenu { widget : Box } +pub struct MediaCollationMenu { widget : Box } -impl FilmCollationMenu { - pub fn new < F : Fn (FilmsSorting) + 'static > ( on_sort : F ) -> Self { - let film_sort_button = FilmSortButton :: new (on_sort) ; +impl MediaCollationMenu { + pub fn new < A : MediaAdapter + 'static > ( on_sort : impl Fn ( A :: Sorting ) + 'static ) -> Self { + let sort_button = MediaSortButton :: :: new (on_sort) ; let widget = g_box ! ( @ orientation : Horizontal ; @ halign : Center ; @ spacing : 20 ; - @ widget_name : "film-collation-menu" ; - @ css_classes : & [ "toolbar" ] ; - film_sort_button . get_widget () , - ) ; - - Self { widget } - } -} -impl SeriesCollationMenu { - pub fn new < F : Fn (SeriesSorting) + 'static > ( on_sort : F ) -> Self { - let series_sort_button = SeriesSortButton :: new (on_sort) ; - - let widget = g_box ! ( - @ orientation : Horizontal ; - @ halign : Center ; - @ spacing : 20 ; - @ widget_name : "series-collation-menu" ; - @ css_classes : & [ "toolbar" ] ; - series_sort_button . get_widget () , + @ css_classes : & [ "toolbar" , "collation-menu" ] ; + sort_button . get_widget () , ) ; Self { widget } } } -impl Component for FilmCollationMenu { - fn get_widget ( & self ) -> & Box { & self . widget } -} -impl Component for SeriesCollationMenu { +impl Component for MediaCollationMenu { fn get_widget ( & self ) -> & Box { & self . widget } } diff --git a/src/ui/collatable_container/collation_menu/sort_button.rs b/src/ui/collatable_container/collation_menu/sort_button.rs index b450daa..0b83430 100644 --- a/src/ui/collatable_container/collation_menu/sort_button.rs +++ b/src/ui/collatable_container/collation_menu/sort_button.rs @@ -1,5 +1,4 @@ use gtk4 :: Image ; -use gtk4 :: ListBoxRow ; use gtk4 :: Align :: * ; use libadwaita :: * ; use std :: cell :: * ; @@ -11,82 +10,37 @@ use crate :: ui :: collatable_container :: SortingDirection :: * ; -pub struct FilmSortButton { +pub struct MediaSortButton < A : MediaAdapter + 'static > { widget : SplitButton , - previous_sorting : & 'static RefCell , -} -pub struct SeriesSortButton { - widget : SplitButton , - previous_sorting : & 'static RefCell , + previous_sorting : & 'static RefCell < A :: Sorting > , } -impl FilmSortButton { - pub fn new < F : Fn (FilmsSorting) + 'static > ( on_sort : F ) -> Self { - let previous_sorting = leak ( RefCell :: new ( FilmsSorting :: default () ) ) ; +impl < A : MediaAdapter > MediaSortButton { + pub fn new ( on_sort : impl Fn ( A :: Sorting ) + 'static ) -> Self { + let previous_sorting = leak ( RefCell :: new ( A :: Sorting :: default () ) ) ; + let property_descriptions = A :: get_property_descriptions () ; - let sort_icons = leak ( [ - image ! ( @ icon_name : "view-sort-ascending-symbolic" ; ) , - image ! ( @ icon_name : "view-sort-ascending-symbolic" ; ) , - image ! ( @ icon_name : "view-sort-ascending-symbolic" ; ) , - ] ) ; + let sort_icons = { + let mut sort_icons = Vec :: new () ; + for _ in property_descriptions { + sort_icons . push ( image ! ( @ icon_name : "view-sort-ascending-symbolic" ; ) ) ; + } + Box :: leak ( sort_icons . into_boxed_slice () ) as & 'static _ + } ; - let widget = split_button ! ( - @ popover : & popover ! ( - @ css_classes : & [ "menu" ] ; - & list_box ! ( - @ connect_row_activated : move | _ , row | { - on_film_sort_activated ( row , previous_sorting , & on_sort , sort_icons ) ; - } ; - & g_box ! ( - @ orientation : Horizontal ; @ spacing : 20 ; - & label ! ( @ hexpand : true ; @ halign : Start ; "Name" ) , - & sort_icons [0] , - ) , - & g_box ! ( - @ orientation : Horizontal ; @ spacing : 20 ; - & label ! ( @ hexpand : true ; @ halign : Start ; "Release date" ) , - & sort_icons [1] , - ) , - & g_box ! ( - @ orientation : Horizontal ; @ spacing : 20 ; - & label ! ( @ hexpand : true ; @ halign : Start ; "Runtime" ) , - & sort_icons [2] , - ) , - ) , - ) ; - & label ! ("Sort") , + let list_box = list_box ! ( @ connect_row_activated : move | _ , row | + on_media_sort_activated :: ( row . index () , previous_sorting , & on_sort , sort_icons ) ; ) ; - - Self { widget , previous_sorting } - } -} -impl SeriesSortButton { - pub fn new < F : Fn (SeriesSorting) + 'static > ( on_sort : F ) -> Self { - let previous_sorting = leak ( RefCell :: new ( SeriesSorting :: default () ) ) ; - - let sort_icons = leak ( [ - image ! ( @ icon_name : "view-sort-ascending-symbolic" ; ) , - image ! ( @ icon_name : "view-sort-ascending-symbolic" ; ) , - ] ) ; + for ( index , ( _ , description ) ) in property_descriptions . iter () . enumerate () { + list_box . append ( & g_box ! ( + @ orientation : Horizontal ; @ spacing : 20 ; + & label ! ( @ hexpand : true ; @ halign : Start ; description ) , + & sort_icons [index] , + ) ) ; + } let widget = split_button ! ( - @ popover : & popover ! ( - @ css_classes : & [ "menu" ] ; - & list_box ! ( - @ connect_row_activated : move | _ , row | - on_series_sort_activated ( row , previous_sorting , & on_sort , sort_icons ) ; - & g_box ! ( - @ orientation : Horizontal ; @ spacing : 20 ; - & label ! ( @ hexpand : true ; @ halign : Start ; "Name" ) , - & sort_icons [0] , - ) , - & g_box ! ( - @ orientation : Horizontal ; @ spacing : 20 ; - & label ! ( @ hexpand : true ; @ halign : Start ; "First release date" ) , - & sort_icons [1] , - ) , - ) , - ) ; + @ popover : & popover ! ( @ css_classes : & [ "menu" ] ; & list_box ) ; & label ! ("Sort") , ) ; @@ -94,87 +48,38 @@ impl SeriesSortButton { } } -impl Component for FilmSortButton { - fn get_widget ( & self ) -> & SplitButton { & self . widget } -} -impl Component for SeriesSortButton { +impl < A : MediaAdapter > Component for MediaSortButton { fn get_widget ( & self ) -> & SplitButton { & self . widget } } -fn on_film_sort_activated < F : Fn (FilmsSorting) > ( - row : & ListBoxRow , - previous_sorting : & RefCell , - on_sort : & F , +fn on_media_sort_activated < A : MediaAdapter + 'static > ( + row : i32 , + previous_sorting_mut : & RefCell < A :: Sorting > , + on_sort : & impl Fn ( A :: Sorting ) , sort_icons : & [ Image ] , ) { - let sorting_property = match row . index () { - 0 => FilmProperty :: Name , - 1 => FilmProperty :: ReleaseDate , - 2 => FilmProperty :: Runtime , - _ => panic ! () , - } ; + let ( sorting_property , _ ) = A :: get_property_descriptions () [ row as usize ] . clone () ; // TODO: Bounds checking - if sorting_property == previous_sorting . borrow () . property { - let previous_sorting_direction = previous_sorting . borrow () . direction ; - match previous_sorting_direction { + let previous_sorting = * previous_sorting_mut . borrow () ; + if sorting_property == previous_sorting . get_property () { + match previous_sorting . get_direction () { Ascending => { - previous_sorting . replace ( FilmsSorting :: new ( sorting_property , Descending ) ) ; - for icon in sort_icons { - icon . set_icon_name ( Some ("view-sort-descending-symbolic") ) ; - } - on_sort ( FilmsSorting :: new ( sorting_property , Descending ) ) ; + let new_sorting = A :: Sorting :: new ( sorting_property , Descending ) ; + previous_sorting_mut . replace (new_sorting) ; + sort_icons [ row as usize ] . set_icon_name ( Some ("view-sort-descending-symbolic") ) ; + on_sort (new_sorting) ; } , Descending => { - previous_sorting . replace ( FilmsSorting :: new ( sorting_property , Ascending ) ) ; - for icon in sort_icons { - icon . set_icon_name ( Some ("view-sort-ascending-symbolic") ) ; - } - on_sort ( FilmsSorting :: new ( sorting_property , Ascending ) ) ; + let new_sorting = A :: Sorting :: new ( sorting_property , Ascending ) ; + previous_sorting_mut . replace (new_sorting) ; + sort_icons [ row as usize ] . set_icon_name ( Some ("view-sort-ascending-symbolic") ) ; + on_sort (new_sorting) ; } , } } else { - previous_sorting . replace ( FilmsSorting :: new ( sorting_property , Ascending ) ) ; - for icon in sort_icons { - icon . set_icon_name ( Some ("view-sort-ascending-symbolic") ) ; - } - on_sort ( FilmsSorting :: new ( sorting_property , Ascending ) ) ; - } -} -fn on_series_sort_activated < F : Fn (SeriesSorting) > ( - row : & ListBoxRow , - previous_sorting : & RefCell , - on_sort : & F , - sort_icons : & [ Image ] , -) { - let sorting_property = match row . index () { - 0 => SeriesProperty :: Name , - 1 => SeriesProperty :: FirstReleaseDate , - _ => panic ! () , - } ; - - if sorting_property == previous_sorting . borrow () . property { - let previous_sorting_direction = previous_sorting . borrow () . direction ; - match previous_sorting_direction { - Ascending => { - previous_sorting . replace ( SeriesSorting :: new ( sorting_property , Descending ) ) ; - for icon in sort_icons { - icon . set_icon_name ( Some ("view-sort-descending-symbolic") ) ; - } - on_sort ( SeriesSorting :: new ( sorting_property , Descending ) ) ; - } , - Descending => { - previous_sorting . replace ( SeriesSorting :: new ( sorting_property , Ascending ) ) ; - for icon in sort_icons { - icon . set_icon_name ( Some ("view-sort-ascending-symbolic") ) ; - } - on_sort ( SeriesSorting :: new ( sorting_property , Ascending ) ) ; - } , - } - } else { - previous_sorting . replace ( SeriesSorting :: new ( sorting_property , Ascending ) ) ; - for icon in sort_icons { - icon . set_icon_name ( Some ("view-sort-ascending-symbolic") ) ; - } - on_sort ( SeriesSorting :: new ( sorting_property , Ascending ) ) ; + let new_sorting = A :: Sorting :: new ( sorting_property , Ascending ) ; + previous_sorting_mut . replace (new_sorting) ; + sort_icons [ row as usize ] . set_icon_name ( Some ("view-sort-ascending-symbolic") ) ; + on_sort (new_sorting) ; } } diff --git a/src/ui/collatable_container/mod.rs b/src/ui/collatable_container/mod.rs index 549dde3..cf7b36d 100644 --- a/src/ui/collatable_container/mod.rs +++ b/src/ui/collatable_container/mod.rs @@ -1,9 +1,11 @@ mod collated_grid ; mod collation_menu ; -use gtk4 :: * ; +use gtk4 :: Box ; use gtk4 :: Orientation :: * ; use gtk4 :: prelude :: * ; +use std :: cmp :: * ; +use std :: fmt :: * ; use crate :: data_manager :: * ; use crate :: ui :: component :: * ; @@ -14,28 +16,40 @@ use crate :: utility :: * ; -# [ derive ( Clone , Copy , PartialEq ) ] pub enum FilmProperty { Name , ReleaseDate , Runtime } -# [ derive ( Clone , Copy , PartialEq ) ] pub enum SeriesProperty { Name , FirstReleaseDate } -# [ derive ( Clone , Copy , PartialEq ) ] pub enum SortingDirection { Ascending , Descending } +pub trait MediaSorting < P : MediaProperty > : Clone + Copy + Debug + Default { + fn new ( property : P , direction : SortingDirection ) -> Self ; + fn get_property ( & self ) -> P ; + fn get_direction ( & self ) -> SortingDirection ; +} -# [ derive ( Clone , Copy ) ] pub struct FilmsSorting { +pub trait MediaProperty : Clone + Copy + Debug + PartialEq {} + +# [ derive ( Clone , Copy , Debug , PartialEq ) ] pub enum FilmProperty { Name , ReleaseDate , Runtime } +# [ derive ( Clone , Copy , Debug , PartialEq ) ] pub enum SeriesProperty { Name , FirstReleaseDate } +# [ derive ( Clone , Copy , Debug , PartialEq ) ] pub enum SortingDirection { Ascending , Descending } + +# [ derive ( Clone , Copy , Debug ) ] pub struct FilmsSorting { property : FilmProperty , direction : SortingDirection , } -# [ derive ( Clone , Copy ) ] pub struct SeriesSorting { +# [ derive ( Clone , Copy , Debug ) ] pub struct SeriesSorting { property : SeriesProperty , direction : SortingDirection , } -impl FilmsSorting { - pub fn new ( property : FilmProperty , direction : SortingDirection ) -> Self { +impl MediaSorting for FilmsSorting { + fn new ( property : FilmProperty , direction : SortingDirection ) -> Self { Self { property , direction } } + fn get_property ( & self ) -> FilmProperty { self . property } + fn get_direction ( & self ) -> SortingDirection { self . direction } } -impl SeriesSorting { - pub fn new ( property : SeriesProperty , direction : SortingDirection ) -> Self { +impl MediaSorting for SeriesSorting { + fn new ( property : SeriesProperty , direction : SortingDirection ) -> Self { Self { property , direction } } + fn get_property ( & self ) -> SeriesProperty { self . property } + fn get_direction ( & self ) -> SortingDirection { self . direction } } impl Default for FilmsSorting { @@ -51,65 +65,110 @@ impl Default for SeriesSorting { } } } +impl MediaProperty for FilmProperty {} +impl MediaProperty for SeriesProperty {} - -pub struct CollatableFilmsContainer { - collated_grid : & 'static CollatedFilmsGrid , - widget : Box , -} -pub struct CollatableSeriesContainer { - collated_grid : & 'static CollatedSeriesGrid , +pub struct CollatableMediaContainer < A : MediaAdapter + 'static > { + collated_grid : & 'static CollatedMediaGrid , widget : Box , } -impl CollatableFilmsContainer { +impl < A : MediaAdapter > CollatableMediaContainer { pub fn new () -> Self { - let collated_grid = leak ( CollatedFilmsGrid :: new () ) ; - let film_collation_menu = FilmCollationMenu :: new ( |sorting| - collated_grid . set_sorting (sorting) ) ; + let collated_grid = leak ( CollatedMediaGrid :: new () ) ; + let collation_menu = MediaCollationMenu :: new :: ( |sorting| + collated_grid . set_sorting (sorting) , + ) ; let widget = g_box ! ( @ orientation : Vertical ; - film_collation_menu . get_widget () , + collation_menu . get_widget () , & scrolled_window ! ( @ propagate_natural_height : true ; & vertically_filling ! ( collated_grid . get_widget () ) , ) , ) ; - Self { collated_grid , widget } + Self { collated_grid, widget } } - pub async fn set_films ( & self , films : Vec ) { - self . collated_grid . set_films ( films , FilmsSorting :: default () ) . await ; - } -} -impl CollatableSeriesContainer { - pub fn new () -> Self { - let collated_grid = leak ( CollatedSeriesGrid :: new () ) ; - let series_collation_menu = SeriesCollationMenu :: new ( |sorting| - collated_grid . set_sorting (sorting) ) ; - - let widget = g_box ! ( - @ orientation : Vertical ; - series_collation_menu . get_widget () , - & scrolled_window ! ( - @ propagate_natural_height : true ; - & vertically_filling ! ( collated_grid . get_widget () ) , - ) , - ) ; - - Self { collated_grid , widget } - } - - pub async fn set_series ( & self , series : Vec ) { - self . collated_grid . set_series ( series , SeriesSorting :: default () ) . await ; + pub async fn set_media ( & self , media : Vec < A :: Overview > ) { + self . collated_grid . set_media ( media , A :: Sorting :: default () ) . await ; } } -impl Component for CollatableFilmsContainer { +pub trait MediaAdapter { + type Overview : MediaOverview ; + type Sorting : MediaSorting < Self :: Property > ; + type Property : MediaProperty ; + fn compare_by ( + media_1 : & Self :: Overview , + media_2 : & Self :: Overview , + sorting : Self :: Sorting , + ) -> Ordering ; + fn get_property_descriptions () -> & 'static [ ( Self :: Property , & 'static str ) ] ; +} + +impl < A : MediaAdapter > Component for CollatableMediaContainer { fn get_widget ( & self ) -> & Box { & self . widget } } -impl Component for CollatableSeriesContainer { - fn get_widget ( & self ) -> & Box { & self . widget } + +pub struct FilmsAdapter {} +pub struct SeriesAdapter {} + +impl MediaAdapter for FilmsAdapter { + type Overview = FilmOverview ; + type Sorting = FilmsSorting ; + type Property = FilmProperty ; + + fn compare_by ( + film_1 : & FilmOverview , + film_2 : & FilmOverview , + sorting : FilmsSorting , + ) -> Ordering { + let ordering = match sorting . property { + FilmProperty :: Name => film_1 . name . cmp ( & film_2 . name ) , + FilmProperty :: ReleaseDate => film_1 . release_date . cmp ( & film_2 . release_date ) , + FilmProperty :: Runtime => film_1 . runtime_minutes . cmp ( & film_2 . runtime_minutes ) , + } ; + match sorting . direction { + SortingDirection :: Ascending => ordering , + SortingDirection :: Descending => ordering . reverse () , + } + } + + fn get_property_descriptions () -> & 'static [ ( FilmProperty , & 'static str ) ] { + leak ( [ + ( FilmProperty :: Name , "Name" ) , + ( FilmProperty :: ReleaseDate , "Release date" ) , + ( FilmProperty :: Runtime , "Runtime" ) , + ] ) + } +} +impl MediaAdapter for SeriesAdapter { + type Overview = SeriesOverview ; + type Sorting = SeriesSorting ; + type Property = SeriesProperty ; + + fn compare_by ( + series_1 : & SeriesOverview , + series_2 : & SeriesOverview , + sorting : SeriesSorting , + ) -> Ordering { + let ordering = match sorting . property { + SeriesProperty :: Name => series_1 . name . cmp ( & series_2 . name ) , + SeriesProperty :: FirstReleaseDate => series_1 . first_release_date . cmp ( & series_2 . first_release_date ) , + } ; + match sorting . direction { + SortingDirection :: Ascending => ordering , + SortingDirection :: Descending => ordering . reverse () , + } + } + + fn get_property_descriptions () -> & 'static [ ( SeriesProperty , & 'static str ) ] { + leak ( [ + ( SeriesProperty :: Name , "Name" ) , + ( SeriesProperty :: FirstReleaseDate , "First release date" ) , + ] ) + } } diff --git a/src/ui/collection_view_stack.rs b/src/ui/collection_view_stack.rs deleted file mode 100644 index 2fe2496..0000000 --- a/src/ui/collection_view_stack.rs +++ /dev/null @@ -1,27 +0,0 @@ -use libadwaita :: * ; - -use crate :: ui :: collatable_container :: * ; -use crate :: ui :: component :: * ; -use crate :: ui :: utility :: * ; - - - -pub struct CollectionViewStack { widget : ViewStack } - -impl CollectionViewStack { - pub fn new ( - films_container : & CollatableFilmsContainer , - series_container : & CollatableSeriesContainer , - ) -> Self { - let widget = view_stack ! ( - ( "Films" , "camera-video-symbolic" , films_container . get_widget () ) , - ( "Series" , "video-display-symbolic" , series_container . get_widget () ) , - ) ; - - Self { widget } - } -} - -impl Component for CollectionViewStack { - fn get_widget ( & self ) -> & ViewStack { & self . widget } -} diff --git a/src/ui/component.rs b/src/ui/component.rs index a7fda8f..27c6468 100644 --- a/src/ui/component.rs +++ b/src/ui/component.rs @@ -3,6 +3,6 @@ use gtk4 :: prelude :: * ; -pub trait Component < W : IsA > { - fn get_widget ( & self ) -> & W ; +pub trait Component { + fn get_widget ( & self ) -> & impl IsA ; } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 9bef1c7..2a1a0fe 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,17 +1,14 @@ -mod application_header_bar ; mod collatable_container ; -mod collection_view_stack ; mod component ; mod utility ; use gtk4 :: Orientation :: * ; use gtk4 :: prelude :: * ; use libadwaita :: * ; +use libadwaita :: ViewSwitcherPolicy :: * ; use crate :: data_manager :: * ; -use crate :: ui :: application_header_bar :: * ; use crate :: ui :: collatable_container :: * ; -use crate :: ui :: collection_view_stack :: * ; use crate :: ui :: component :: * ; use crate :: ui :: utility :: * ; @@ -19,25 +16,29 @@ use crate :: ui :: utility :: * ; pub struct UI { window : ApplicationWindow , - films_component : CollatableFilmsContainer , - series_component : CollatableSeriesContainer , + films_component : CollatableMediaContainer , + series_component : CollatableMediaContainer , } impl UI { pub fn new ( application : & Application ) -> UI { - let films_component = CollatableFilmsContainer :: new () ; - let series_component = CollatableSeriesContainer :: new () ; - let switch_component = CollectionViewStack :: new ( - & films_component , & series_component ) ; - let header_bar = ApplicationHeaderBar :: new ( & switch_component ) ; + let films_component = CollatableMediaContainer :: :: new () ; + let series_component = CollatableMediaContainer :: :: new () ; + let switcher = view_stack ! ( + ( "Films" , "camera-video-symbolic" , films_component . get_widget () ) , + ( "Series" , "video-display-symbolic" , series_component . get_widget () ) , + ) ; + let header_bar = header_bar ! ( + & view_switcher ! ( @ policy : Wide ; & switcher ) , + ) ; let window = application_window ! ( @ application : application ; @ title : "Zoƶdex" ; & g_box ! ( @ orientation : Vertical ; - header_bar . get_widget () , - switch_component . get_widget () , + & header_bar , + & switcher , ) , ) ; @@ -50,7 +51,7 @@ impl UI { pub async fn render_collection_overview ( & self , collection : CollectionOverview ) { // TODO: Find a way to await these futures concurrently - self . films_component . set_films ( collection . films ) . await ; - self . series_component . set_series ( collection . series ) . await ; + self . films_component . set_media ( collection . films ) . await ; + self . series_component . set_media ( collection . series ) . await ; } }