Skip to content

Commit

Permalink
Add filter for which manifest defined a game
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Aug 9, 2024
1 parent e4a7b36 commit a2a06e3
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
and you can click the icon to display the notes.
The primary manifest does not (yet) contain any notes,
so this mainly applies to secondary manifest authors.
* GUI: You can now filter games by which secondary manifest defined them.
* CLI: The `api` command now supports a `checkAppUpdate` message.
* Fixed:
* CLI: Some commands would fail with relative path arguments.
Expand Down
4 changes: 4 additions & 0 deletions lang/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ label-game = Game
# Aliases are alternative titles for the same game.
label-alias = Alias
label-original-name = Original name
# Which manifest a game's data came from
label-source = Source
# This refers to the main Ludusavi manifest: https://github.com/mtkennerly/ludusavi-manifest
label-primary-manifest = Primary manifest
store-ea = EA
store-epic = Epic
Expand Down
9 changes: 9 additions & 0 deletions src/gui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1956,6 +1956,15 @@ impl Application for App {
search.change.choice = filter;
Command::none()
}
Message::EditedSearchFilterManifest(filter) => {
let search = if self.screen == Screen::Backup {
&mut self.backup_screen.log.search
} else {
&mut self.restore_screen.log.search
};
search.manifest.choice = filter;
Command::none()
}
Message::EditedSortKey { screen, value } => {
match screen {
Screen::Backup => {
Expand Down
1 change: 1 addition & 0 deletions src/gui/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ pub enum Message {
EditedSearchFilterCompleteness(game_filter::Completeness),
EditedSearchFilterEnablement(game_filter::Enablement),
EditedSearchFilterChange(game_filter::Change),
EditedSearchFilterManifest(game_filter::Manifest),
EditedSortKey {
screen: Screen,
value: SortKey,
Expand Down
24 changes: 21 additions & 3 deletions src/gui/game_list.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::collections::{BTreeSet, HashSet};

use iced::{alignment::Horizontal as HorizontalAlignment, keyboard::Modifiers, widget::tooltip, Alignment, Length};

Expand All @@ -18,9 +18,9 @@ use crate::{
resource::{
cache::Cache,
config::{Config, Sort},
manifest::{Manifest, Os},
manifest::{self, Manifest, Os},
},
scan::{layout::GameLayout, BackupInfo, DuplicateDetector, OperationStatus, ScanChange, ScanInfo},
scan::{game_filter, layout::GameLayout, BackupInfo, DuplicateDetector, OperationStatus, ScanChange, ScanInfo},
};

#[derive(Default)]
Expand Down Expand Up @@ -415,6 +415,7 @@ impl GameList {
if restoring { Screen::Restore } else { Screen::Backup },
histories,
config.scan.show_deselected_games,
self.manifests(manifest),
)
})
.push({
Expand All @@ -433,6 +434,7 @@ impl GameList {
!self.search.show
|| self.search.qualifies(
&x.scan_info,
manifest,
config.is_game_enabled_for_operation(&x.scan_info.game_name, restoring),
duplicate_detector.is_game_duplicated(&x.scan_info.game_name),
config.scan.show_deselected_games,
Expand Down Expand Up @@ -751,4 +753,20 @@ impl GameList {

layout.save();
}

fn manifests(&self, manifest: &Manifest) -> Vec<game_filter::Manifest> {
let mut manifests = BTreeSet::new();
manifests.insert(&manifest::Source::Primary);

for entry in &self.entries {
if let Some(data) = manifest.0.get(&entry.scan_info.game_name) {
manifests.extend(data.sources.iter());
}
}

manifests
.into_iter()
.map(|x| game_filter::Manifest::new(x.clone()))
.collect::<Vec<_>>()
}
}
50 changes: 48 additions & 2 deletions src/gui/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
widget::{checkbox, pick_list, text, Column, Element, IcedParentExt, Row},
},
lang::TRANSLATOR,
resource::manifest::Manifest,
scan::{
game_filter::{self, FilterKind},
Duplication, ScanInfo,
Expand All @@ -28,6 +29,7 @@ pub struct FilterComponent {
pub completeness: Filter<game_filter::Completeness>,
pub enablement: Filter<game_filter::Enablement>,
pub change: Filter<game_filter::Change>,
pub manifest: Filter<game_filter::Manifest>,
}

fn template<'a, T: 'static + Default + Copy + Eq + PartialEq + ToString>(
Expand All @@ -50,10 +52,28 @@ fn template<'a, T: 'static + Default + Copy + Eq + PartialEq + ToString>(
.into()
}

fn template_with_label<T: 'static + Default + Clone + Eq + PartialEq + ToString>(
filter: &Filter<T>,
label: String,
kind: FilterKind,
options: Vec<T>,
message: fn(T) -> Message,
) -> Element {
Row::new()
.spacing(10)
.align_items(Alignment::Center)
.push(checkbox(label, filter.active, move |enabled| {
Message::ToggledSearchFilter { filter: kind, enabled }
}))
.push(pick_list(options, Some(filter.choice.clone()), message))
.into()
}

impl FilterComponent {
pub fn qualifies(
&self,
scan: &ScanInfo,
manifest: &Manifest,
enabled: bool,
duplicated: Duplication,
show_deselected_games: bool,
Expand All @@ -66,8 +86,14 @@ impl FilterComponent {
let complete = !self.completeness.active || self.completeness.choice.qualifies(scan);
let enable = !show_deselected_games || !self.enablement.active || self.enablement.choice.qualifies(enabled);
let changed = !self.change.active || self.change.choice.qualifies(scan);
let manifest = !self.manifest.active
|| manifest
.0
.get(&scan.game_name)
.map(|game| self.manifest.choice.qualifies(game))
.unwrap_or_default();

fuzzy && unique && complete && changed && enable
fuzzy && unique && complete && changed && enable && manifest
}

pub fn toggle_filter(&mut self, filter: FilterKind, enabled: bool) {
Expand All @@ -76,10 +102,17 @@ impl FilterComponent {
FilterKind::Completeness => self.completeness.active = enabled,
FilterKind::Enablement => self.enablement.active = enabled,
FilterKind::Change => self.change.active = enabled,
FilterKind::Manifest => self.manifest.active = enabled,
}
}

pub fn view(&self, screen: Screen, histories: &TextHistories, show_deselected_games: bool) -> Option<Element> {
pub fn view(
&self,
screen: Screen,
histories: &TextHistories,
show_deselected_games: bool,
manifests: Vec<game_filter::Manifest>,
) -> Option<Element> {
if !self.show {
return None;
}
Expand Down Expand Up @@ -128,6 +161,19 @@ impl FilterComponent {
)
}),
)
.push_if(manifests.len() > 1, || {
Row::new()
.padding([0, 20, 20, 20])
.spacing(20)
.align_items(Alignment::Center)
.push(template_with_label(
&self.manifest,
TRANSLATOR.source_field(),
FilterKind::Manifest,
manifests,
Message::EditedSearchFilterManifest,
))
})
.into(),
)
}
Expand Down
12 changes: 12 additions & 0 deletions src/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,18 @@ impl Translator {
self.field(&self.original_name_label())
}

pub fn source_label(&self) -> String {
translate("label-source")
}

pub fn source_field(&self) -> String {
self.field(&self.source_label())
}

pub fn primary_manifest_label(&self) -> String {
translate("label-primary-manifest")
}

pub fn custom_game_kind(&self, kind: &CustomGameKind) -> String {
match kind {
CustomGameKind::Game => self.game_label(),
Expand Down
32 changes: 28 additions & 4 deletions src/resource/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ pub struct Secondary {
pub data: Manifest,
}

#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Source {
#[default]
Primary,
Secondary(String),
}

#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Manifest(#[serde(serialize_with = "crate::serialization::ordered_map")] pub HashMap<String, Game>);

Expand All @@ -193,6 +200,8 @@ pub struct Game {
pub cloud: CloudMetadata,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub notes: Vec<Note>,
#[serde(skip)]
pub sources: BTreeSet<Source>,
}

impl Game {
Expand Down Expand Up @@ -377,10 +386,17 @@ impl Manifest {
}

pub fn load() -> Result<Self, Error> {
ResourceFile::load().map_err(|e| Error::ManifestInvalid {
why: format!("{}", e),
identifier: None,
})
ResourceFile::load()
.map(|mut manifest: Self| {
for game in manifest.0.values_mut() {
game.sources.insert(Source::Primary);
}
manifest
})
.map_err(|e| Error::ManifestInvalid {
why: format!("{}", e),
identifier: None,
})
}

pub fn load_with_secondary(config: &Config) -> Result<Self, Error> {
Expand Down Expand Up @@ -602,6 +618,7 @@ impl Manifest {
// you probably still want to back up your customized versions of such games.
cloud: CloudMetadata::default(),
notes: Default::default(),
sources: Default::default(),
};

self.0.insert(name, game);
Expand Down Expand Up @@ -659,6 +676,8 @@ impl Manifest {
note.source = Some(secondary.id.clone());
}
standard.notes.extend(game.notes);

standard.sources.insert(Source::Secondary(secondary.id.clone()));
} else {
log::debug!("adding game from secondary manifest: {name}");

Expand All @@ -670,6 +689,8 @@ impl Manifest {
note.source = Some(secondary.id.clone());
}

game.sources.insert(Source::Secondary(secondary.id.clone()));

self.0.insert(name, game);
}
}
Expand All @@ -691,6 +712,7 @@ impl Manifest {
id,
cloud: _,
notes: _,
sources: _,
} = &v;
alias.is_none()
&& (!files.is_empty() || !registry.is_empty() || !steam.is_empty() || !gog.is_empty() || !id.is_empty())
Expand Down Expand Up @@ -765,6 +787,7 @@ mod tests {
id: Default::default(),
cloud: Default::default(),
notes: Default::default(),
sources: Default::default(),
},
manifest.0["game"],
);
Expand Down Expand Up @@ -853,6 +876,7 @@ mod tests {
uplay: true
},
notes: Default::default(),
sources: Default::default(),
},
manifest.0["game"],
);
Expand Down
28 changes: 28 additions & 0 deletions src/scan/game_filter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
lang::TRANSLATOR,
resource::manifest,
scan::{Duplication, ScanInfo},
};

Expand All @@ -11,6 +12,7 @@ pub enum FilterKind {
Completeness,
Enablement,
Change,
Manifest,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
Expand Down Expand Up @@ -112,3 +114,29 @@ impl Change {
}
}
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Manifest {
source: manifest::Source,
}

impl Manifest {
pub fn new(source: manifest::Source) -> Self {
Self { source }
}
}

impl ToString for Manifest {
fn to_string(&self) -> String {
match &self.source {
manifest::Source::Primary => TRANSLATOR.primary_manifest_label(),
manifest::Source::Secondary(id) => id.to_string(),
}
}
}

impl Manifest {
pub fn qualifies(&self, game: &manifest::Game) -> bool {
game.sources.contains(&self.source)
}
}

0 comments on commit a2a06e3

Please sign in to comment.