Skip to content

Commit

Permalink
#201, #260: Apply filter to scan
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Oct 19, 2024
1 parent 49a494c commit c634985
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 46 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ to better preserve the files' executable permissions.
For Steam roots, this also supports shortcuts to non-Steam games,
where the placeholder will map to the shortcut's dynamic app ID.
* Paths may now use the `<winLocalAppDataLow>` placeholder.
* GUI: On the backup and restore screens,
if you activate the filter options,
then the backup/restore buttons will only process the currently listed games.
This allows you to quickly scan a specific subset of games.
* You can now choose whether a custom game will override or extend
a manifest entry with the same name.
Previously, it would always override the manifest entry completely.
Expand Down
2 changes: 2 additions & 0 deletions lang/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,5 @@ new-version-available = An application update is available: {$version}. Would yo
custom-game-will-override = This custom game overrides a manifest entry
custom-game-will-extend = This custom game extends a manifest entry
operation-will-only-include-listed-games = This will only process the games that are currently listed
38 changes: 28 additions & 10 deletions src/gui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,18 +274,27 @@ impl App {
preview,
repair,
jump,
games,
mut games,
} => {
if !self.operation.idle() {
return Task::none();
}

let mut cleared_log = false;
if games.is_none() {
self.backup_screen.log.clear();
self.backup_screen.duplicate_detector.clear();
self.reset_scroll_position(ScrollSubject::Backup);
cleared_log = true;
if self.backup_screen.log.is_filtered() {
games = Some(self.backup_screen.log.visible_games(
false,
&self.config,
&self.manifest.extended,
&self.backup_screen.duplicate_detector,
));
} else {
self.backup_screen.log.clear();
self.backup_screen.duplicate_detector.clear();
self.reset_scroll_position(ScrollSubject::Backup);
cleared_log = true;
}
}

if jump {
Expand Down Expand Up @@ -693,7 +702,7 @@ impl App {
fn handle_restore(&mut self, phase: RestorePhase) -> Task<Message> {
match phase {
RestorePhase::Confirm { games } => self.show_modal(Modal::ConfirmRestore { games }),
RestorePhase::Start { preview, games } => {
RestorePhase::Start { preview, mut games } => {
if !self.operation.idle() {
return Task::none();
}
Expand All @@ -707,10 +716,19 @@ impl App {

let mut cleared_log = false;
if games.is_none() {
self.restore_screen.log.clear();
self.restore_screen.duplicate_detector.clear();
self.reset_scroll_position(ScrollSubject::Restore);
cleared_log = true;
if self.restore_screen.log.is_filtered() {
games = Some(self.restore_screen.log.visible_games(
false,
&self.config,
&self.manifest.extended,
&self.restore_screen.duplicate_detector,
));
} else {
self.restore_screen.log.clear();
self.restore_screen.duplicate_detector.clear();
self.reset_scroll_position(ScrollSubject::Restore);
cleared_log = true;
}
}

self.operation =
Expand Down
70 changes: 60 additions & 10 deletions src/gui/button.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use iced::{alignment, keyboard};
use iced::{alignment, keyboard, Length};

use crate::{
gui::{
Expand All @@ -8,7 +8,7 @@ use crate::{
},
icon::Icon,
style,
widget::{text, Button, Element, Text},
widget::{text, Button, Container, Element, Row, Text, Tooltip},
},
lang::TRANSLATOR,
prelude::{Finality, SyncDirection},
Expand All @@ -33,6 +33,48 @@ fn template_bare(content: Text, action: Option<Message>, style: Option<style::Bu
.into()
}

fn template_extended(
content: Text,
action: Option<Message>,
style: Option<style::Button>,
icon: Option<Icon>,
tooltip: Option<String>,
) -> Element {
let button = match icon {
Some(icon) => template_complex(
Container::new(
Row::new()
.spacing(5)
.push(icon.text_narrow())
.push(content.width(Length::Shrink)),
)
.center_x(WIDTH),
action,
style,
),
None => template(content, action, style),
};

match tooltip {
Some(tooltip) => Tooltip::new(button, text(tooltip), iced::widget::tooltip::Position::Top)
.class(style::Container::Tooltip)
.into(),
None => button,
}
}

fn template_complex<'a>(
content: impl Into<Element<'a>>,
action: Option<Message>,
style: Option<style::Button>,
) -> Element<'a> {
Button::new(content)
.on_press_maybe(action)
.class(style.unwrap_or(style::Button::Primary))
.padding(5)
.into()
}

pub fn primary<'a>(content: String, action: Option<Message>) -> Element<'a> {
Button::new(text(content).align_x(alignment::Horizontal::Center))
.on_press_maybe(action)
Expand Down Expand Up @@ -329,8 +371,8 @@ pub fn download<'a>(operation: &Operation) -> Element<'a> {
)
}

pub fn backup<'a>(ongoing: &Operation) -> Element<'a> {
template(
pub fn backup<'a>(ongoing: &Operation, filtered: bool) -> Element<'a> {
template_extended(
text(match ongoing {
Operation::Backup {
finality: Finality::Final,
Expand Down Expand Up @@ -363,11 +405,13 @@ pub fn backup<'a>(ongoing: &Operation) -> Element<'a> {
}
)
.then_some(style::Button::Negative),
filtered.then_some(Icon::Filter),
filtered.then(|| TRANSLATOR.operation_will_only_include_listed_games()),
)
}

pub fn backup_preview<'a>(ongoing: &Operation) -> Element<'a> {
template(
pub fn backup_preview<'a>(ongoing: &Operation, filtered: bool) -> Element<'a> {
template_extended(
text(match ongoing {
Operation::Backup {
finality: Finality::Preview,
Expand Down Expand Up @@ -405,11 +449,13 @@ pub fn backup_preview<'a>(ongoing: &Operation) -> Element<'a> {
}
)
.then_some(style::Button::Negative),
filtered.then_some(Icon::Filter),
filtered.then(|| TRANSLATOR.operation_will_only_include_listed_games()),
)
}

pub fn restore<'a>(ongoing: &Operation) -> Element<'a> {
template(
pub fn restore<'a>(ongoing: &Operation, filtered: bool) -> Element<'a> {
template_extended(
text(match ongoing {
Operation::Restore {
finality: Finality::Final,
Expand Down Expand Up @@ -442,11 +488,13 @@ pub fn restore<'a>(ongoing: &Operation) -> Element<'a> {
}
)
.then_some(style::Button::Negative),
filtered.then_some(Icon::Filter),
filtered.then(|| TRANSLATOR.operation_will_only_include_listed_games()),
)
}

pub fn restore_preview<'a>(ongoing: &Operation) -> Element<'a> {
template(
pub fn restore_preview<'a>(ongoing: &Operation, filtered: bool) -> Element<'a> {
template_extended(
text(match ongoing {
Operation::Restore {
finality: Finality::Preview,
Expand Down Expand Up @@ -482,6 +530,8 @@ pub fn restore_preview<'a>(ongoing: &Operation) -> Element<'a> {
}
)
.then_some(style::Button::Negative),
filtered.then_some(Icon::Filter),
filtered.then(|| TRANSLATOR.operation_will_only_include_listed_games()),
)
}

Expand Down
99 changes: 77 additions & 22 deletions src/gui/game_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,31 +423,16 @@ impl GameList {
let content = self
.entries
.iter()
.filter(|x| {
config.should_show_game(
&x.scan_info.game_name,
.filter(|entry| {
self.filter_game(
entry,
restoring,
x.scan_info.overall_change().is_changed(),
x.scan_info.found_anything(),
config,
manifest,
duplicate_detector,
duplicatees.as_ref(),
)
})
.filter(|x| {
!self.search.show
|| self.search.qualifies(
&x.scan_info,
manifest,
config.is_game_enabled_for_operation(&x.scan_info.game_name, restoring),
config.is_game_customized(&x.scan_info.game_name),
duplicate_detector.is_game_duplicated(&x.scan_info.game_name),
config.scan.show_deselected_games,
)
})
.filter(|x| {
duplicatees
.as_ref()
.map(|xs| xs.contains(&x.scan_info.game_name))
.unwrap_or(true)
})
.fold(
Column::new()
.width(Length::Fill)
Expand Down Expand Up @@ -478,6 +463,76 @@ impl GameList {
.all(|x| config.is_game_enabled_for_operation(&x.scan_info.game_name, restoring))
}

fn filter_game(
&self,
entry: &GameListEntry,
restoring: bool,
config: &Config,
manifest: &Manifest,
duplicate_detector: &DuplicateDetector,
duplicatees: Option<&HashSet<String>>,
) -> bool {
let show = config.should_show_game(
&entry.scan_info.game_name,
restoring,
entry.scan_info.overall_change().is_changed(),
entry.scan_info.found_anything(),
);

let qualifies = self.search.qualifies(
&entry.scan_info,
manifest,
config.is_game_enabled_for_operation(&entry.scan_info.game_name, restoring),
config.is_game_customized(&entry.scan_info.game_name),
duplicate_detector.is_game_duplicated(&entry.scan_info.game_name),
config.scan.show_deselected_games,
);

let duplicate = duplicatees
.as_ref()
.map(|xs| xs.contains(&entry.scan_info.game_name))
.unwrap_or(true);

show && qualifies && duplicate
}

pub fn visible_games(
&self,
restoring: bool,
config: &Config,
manifest: &Manifest,
duplicate_detector: &DuplicateDetector,
) -> Vec<String> {
let duplicatees = self.filter_duplicates_of.as_ref().and_then(|game| {
let mut duplicatees = duplicate_detector.duplicate_games(game);
if duplicatees.is_empty() {
None
} else {
duplicatees.insert(game.clone());
Some(duplicatees)
}
});

self.entries
.iter()
.filter(|entry| {
self.filter_game(
entry,
restoring,
config,
manifest,
duplicate_detector,
duplicatees.as_ref(),
)
})
.map(|x| x.scan_info.game_name.clone())
.collect()
}

pub fn is_filtered(&self) -> bool {
self.search.show || self.filter_duplicates_of.is_some()
}

pub fn compute_operation_status(&self, config: &Config, restoring: bool) -> OperationStatus {
let mut status = OperationStatus::default();
for entry in self.entries.iter() {
Expand Down
8 changes: 4 additions & 4 deletions src/gui/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ impl Backup {
.padding([0, 20])
.spacing(20)
.align_y(Alignment::Center)
.push(button::backup_preview(operation))
.push(button::backup(operation))
.push(button::backup_preview(operation, self.log.is_filtered()))
.push(button::backup(operation, self.log.is_filtered()))
.push(button::toggle_all_scanned_games(
self.log.all_entries_selected(config, false),
))
Expand Down Expand Up @@ -165,8 +165,8 @@ impl Restore {
.padding([0, 20])
.spacing(20)
.align_y(Alignment::Center)
.push(button::restore_preview(operation))
.push(button::restore(operation))
.push(button::restore_preview(operation, self.log.is_filtered()))
.push(button::restore(operation, self.log.is_filtered()))
.push(button::toggle_all_scanned_games(
self.log.all_entries_selected(config, true),
))
Expand Down
4 changes: 4 additions & 0 deletions src/gui/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ impl FilterComponent {
duplicated: Duplication,
show_deselected_games: bool,
) -> bool {
if !self.show {
return true;
}

let fuzzy = self.game_name.is_empty()
|| fuzzy_matcher::skim::SkimMatcherV2::default()
.fuzzy_match(&scan.game_name, &self.game_name)
Expand Down
4 changes: 4 additions & 0 deletions src/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,10 @@ impl Translator {
pub fn custom_game_will_extend(&self) -> String {
translate("custom-game-will-extend")
}

pub fn operation_will_only_include_listed_games(&self) -> String {
translate("operation-will-only-include-listed-games")
}
}

#[cfg(test)]
Expand Down

0 comments on commit c634985

Please sign in to comment.