use gtk4 :: * ; use gtk4 :: Align :: * ; use gtk4 :: Orientation :: * ; use gtk4 :: gdk :: * ; use gtk4 :: gio :: * ; use gtk4 :: glib :: * ; use gtk4 :: pango :: * ; use gtk4 :: pango :: Weight :: * ; use gtk4 :: prelude :: * ; use std :: cell :: * ; use std :: iter :: * ; use std :: path :: PathBuf ; use crate :: ui :: collatable_container :: * ; use crate :: ui :: component :: * ; pub struct CollatedMediaGrid < A : MediaAdapter > { media_widget_pairs : RefCell < Vec < ( A :: Overview , Button ) > > , grid_widget : FlowBox , on_media_selected : & 'static dyn Fn ( A :: Overview ) , } impl < A : MediaAdapter > CollatedMediaGrid { pub fn new ( on_media_selected : impl Fn ( A :: Overview ) + 'static ) -> Self { let grid_widget = flow_box ! ( @ orientation : Horizontal ; @ homogeneous : true ; @ selection_mode : SelectionMode :: None ; ) ; let media_widget_pairs = RefCell :: new ( Vec :: new () ) ; let on_media_selected = leak (on_media_selected) ; Self { media_widget_pairs , grid_widget , on_media_selected } } 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 media in media . as_slice () { widgets . push ( self . create_media_entry (media) . await ) ; } self . media_widget_pairs . replace ( zip ( media , widgets ) . collect () ) ; for ( _ , widget ) in self . sort_media_widget_pairs (sorting) { self . grid_widget . append ( & widget ) ; } } async fn create_media_entry ( & self , media : & A :: Overview ) -> Button { button ! ( @ css_classes : & [ "flat" , "open-collection-item-button" ] ; @ connect_clicked : clone ! ( # [strong] media , # [ strong ( rename_to = on_media_selected ) ] self . on_media_selected , move |_| on_media_selected ( media . clone () ) , ) ; & 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 () , 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 ; ) ; media . get_name () . as_str () , ) ) , media . get_original_name () . map ( |original_name| label ! ( @ justify : Justification :: Center ; @ wrap : true ; @ max_width_chars : 1 ; // Not the actual limit, used instead to wrap more aggressively original_name . as_str () , ) ) . as_ref () , Some ( & g_box ! ( @ option_children ; @ orientation : Horizontal ; @ halign : Center ; @ spacing : 20 ; Some ( & label ! ( media . get_release_date () . split ('-') . next () . unwrap () , ) ) , media . get_runtime_minutes () . map ( |runtime_minutes| label ! ( format ! ( "{}m" , runtime_minutes ) . as_str () ) , ) . as_ref () , ) ) , ) , ) } pub fn set_sorting ( & self , sorting : A :: Sorting ) { self . grid_widget . remove_all () ; for ( _ , widget ) in self . sort_media_widget_pairs (sorting) { self . grid_widget . append ( & widget ) ; } } fn sort_media_widget_pairs ( & self , sorting : A :: Sorting ) -> Vec < ( A :: Overview , Button ) > { let mut sorted = Vec :: from ( self . media_widget_pairs . borrow () . as_slice () , ) ; sorted . sort_by ( | ( media_1 , _ ) , ( media_2 , _ ) | A :: compare_by ( media_1 , media_2 , sorting ) , ) ; // See it, say it, ... sorted } } impl < A : MediaAdapter > Component for CollatedMediaGrid { fn get_widget ( & self ) -> & FlowBox { & self . grid_widget } }