Fully deduplicate shared logic between films and series

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

View file

@ -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 ;
}

View file

@ -58,6 +58,15 @@ pub struct CollectionOverview {
pub series : Vec <SeriesOverview> ,
}
pub trait MediaOverview : Clone {
fn get_uuid ( & self ) -> String ;
fn get_name ( & self ) -> String ;
fn get_original_name ( & self ) -> Option <String> ;
fn get_release_date ( & self ) -> String ;
fn get_runtime_minutes ( & self ) -> Option <u32> ;
fn get_poster_file_path ( & self ) -> Option <PathBuf> ;
}
# [ derive (Clone) ] pub struct FilmOverview {
pub uuid : String ,
pub name : String ,
@ -74,6 +83,23 @@ pub struct CollectionOverview {
pub poster_file_path : Option <PathBuf> ,
}
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 <String> { self . original_name . clone () }
fn get_release_date ( & self ) -> String { self . release_date . clone () }
fn get_runtime_minutes ( & self ) -> Option <u32> { Some ( self . runtime_minutes ) }
fn get_poster_file_path ( & self ) -> Option <PathBuf> { 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 <String> { self . original_name . clone () }
fn get_release_date ( & self ) -> String { self . first_release_date . clone () }
fn get_runtime_minutes ( & self ) -> Option <u32> { None }
fn get_poster_file_path ( & self ) -> Option <PathBuf> { self . poster_file_path . clone () }
}
fn row_to_film_overview ( row : & Row ) -> rusqlite :: Result <FilmOverview> {
let uuid = row . get (0) ? ;
let name = row . get (1) ? ;

View file

@ -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 <HeaderBar> for ApplicationHeaderBar {
fn get_widget ( & self ) -> & HeaderBar { & self . widget }
}

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 () ,
) ) ,
) ,
)
}

View file

@ -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 :: <A> :: 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 <Box> for FilmCollationMenu {
fn get_widget ( & self ) -> & Box { & self . widget }
}
impl Component <Box> for SeriesCollationMenu {
impl Component for MediaCollationMenu {
fn get_widget ( & self ) -> & Box { & self . widget }
}

View file

@ -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 <FilmsSorting> ,
}
pub struct SeriesSortButton {
widget : SplitButton ,
previous_sorting : & 'static RefCell <SeriesSorting> ,
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 <A> {
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 :: <A> ( 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 <SplitButton> for FilmSortButton {
fn get_widget ( & self ) -> & SplitButton { & self . widget }
}
impl Component <SplitButton> for SeriesSortButton {
impl < A : MediaAdapter > Component for MediaSortButton <A> {
fn get_widget ( & self ) -> & SplitButton { & self . widget }
}
fn on_film_sort_activated < F : Fn (FilmsSorting) > (
row : & ListBoxRow ,
previous_sorting : & RefCell <FilmsSorting> ,
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 <SeriesSorting> ,
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) ;
}
}

View file

@ -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 <FilmProperty> 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 <SeriesProperty> 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 <A> ,
widget : Box ,
}
impl CollatableFilmsContainer {
impl < A : MediaAdapter > CollatableMediaContainer <A> {
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 :: <A> ( |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 <FilmOverview> ) {
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 <SeriesOverview> ) {
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 <Box> 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 <A> {
fn get_widget ( & self ) -> & Box { & self . widget }
}
impl Component <Box> 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" ) ,
] )
}
}

View file

@ -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 <ViewStack> for CollectionViewStack {
fn get_widget ( & self ) -> & ViewStack { & self . widget }
}

View file

@ -3,6 +3,6 @@ use gtk4 :: prelude :: * ;
pub trait Component < W : IsA <Widget> > {
fn get_widget ( & self ) -> & W ;
pub trait Component {
fn get_widget ( & self ) -> & impl IsA <Widget> ;
}

View file

@ -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 <FilmsAdapter> ,
series_component : CollatableMediaContainer <SeriesAdapter> ,
}
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 :: <FilmsAdapter> :: new () ;
let series_component = CollatableMediaContainer :: <SeriesAdapter> :: 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 ;
}
}