Fully deduplicate shared logic between films and series

This commit is contained in:
Reinout Meliesie 2025-02-10 23:56:48 +01:00
commit c3e2bd0f69
Signed by: zedfrigg
GPG key ID: 3AFCC06481308BC6
10 changed files with 275 additions and 452 deletions

View file

@ -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 <A> {
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 <FilmOverview> , 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 <SeriesOverview> , 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 <FlowBox> for CollatedFilmsGrid {
fn get_widget ( & self ) -> & FlowBox { & self . grid_widget }
}
impl Component <FlowBox> for CollatedSeriesGrid {
impl < A : MediaAdapter > Component for CollatedMediaGrid <A> {
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 () ,
) ) ,
) ,
)
}