Use cargo fmt and conform to Style Guide (mostly)

This commit is contained in:
Reinout Meliesie 2026-01-08 14:00:01 +01:00
commit 2982a13bc2
Signed by: zedfrigg
GPG key ID: 3AFCC06481308BC6
15 changed files with 980 additions and 888 deletions

View file

@ -1,9 +0,0 @@
root = true
[*.rs]
charset = utf-8
indent_style = tab
tab_width = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

18
rustfmt.toml Normal file
View file

@ -0,0 +1,18 @@
edition = "2024"
style_edition = "2024"
# Modifications to the standard style
blank_lines_upper_bound = 3
tab_spaces = 2
use_field_init_shorthand = true
# Stricter subset of standard style
condense_wildcard_suffixes = true
format_macro_matchers = true
group_imports = "StdExternalCrate"
hex_literal_case = "Upper"
imports_granularity = "Module"
newline_style = "Unix"
normalize_comments = true
normalize_doc_attributes = true
wrap_comments = true

View file

@ -1,16 +1,12 @@
use async_sqlite :: * ; use std::env::var_os;
use async_sqlite :: Error :: * ; use std::path::PathBuf;
use async_sqlite :: rusqlite :: OpenFlags ;
use async_sqlite :: rusqlite :: Row ;
use async_sqlite :: rusqlite :: Error :: * ;
use async_sqlite :: rusqlite :: ffi :: ErrorCode :: * ;
use fallible_iterator :: * ;
use std :: env :: * ;
use std :: path :: * ;
use crate :: error :: * ; use async_sqlite::rusqlite::{OpenFlags, Row};
use crate :: error :: ZoodexError :: * ; use async_sqlite::{Client, ClientBuilder, rusqlite};
use crate :: utility :: * ; use fallible_iterator::FallibleIterator;
use crate::error::{Result, ZoodexError};
use crate::utility::concat_os_str;
@ -48,37 +44,47 @@ impl DataManager {
} }
pub async fn get_collection_overview(&self) -> Result<CollectionOverview> { pub async fn get_collection_overview(&self) -> Result<CollectionOverview> {
let collection_overview = self . sqlite_client_shared . conn ( |sqlite_connection| { let collection_overview = self
.sqlite_client_shared
.conn(|sqlite_connection| {
let films = sqlite_connection let films = sqlite_connection
. prepare ( " .prepare(
"
select uuid , name , original_name , release_date , runtime_minutes select uuid , name , original_name , release_date , runtime_minutes
from films from films
" ) ? ",
)?
.query(())? .query(())?
.map(row_to_film_overview) .map(row_to_film_overview)
.collect()?; .collect()?;
let series = sqlite_connection let series = sqlite_connection
. prepare ( " .prepare(
"
select series . uuid , series . name , series . original_name , select series . uuid , series . name , series . original_name ,
min ( episodes . release_date ) min ( episodes . release_date )
from series , seasons , episodes from series , seasons , episodes
where series . uuid = seasons . series and seasons . uuid = episodes . season where series . uuid = seasons . series and seasons . uuid = episodes . season
group by series . uuid group by series . uuid
" ) ? ",
)?
.query(())? .query(())?
.map(row_to_series_overview) .map(row_to_series_overview)
.collect()?; .collect()?;
Ok(CollectionOverview { films, series }) Ok(CollectionOverview { films, series })
} ) . await ? ; })
.await?;
Ok(collection_overview) Ok(collection_overview)
} }
pub async fn get_film_details(&self, uuid: String) -> Result<FilmDetails> { pub async fn get_film_details(&self, uuid: String) -> Result<FilmDetails> {
let film_details = self . sqlite_client_shared . conn ( |sqlite_connection| { let film_details = self
.sqlite_client_shared
.conn(|sqlite_connection| {
let film_details = sqlite_connection let film_details = sqlite_connection
. prepare ( " .prepare(
"
select select
films . uuid , films . uuid ,
films . name , films . name ,
@ -93,13 +99,15 @@ impl DataManager {
from films left join sources from films left join sources
on films . uuid = sources . media_uuid on films . uuid = sources . media_uuid
where films . uuid = (?1) where films . uuid = (?1)
" ) ? ",
)?
.query([uuid])? .query([uuid])?
.map(row_to_film_details) .map(row_to_film_details)
.next()? .next()?
.unwrap(); .unwrap();
Ok(film_details) Ok(film_details)
} ) . await ? ; })
.await?;
Ok(film_details) Ok(film_details)
} }
} }
@ -119,33 +127,57 @@ pub trait MediaOverview : Clone {
fn get_runtime_minutes(&self) -> Option<u32>; fn get_runtime_minutes(&self) -> Option<u32>;
} }
# [ derive (Clone) ] pub struct FilmOverview { #[derive(Clone)]
pub struct FilmOverview {
pub uuid: String, pub uuid: String,
pub name: String, pub name: String,
pub original_name: Option<String>, pub original_name: Option<String>,
pub release_date : String , // TODO: Switch to chrono types, I think rusqlite has crate option for it // TODO: Switch to chrono types, I think rusqlite has crate option for it
pub release_date: String,
pub runtime_minutes: u32, pub runtime_minutes: u32,
} }
# [ derive (Clone) ] pub struct SeriesOverview { #[derive(Clone)]
pub struct SeriesOverview {
pub uuid: String, pub uuid: String,
pub name: String, pub name: String,
pub original_name: Option<String>, pub original_name: Option<String>,
pub first_release_date : String , // TODO: Switch to chrono types, I think rusqlite has crate option for it // TODO: Switch to chrono types, I think rusqlite has crate option for it
pub first_release_date: String,
} }
impl MediaOverview for FilmOverview { impl MediaOverview for FilmOverview {
fn get_uuid ( & self ) -> String { self . uuid . clone () } fn get_uuid(&self) -> String {
fn get_name ( & self ) -> String { self . name . clone () } self.uuid.clone()
fn get_original_name ( & self ) -> Option <String> { self . original_name . clone () } }
fn get_release_date ( & self ) -> String { self . release_date . clone () } fn get_name(&self) -> String {
fn get_runtime_minutes ( & self ) -> Option <u32> { Some ( self . runtime_minutes ) } 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)
}
} }
impl MediaOverview for SeriesOverview { impl MediaOverview for SeriesOverview {
fn get_uuid ( & self ) -> String { self . uuid . clone () } fn get_uuid(&self) -> String {
fn get_name ( & self ) -> String { self . name . clone () } self.uuid.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_name(&self) -> String {
fn get_runtime_minutes ( & self ) -> Option <u32> { None } 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 row_to_film_overview(row: &Row) -> rusqlite::Result<FilmOverview> { fn row_to_film_overview(row: &Row) -> rusqlite::Result<FilmOverview> {
@ -179,7 +211,8 @@ fn row_to_series_overview ( row : & Row ) -> rusqlite :: Result <SeriesOverview>
# [ derive (Clone) ] pub struct FilmDetails { #[derive(Clone)]
pub struct FilmDetails {
pub uuid: String, pub uuid: String,
pub name: String, pub name: String,
pub original_name: Option<String>, pub original_name: Option<String>,
@ -187,7 +220,8 @@ fn row_to_series_overview ( row : & Row ) -> rusqlite :: Result <SeriesOverview>
pub runtime_minutes: u32, pub runtime_minutes: u32,
pub source: Option<SourceDetails>, pub source: Option<SourceDetails>,
} }
# [ derive (Clone) ] pub struct SourceDetails { #[derive(Clone)]
pub struct SourceDetails {
pub bittorrent_hash: String, pub bittorrent_hash: String,
pub file_path: PathBuf, pub file_path: PathBuf,
pub audio_track: Option<u32>, pub audio_track: Option<u32>,
@ -215,7 +249,7 @@ fn row_to_film_details ( row : & Row ) -> rusqlite :: Result <FilmDetails> {
audio_track, audio_track,
subtitle_track, subtitle_track,
}) })
} , }
None => None, None => None,
}; };
@ -231,10 +265,10 @@ fn row_to_film_details ( row : & Row ) -> rusqlite :: Result <FilmDetails> {
impl From <Error> for ZoodexError { impl From<async_sqlite::Error> for ZoodexError {
fn from ( error : Error ) -> Self { fn from(error: async_sqlite::Error) -> Self {
match error { match error {
Rusqlite (error) => ZoodexError :: from (error) , async_sqlite::Error::Rusqlite(error) => ZoodexError::from(error),
_ => panic!("{}", error), _ => panic!("{}", error),
} }
} }
@ -243,11 +277,9 @@ impl From <Error> for ZoodexError {
impl From<rusqlite::Error> for ZoodexError { impl From<rusqlite::Error> for ZoodexError {
fn from(error: rusqlite::Error) -> Self { fn from(error: rusqlite::Error) -> Self {
match error { match error {
SqliteFailure ( error , _ ) => { rusqlite::Error::SqliteFailure(error, _) => match error.code {
match error . code { rusqlite::ffi::ErrorCode::CannotOpen => ZoodexError::CollectionFileReadError,
CannotOpen => CollectionFileReadError ,
_ => panic!("{}", error), _ => panic!("{}", error),
}
}, },
_ => panic!("{}", error), _ => panic!("{}", error),
} }

View file

@ -1,9 +1,10 @@
use std :: * ; use std::any::Any;
use std :: any :: * ; use std::result;
# [ derive (Debug) ] pub enum ZoodexError { #[derive(Debug)]
pub enum ZoodexError {
CollectionFileReadError, CollectionFileReadError,
} }
@ -20,7 +21,8 @@ macro_rules ! async_result_context { (
$(, ok => $on_success: expr)? $(, ok => $on_success: expr)?
$(, err => $on_failure: expr)?$(,)? $(, err => $on_failure: expr)?$(,)?
) => { ) => {
# [ allow (unreachable_patterns) ] match $ future . await { #[allow(unreachable_patterns)]
match $future.await {
$(Ok(value) => $on_success(value),)? $(Ok(value) => $on_success(value),)?
Ok(_) => {}, Ok(_) => {},
$(Err(error) => $on_failure(error),)? $(Err(error) => $on_failure(error),)?

View file

@ -1,21 +1,20 @@
mod error ;
mod data_manager; mod data_manager;
mod error;
mod ui; mod ui;
mod utility; mod utility;
use gtk4 :: CssProvider ; use gtk4::gdk::Display;
use gtk4 :: style_context_add_provider_for_display ; use gtk4::glib::{ExitCode, spawn_future_local};
use gtk4 :: STYLE_PROVIDER_PRIORITY_APPLICATION ; use gtk4::prelude::{ApplicationExt, ApplicationExtManual};
use gtk4 :: gdk :: * ; use gtk4::{
use gtk4 :: glib :: * ; CssProvider, STYLE_PROVIDER_PRIORITY_APPLICATION, style_context_add_provider_for_display,
};
use libadwaita::Application; use libadwaita::Application;
use libadwaita :: prelude :: * ;
use crate :: data_manager :: * ; use crate::data_manager::DataManager;
use crate :: error :: * ; use crate::error::{ZoodexError, async_result_context};
use crate :: error :: ZoodexError :: * ; use crate::ui::{UI, Window};
use crate :: ui :: * ; use crate::utility::leak;
use crate :: utility :: * ;
@ -61,7 +60,7 @@ fn show_window ( application : & Application ) {
}, },
err => |error| { err => |error| {
match error { match error {
CollectionFileReadError => eprintln ! ("Could not read collection file") , ZoodexError::CollectionFileReadError => eprintln!("Could not read collection file"),
}; };
window.close(); window.close();
}, },

View file

@ -1,18 +1,19 @@
use gtk4 :: { Button , FlowBox , Image , Justification , Label , SelectionMode } ; use std::cell::RefCell;
use gtk4 :: Align :: * ; use std::env::var_os;
use gtk4 :: Orientation :: * ; use std::iter::zip;
use gtk4 :: gdk :: * ;
use gtk4 :: gio :: * ;
use gtk4 :: glib :: * ;
use gtk4 :: pango :: * ;
use gtk4 :: pango :: Weight :: * ;
use gtk4 :: prelude :: * ;
use std :: cell :: * ;
use std :: env :: * ;
use std :: iter :: * ;
use crate :: ui :: collatable_container :: * ; use gtk4::gdk::Texture;
use crate :: ui :: component :: * ; use gtk4::gio::{IOErrorEnum, spawn_blocking};
use gtk4::glib::clone;
use gtk4::pango::{SCALE_LARGE, Weight};
use gtk4::prelude::{BoxExt, ButtonExt, OrientableExt, WidgetExt};
use gtk4::{Align, Button, FlowBox, Image, Justification, Label, Orientation, SelectionMode};
use crate::data_manager::MediaOverview;
use crate::ui::collatable_container::MediaAdapter;
use crate::ui::component::Component;
use crate::ui::utility::{OptChildExt, pango_attributes, view_expr};
use crate::utility::{concat_os_str, leak};
@ -29,13 +30,17 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
set_homogeneous: true, set_homogeneous: true,
set_selection_mode: SelectionMode::None, set_selection_mode: SelectionMode::None,
set_css_classes: &["collatable-container"], set_css_classes: &["collatable-container"],
set_orientation : Horizontal , set_orientation: Orientation::Horizontal,
} }
}; };
let media_widget_pairs = RefCell::new(Vec::new()); let media_widget_pairs = RefCell::new(Vec::new());
let on_media_selected = leak(on_media_selected); let on_media_selected = leak(on_media_selected);
Self { media_widget_pairs , grid_widget , 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) { pub async fn set_media(&self, media: Vec<A::Overview>, sorting: A::Sorting) {
@ -45,7 +50,9 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
for media in media.as_slice() { for media in media.as_slice() {
widgets.push(self.create_media_entry(media).await); widgets.push(self.create_media_entry(media).await);
} }
self . media_widget_pairs . replace ( zip ( media , widgets ) . collect () ) ; self
.media_widget_pairs
.replace(zip(media, widgets).collect());
for (_, widget) in self.sort_media_widget_pairs(sorting) { for (_, widget) in self.sort_media_widget_pairs(sorting) {
self.grid_widget.append(&widget); self.grid_widget.append(&widget);
@ -66,8 +73,8 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
set_child: Some(&view_expr! { set_child: Some(&view_expr! {
gtk4::Box { gtk4::Box {
set_css_classes: &["collection-item-box"], set_css_classes: &["collection-item-box"],
set_valign : Center , set_valign: Align::Center,
set_orientation : Vertical , set_orientation: Orientation::Vertical,
// Poster // Poster
append_opt: &{ append_opt: &{
@ -81,9 +88,9 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
let poster_file_path = concat_os_str!(data_dir, "/posters/", media.get_uuid()); let poster_file_path = concat_os_str!(data_dir, "/posters/", media.get_uuid());
let poster_texture = spawn_blocking ( let poster_texture = spawn_blocking(move || Texture::from_filename(poster_file_path))
move || Texture :: from_filename (poster_file_path) , .await
) . await . unwrap () ; .unwrap();
match poster_texture { match poster_texture {
Ok(poster_texture) => Some(view_expr! { Ok(poster_texture) => Some(view_expr! {
@ -95,9 +102,11 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
}), }),
Err(error) => { Err(error) => {
if error.matches(IOErrorEnum::NotFound) { if error.matches(IOErrorEnum::NotFound) {
None // The file not existing simply means there is no poster for this piece of media // The file not existing simply means there is no poster for this piece of media
None
} else { } else {
panic ! ( "{}" , error ) // Any other error means something unexpected went wrong // Any other error means something unexpected went wrong
panic!("{}", error)
} }
}, },
} }
@ -106,9 +115,10 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
// Name // Name
append: &view_expr! { append: &view_expr! {
Label { Label {
set_attributes : Some ( & pango_attributes ! ( scale : SCALE_LARGE , weight : Bold ) ) , set_attributes: Some(&pango_attributes!(scale: SCALE_LARGE, weight: Weight::Bold)),
set_justify: Justification::Center, set_justify: Justification::Center,
set_max_width_chars : 1 , // Not the actual limit, used instead to wrap more aggressively // Not the actual limit, used instead to wrap more aggressively
set_max_width_chars: 1,
set_wrap: true, set_wrap: true,
set_label: media.get_name().as_str(), set_label: media.get_name().as_str(),
} }
@ -128,8 +138,8 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
append: &view_expr! { append: &view_expr! {
gtk4::Box { gtk4::Box {
set_spacing: 20, set_spacing: 20,
set_halign : Center , set_halign: Align::Center,
set_orientation : Horizontal , set_orientation: Orientation::Horizontal,
// Release date // Release date
append: &view_expr! { append: &view_expr! {
@ -157,13 +167,9 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
} }
fn sort_media_widget_pairs(&self, sorting: A::Sorting) -> Vec<(A::Overview, Button)> { fn sort_media_widget_pairs(&self, sorting: A::Sorting) -> Vec<(A::Overview, Button)> {
let mut sorted = Vec :: from ( let mut sorted = Vec::from(self.media_widget_pairs.borrow().as_slice());
self . media_widget_pairs . borrow () . as_slice () ,
) ;
sorted . sort_by ( sorted.sort_by(|(media_1, _), (media_2, _)| A::compare_by(media_1, media_2, sorting));
| ( media_1 , _ ) , ( media_2 , _ ) | A :: compare_by ( media_1 , media_2 , sorting ) ,
) ;
// See it, say it, ... // See it, say it, ...
sorted sorted
@ -171,5 +177,7 @@ impl < A : MediaAdapter > CollatedMediaGrid <A> {
} }
impl<A: MediaAdapter> Component for CollatedMediaGrid<A> { impl<A: MediaAdapter> Component for CollatedMediaGrid<A> {
fn get_widget ( & self ) -> & FlowBox { & self . grid_widget } fn get_widget(&self) -> &FlowBox {
&self.grid_widget
}
} }

View file

@ -1,19 +1,18 @@
mod sort_button; mod sort_button;
use gtk4 :: Box ; use gtk4::prelude::{BoxExt, OrientableExt, WidgetExt};
use gtk4 :: Align :: * ; use gtk4::{Align, Box, Orientation};
use gtk4 :: Orientation :: * ; use relm4_macros::view;
use gtk4 :: prelude :: * ;
use relm4_macros :: * ;
use std :: ops :: * ;
use crate :: ui :: component :: * ; use crate::ui::collatable_container::MediaAdapter;
use crate :: ui :: collatable_container :: * ; use crate::ui::collatable_container::collation_menu::sort_button::MediaSortButton;
use crate :: ui :: collatable_container :: collation_menu :: sort_button :: * ; use crate::ui::component::Component;
pub struct MediaCollationMenu { widget : Box } pub struct MediaCollationMenu {
widget: Box,
}
impl MediaCollationMenu { impl MediaCollationMenu {
pub fn new<A: MediaAdapter>(on_sort: impl Fn(A::Sorting) + 'static) -> Self { pub fn new<A: MediaAdapter>(on_sort: impl Fn(A::Sorting) + 'static) -> Self {
@ -23,8 +22,8 @@ impl MediaCollationMenu {
widget = gtk4::Box { widget = gtk4::Box {
set_spacing: 20, set_spacing: 20,
set_css_classes: &["toolbar", "collation-menu"], set_css_classes: &["toolbar", "collation-menu"],
set_halign : Center , set_halign: Align::Center,
set_orientation : Horizontal , set_orientation: Orientation::Horizontal,
append: sort_button.get_widget(), append: sort_button.get_widget(),
}, },
@ -35,5 +34,7 @@ impl MediaCollationMenu {
} }
impl Component for MediaCollationMenu { impl Component for MediaCollationMenu {
fn get_widget ( & self ) -> & Box { & self . widget } fn get_widget(&self) -> &Box {
&self.widget
}
} }

View file

@ -1,13 +1,14 @@
use gtk4 :: { Image , ListBox , Popover } ; use std::cell::RefCell;
use gtk4 :: Align :: * ;
use libadwaita :: SplitButton ;
use relm4_macros :: * ;
use std :: cell :: * ;
use crate :: utility :: * ; use gtk4::prelude::{BoxExt, ListBoxRowExt, OrientableExt, PopoverExt, WidgetExt};
use crate :: ui :: * ; use gtk4::{Align, Image, Label, ListBox, Orientation, Popover};
use crate :: ui :: utility :: * ; use libadwaita::SplitButton;
use crate :: ui :: collatable_container :: SortingDirection :: * ; use relm4_macros::view;
use crate::ui::collatable_container::{MediaAdapter, MediaSorting, SortingDirection};
use crate::ui::component::Component;
use crate::ui::utility::view_expr;
use crate::utility::leak;
@ -57,10 +58,10 @@ impl < A : MediaAdapter > MediaSortButton <A> {
list_box.append(&view_expr! { list_box.append(&view_expr! {
gtk4::Box { gtk4::Box {
set_spacing: 20, set_spacing: 20,
set_orientation : Horizontal , set_orientation: Orientation::Horizontal,
append: &view_expr! { append: &view_expr! {
Label { Label {
set_halign : Start , set_halign: Align::Start,
set_hexpand: true, set_hexpand: true,
set_label: description, set_label: description,
} }
@ -70,12 +71,17 @@ impl < A : MediaAdapter > MediaSortButton <A> {
}); });
} }
Self { widget , previous_sorting } Self {
widget,
previous_sorting,
}
} }
} }
impl<A: MediaAdapter> Component for MediaSortButton<A> { impl<A: MediaAdapter> Component for MediaSortButton<A> {
fn get_widget ( & self ) -> & SplitButton { & self . widget } fn get_widget(&self) -> &SplitButton {
&self.widget
}
} }
fn on_media_sort_activated<A: MediaAdapter>( fn on_media_sort_activated<A: MediaAdapter>(
@ -94,21 +100,21 @@ fn on_media_sort_activated < A : MediaAdapter > (
let previous_sorting = *previous_sorting_mut.borrow(); let previous_sorting = *previous_sorting_mut.borrow();
if sorting_property == previous_sorting.get_property() { if sorting_property == previous_sorting.get_property() {
match previous_sorting.get_direction() { match previous_sorting.get_direction() {
Ascending => { SortingDirection::Ascending => {
let new_sorting = A :: Sorting :: new ( sorting_property , Descending ) ; let new_sorting = A::Sorting::new(sorting_property, SortingDirection::Descending);
previous_sorting_mut.replace(new_sorting); previous_sorting_mut.replace(new_sorting);
sort_icons[row].set_icon_name(Some("view-sort-descending-symbolic")); sort_icons[row].set_icon_name(Some("view-sort-descending-symbolic"));
on_sort(new_sorting); on_sort(new_sorting);
} , }
Descending => { SortingDirection::Descending => {
let new_sorting = A :: Sorting :: new ( sorting_property , Ascending ) ; let new_sorting = A::Sorting::new(sorting_property, SortingDirection::Ascending);
previous_sorting_mut.replace(new_sorting); previous_sorting_mut.replace(new_sorting);
sort_icons[row].set_icon_name(Some("view-sort-ascending-symbolic")); sort_icons[row].set_icon_name(Some("view-sort-ascending-symbolic"));
on_sort(new_sorting); on_sort(new_sorting);
} , }
} }
} else { } else {
let new_sorting = A :: Sorting :: new ( sorting_property , Ascending ) ; let new_sorting = A::Sorting::new(sorting_property, SortingDirection::Ascending);
previous_sorting_mut.replace(new_sorting); previous_sorting_mut.replace(new_sorting);
sort_icons[row].set_icon_name(Some("view-sort-ascending-symbolic")); sort_icons[row].set_icon_name(Some("view-sort-ascending-symbolic"));
on_sort(new_sorting); on_sort(new_sorting);

View file

@ -1,19 +1,19 @@
mod collated_grid; mod collated_grid;
mod collation_menu; mod collation_menu;
use gtk4 :: { Box , ScrolledWindow } ; use std::cmp::Ordering;
use gtk4 :: Orientation :: * ; use std::fmt::Debug;
use gtk4 :: prelude :: * ;
use relm4_macros :: * ;
use std :: cmp :: * ;
use std :: fmt :: * ;
use crate :: data_manager :: * ; use gtk4::prelude::{BoxExt, OrientableExt};
use crate :: ui :: component :: * ; use gtk4::{Box, Orientation, ScrolledWindow};
use crate :: ui :: utility :: * ; use relm4_macros::view;
use crate :: ui :: collatable_container :: collated_grid :: * ;
use crate :: ui :: collatable_container :: collation_menu :: * ; use crate::data_manager::{FilmOverview, MediaOverview, SeriesOverview};
use crate :: utility :: * ; use crate::ui::collatable_container::collated_grid::CollatedMediaGrid;
use crate::ui::collatable_container::collation_menu::MediaCollationMenu;
use crate::ui::component::Component;
use crate::ui::utility::{vertical_filler, view_expr};
use crate::utility::leak;
@ -25,38 +25,64 @@ pub trait MediaSorting < P : MediaProperty > : Clone + Copy + Debug + Default {
pub trait MediaProperty: Clone + Copy + Debug + PartialEq {} pub trait MediaProperty: Clone + Copy + Debug + PartialEq {}
# [ derive ( Clone , Copy , Debug , Default , PartialEq ) ] pub enum FilmProperty { #[derive(Clone, Copy, Debug, Default, PartialEq)]
# [default] Name , ReleaseDate , Runtime , pub enum FilmProperty {
#[default]
Name,
ReleaseDate,
Runtime,
} }
# [ derive ( Clone , Copy , Debug , Default , PartialEq ) ] pub enum SeriesProperty { #[derive(Clone, Copy, Debug, Default, PartialEq)]
# [default] Name , FirstReleaseDate , pub enum SeriesProperty {
#[default]
Name,
FirstReleaseDate,
} }
# [ derive ( Clone , Copy , Debug , Default , PartialEq ) ] pub enum SortingDirection { #[derive(Clone, Copy, Debug, Default, PartialEq)]
# [default] Ascending , Descending , pub enum SortingDirection {
#[default]
Ascending,
Descending,
} }
# [ derive ( Clone , Copy , Debug , Default ) ] pub struct FilmsSorting { #[derive(Clone, Copy, Debug, Default)]
pub struct FilmsSorting {
property: FilmProperty, property: FilmProperty,
direction: SortingDirection, direction: SortingDirection,
} }
# [ derive ( Clone , Copy , Debug , Default ) ] pub struct SeriesSorting { #[derive(Clone, Copy, Debug, Default)]
pub struct SeriesSorting {
property: SeriesProperty, property: SeriesProperty,
direction: SortingDirection, direction: SortingDirection,
} }
impl MediaSorting<FilmProperty> for FilmsSorting { impl MediaSorting<FilmProperty> for FilmsSorting {
fn new(property: FilmProperty, direction: SortingDirection) -> Self { fn new(property: FilmProperty, direction: SortingDirection) -> Self {
Self { property , direction } Self {
property,
direction,
}
}
fn get_property(&self) -> FilmProperty {
self.property
}
fn get_direction(&self) -> SortingDirection {
self.direction
} }
fn get_property ( & self ) -> FilmProperty { self . property }
fn get_direction ( & self ) -> SortingDirection { self . direction }
} }
impl MediaSorting<SeriesProperty> for SeriesSorting { impl MediaSorting<SeriesProperty> for SeriesSorting {
fn new(property: SeriesProperty, direction: SortingDirection) -> Self { fn new(property: SeriesProperty, direction: SortingDirection) -> Self {
Self { property , direction } Self {
property,
direction,
}
}
fn get_property(&self) -> SeriesProperty {
self.property
}
fn get_direction(&self) -> SortingDirection {
self.direction
} }
fn get_property ( & self ) -> SeriesProperty { self . property }
fn get_direction ( & self ) -> SortingDirection { self . direction }
} }
impl MediaProperty for FilmProperty {} impl MediaProperty for FilmProperty {}
@ -70,13 +96,11 @@ pub struct CollatableMediaContainer < A : MediaAdapter > {
impl<A: MediaAdapter> CollatableMediaContainer<A> { impl<A: MediaAdapter> CollatableMediaContainer<A> {
pub fn new(on_media_selected: impl Fn(A::Overview) + 'static) -> Self { pub fn new(on_media_selected: impl Fn(A::Overview) + 'static) -> Self {
let collated_grid = leak(CollatedMediaGrid::new(on_media_selected)); let collated_grid = leak(CollatedMediaGrid::new(on_media_selected));
let collation_menu = MediaCollationMenu :: new :: <A> ( let collation_menu = MediaCollationMenu::new::<A>(|sorting| collated_grid.set_sorting(sorting));
|sorting| collated_grid . set_sorting (sorting) ,
) ;
view! { view! {
widget = gtk4::Box { widget = gtk4::Box {
set_orientation : Vertical , set_orientation: Orientation::Vertical,
append: collation_menu.get_widget(), append: collation_menu.get_widget(),
append: &view_expr! { append: &view_expr! {
ScrolledWindow { ScrolledWindow {
@ -87,11 +111,17 @@ impl < A : MediaAdapter > CollatableMediaContainer <A> {
} }
} }
Self { collated_grid, widget } Self {
collated_grid,
widget,
}
} }
pub async fn set_media(&self, media: Vec<A::Overview>) { pub async fn set_media(&self, media: Vec<A::Overview>) {
self . collated_grid . set_media ( media , A :: Sorting :: default () ) . await ; self
.collated_grid
.set_media(media, A::Sorting::default())
.await;
} }
} }
@ -108,7 +138,9 @@ pub trait MediaAdapter : 'static {
} }
impl<A: MediaAdapter> Component for CollatableMediaContainer<A> { impl<A: MediaAdapter> Component for CollatableMediaContainer<A> {
fn get_widget ( & self ) -> & Box { & self . widget } fn get_widget(&self) -> &Box {
&self.widget
}
} }
pub struct FilmsAdapter {} pub struct FilmsAdapter {}
@ -119,18 +151,11 @@ impl MediaAdapter for FilmsAdapter {
type Sorting = FilmsSorting; type Sorting = FilmsSorting;
type Property = FilmProperty; type Property = FilmProperty;
fn compare_by ( fn compare_by(film_1: &FilmOverview, film_2: &FilmOverview, sorting: FilmsSorting) -> Ordering {
film_1 : & FilmOverview ,
film_2 : & FilmOverview ,
sorting : FilmsSorting ,
) -> Ordering {
let ordering = match sorting.property { let ordering = match sorting.property {
FilmProperty :: Name => FilmProperty::Name => film_1.name.cmp(&film_2.name),
film_1 . name . cmp ( & film_2 . name ) , FilmProperty::ReleaseDate => film_1.release_date.cmp(&film_2.release_date),
FilmProperty :: ReleaseDate => FilmProperty::Runtime => film_1.runtime_minutes.cmp(&film_2.runtime_minutes),
film_1 . release_date . cmp ( & film_2 . release_date ) ,
FilmProperty :: Runtime =>
film_1 . runtime_minutes . cmp ( & film_2 . runtime_minutes ) ,
}; };
match sorting.direction { match sorting.direction {
SortingDirection::Ascending => ordering, SortingDirection::Ascending => ordering,
@ -157,10 +182,10 @@ impl MediaAdapter for SeriesAdapter {
sorting: SeriesSorting, sorting: SeriesSorting,
) -> Ordering { ) -> Ordering {
let ordering = match sorting.property { let ordering = match sorting.property {
SeriesProperty :: Name => SeriesProperty::Name => series_1.name.cmp(&series_2.name),
series_1 . name . cmp ( & series_2 . name ) , SeriesProperty::FirstReleaseDate => series_1
SeriesProperty :: FirstReleaseDate => .first_release_date
series_1 . first_release_date . cmp ( & series_2 . first_release_date ) , .cmp(&series_2.first_release_date),
}; };
match sorting.direction { match sorting.direction {
SortingDirection::Ascending => ordering, SortingDirection::Ascending => ordering,

View file

@ -1,5 +1,5 @@
use gtk4 :: * ; use gtk4::Widget;
use gtk4 :: prelude :: * ; use gtk4::prelude::IsA;

View file

@ -2,22 +2,24 @@ mod collatable_container ;
mod component; mod component;
mod utility; mod utility;
use futures :: * ; use std::process::Command;
use gtk4 :: { Button , Image , Label } ;
use gtk4 :: Orientation :: * ;
use gtk4 :: glib :: * ;
use gtk4 :: prelude :: * ;
use libadwaita :: { Application , ApplicationWindow , Dialog , HeaderBar , ToolbarView , ViewStack , ViewSwitcher } ;
use libadwaita :: ViewSwitcherPolicy :: * ;
use libadwaita :: prelude :: * ;
use relm4_macros :: * ;
use std :: process :: * ;
use crate :: data_manager :: * ; use futures::join;
use crate :: ui :: collatable_container :: * ; use gtk4::glib::spawn_future_local;
use crate :: ui :: component :: * ; use gtk4::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt, WidgetExt};
use crate :: ui :: utility :: * ; use gtk4::{Button, Image, Label, Orientation};
use crate :: utility :: * ; use libadwaita::prelude::{AdwApplicationWindowExt, AdwDialogExt};
use libadwaita::{
Application, ApplicationWindow, Dialog, HeaderBar, ToolbarView, ViewStack, ViewSwitcher,
ViewSwitcherPolicy,
};
use relm4_macros::view;
use crate::data_manager::{CollectionOverview, FilmDetails};
use crate::ui::collatable_container::{CollatableMediaContainer, FilmsAdapter, SeriesAdapter};
use crate::ui::component::Component;
use crate::ui::utility::{OptChildExt, view_expr};
use crate::utility::{concat_os_str, leak, to_os_string};
@ -44,7 +46,7 @@ impl UI {
gtk4::Box { gtk4::Box {
set_spacing: 40, set_spacing: 40,
set_css_classes: &["media-modal"], set_css_classes: &["media-modal"],
set_orientation : Vertical , set_orientation: Orientation::Vertical,
append: &view_expr! { append: &view_expr! {
Label { Label {
@ -105,7 +107,7 @@ impl UI {
header_bar = HeaderBar { header_bar = HeaderBar {
set_title_widget: Some(&view_expr! { set_title_widget: Some(&view_expr! {
ViewSwitcher { ViewSwitcher {
set_policy : Wide , set_policy: ViewSwitcherPolicy::Wide,
set_stack: Some(&switched), set_stack: Some(&switched),
} }
}), }),
@ -119,7 +121,10 @@ impl UI {
} }
})); }));
UI { films_component , series_component } UI {
films_component,
series_component,
}
} }
pub async fn render_collection_overview(&self, collection: CollectionOverview) { pub async fn render_collection_overview(&self, collection: CollectionOverview) {
@ -148,7 +153,11 @@ impl Window {
Self { libadwaita_window } Self { libadwaita_window }
} }
pub fn show ( & self ) { self . libadwaita_window . set_visible (true) } pub fn show(&self) {
self.libadwaita_window.set_visible(true)
pub fn close ( & self ) { self . libadwaita_window . close () } }
pub fn close(&self) {
self.libadwaita_window.close()
}
} }

View file

@ -1,6 +1,5 @@
use gtk4 :: Widget ; use gtk4::prelude::{BoxExt, IsA, OrientableExt, WidgetExt};
use gtk4 :: Orientation :: * ; use gtk4::{Orientation, Widget};
use gtk4 :: prelude :: * ;
use libadwaita::Bin; use libadwaita::Bin;
@ -21,9 +20,12 @@ impl OptChildExt for gtk4 :: Box {
// The `view` macro from Relm4 as an expression instead of a variable declaration // The `view` macro from Relm4 as an expression instead of a variable
// declaration
macro_rules ! view_expr { ( $ ( $ contents : tt ) * ) => { { macro_rules! view_expr {(
$($contents: tt)*
) => {{
relm4_macros::view! { outer = $($contents)* } relm4_macros::view! { outer = $($contents)* }
outer outer
}}} }}}
@ -33,7 +35,7 @@ macro_rules ! view_expr { ( $ ( $ contents : tt ) * ) => { {
pub fn vertical_filler(child: &impl IsA<Widget>) -> gtk4::Box { pub fn vertical_filler(child: &impl IsA<Widget>) -> gtk4::Box {
view_expr! { view_expr! {
gtk4::Box { gtk4::Box {
set_orientation : Vertical , set_orientation: Orientation::Vertical,
append: child, append: child,
append: &view_expr! { append: &view_expr! {
Bin { set_vexpand: true } Bin { set_vexpand: true }
@ -49,7 +51,8 @@ macro_rules ! pango_attributes { (
$(, weight: $weight: expr $(,)?)? $(, weight: $weight: expr $(,)?)?
) => {{ ) => {{
let attributes = gtk4::pango::AttrList::new(); let attributes = gtk4::pango::AttrList::new();
# [ allow (unused_mut) ] let mut font_description = gtk4 :: pango :: FontDescription :: new () ; #[allow(unused_mut)]
let mut font_description = gtk4::pango::FontDescription::new();
$(attributes.insert(gtk4::pango::AttrFloat::new_scale($scale));)? $(attributes.insert(gtk4::pango::AttrFloat::new_scale($scale));)?
$(font_description.set_weight($weight);)? $(font_description.set_weight($weight);)?
@ -60,7 +63,5 @@ macro_rules ! pango_attributes { (
# [ allow (unused_imports) ] pub (crate) use { #[allow(unused_imports)]
pango_attributes , pub(crate) use {pango_attributes, view_expr};
view_expr ,
} ;

View file

@ -1,11 +1,10 @@
use std :: ffi :: * ; use std::ffi::OsString;
use std :: fmt :: * ; use std::fmt::Display;
macro_rules! concat_os_str {( macro_rules! concat_os_str {(
$ base : expr , $base: expr, $($suffix: expr),+
$ ( $ suffix : expr ) , +
) => {{ ) => {{
let mut base = std :: ffi :: OsString :: from ( $ base ) ; let mut base = std :: ffi :: OsString :: from ( $ base ) ;
$ ( base . push ( $ suffix ) ; ) + $ ( base . push ( $ suffix ) ; ) +
@ -26,4 +25,5 @@ pub fn to_os_string ( value : impl Display + Sized ) -> OsString {
# [ allow (unused_imports) ] pub (crate) use concat_os_str ; #[allow(unused_imports)]
pub(crate) use concat_os_str;