Fully deduplicate shared logic between films and series
This commit is contained in:
parent
c3dfa5b459
commit
c3e2bd0f69
10 changed files with 275 additions and 452 deletions
|
@ -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 ;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) ? ;
|
||||
|
|
|
@ -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 }
|
||||
}
|
|
@ -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 () ,
|
||||
) ) ,
|
||||
) ,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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) ;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" ) ,
|
||||
] )
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
|
@ -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> ;
|
||||
}
|
||||
|
|
|
@ -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 ;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue