From 365757c5b5af5475204108f913b167aaa1fea84e Mon Sep 17 00:00:00 2001 From: Philip Kristoffersen Date: Tue, 26 Apr 2022 20:15:25 +0200 Subject: [PATCH] Multiple improvements for steamdeck (#99) Paste the SteamgridDB API key in from clipboard automatically if it looks like a API key. This is nice for steam deck so you don't have to do a right-click paste or worse manually write in the key. BoilR guesses what game it should find images for based on the game name, but it might be wrong, it is now possible to correct this No longer show a back button on the user select screen (it did nothing) Now you can change the images for ALL your shortcuts, not just the BoilR ones. --- Cargo.lock | 1 + Cargo.toml | 3 +- src/ui/ui_image_download.rs | 193 +++++++++++++++++++++++++----------- src/ui/ui_settings.rs | 19 ++++ 4 files changed, 155 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a7a6ae..763d352 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,7 @@ version = "1.2.0" dependencies = [ "base64", "config", + "copypasta", "dashmap", "eframe", "egui", diff --git a/Cargo.toml b/Cargo.toml index 39cc181..2949a5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,9 +26,10 @@ image = { version = "0.24.1", features = ["png"] } toml = { version = "^0.5.8" } sysinfo = "0.23.10" sqlite = "0.26.0" +copypasta = "0.7.1" [target.'cfg(windows)'.dependencies] winreg = "0.10.1" [target.'cfg(windows)'.build-dependencies] -winres = "0.1" \ No newline at end of file +winres = "0.1" diff --git a/src/ui/ui_image_download.rs b/src/ui/ui_image_download.rs index b5a9b1d..a6635d5 100644 --- a/src/ui/ui_image_download.rs +++ b/src/ui/ui_image_download.rs @@ -27,11 +27,14 @@ pub struct ImageSelectState { pub steam_user: Option, pub steam_users: Option>, + pub user_shortcuts: Option>, pub image_type_selected: Option, pub image_options: Receiver>>, pub image_handles: std::sync::Arc>, + + pub possible_names: Option>, } pub enum TextureState { @@ -69,7 +72,9 @@ impl Default for ImageSelectState { wide_image: Default::default(), steam_user: Default::default(), steam_users: Default::default(), + user_shortcuts: Default::default(), image_type_selected: Default::default(), + possible_names: None, image_options: watch::channel(FetcStatus::NeedsFetched).1, image_handles: Arc::new(DashMap::new()), } @@ -78,6 +83,7 @@ impl Default for ImageSelectState { #[derive(Debug)] enum UserAction { + CorrectGridId, UserSelected(SteamUsersInfo), ShortcutSelected(ShortcutOwned), ImageTypeSelected(ImageType), @@ -91,7 +97,7 @@ impl MyEguiApp { fn render_ui_image_action(&self, ui: &mut egui::Ui) -> UserAction { let state = &self.image_selected_state; ui.heading("Images"); - if (state.selected_shortcut.is_some() || state.has_multiple_users()) + if (state.selected_shortcut.is_some() || (state.has_multiple_users() && state.steam_user.is_some())) && ui.button("Back").clicked() { return UserAction::BackButton; @@ -101,12 +107,19 @@ impl MyEguiApp { } if let Some(shortcut) = state.selected_shortcut.as_ref() { ui.heading(&shortcut.app_name); - if let Some(image_type) = state.image_type_selected.as_ref() { - if let Some(action) = self.render_possible_images(ui, image_type, state) { + + if let Some(possible_names) = state.possible_names.as_ref() { + if let Some(value) = render_possible_names(possible_names, ui) { + return value; + } + } else { + if let Some(image_type) = state.image_type_selected.as_ref() { + if let Some(action) = self.render_possible_images(ui, image_type, state) { + return action; + } + } else if let Some(action) = render_shortcut_images(ui, state) { return action; } - } else if let Some(action) = render_shortcut_images(ui, state) { - return action; } } else if let Some(action) = self.render_shortcut_select(ui) { return action; @@ -115,20 +128,17 @@ impl MyEguiApp { } fn render_shortcut_select(&self, ui: &mut egui::Ui) -> Option { - match &*self.games_to_sync.borrow() { - FetcStatus::Fetched(shortcuts) => { - for (platform_name, shortcuts) in shortcuts { - ui.heading(platform_name); - for shortcut in shortcuts { - if ui.button(&shortcut.app_name).clicked() { - return Some(UserAction::ShortcutSelected(shortcut.clone())); - } + let shortcuts = &self.image_selected_state.user_shortcuts; + match shortcuts { + Some(shortcuts) => { + for shortcut in shortcuts { + if ui.button(&shortcut.app_name).clicked() { + return Some(UserAction::ShortcutSelected(shortcut.clone())); } } } - _ => { - ui.ctx().request_repaint(); - ui.label("Finding installed games"); + None => { + ui.label("Could not find any shortcuts"); } } None @@ -245,19 +255,40 @@ impl MyEguiApp { self.handle_image_selected(image); } UserAction::BackButton => { - let state = &mut self.image_selected_state; - handle_back_button_action(state); + self.handle_back_button_action(); } UserAction::GridIdChanged(grid_id) => { self.handle_grid_change(grid_id); } UserAction::NoAction => {} + UserAction::CorrectGridId => { + self.handle_correct_grid_request(); + } }; } + fn handle_correct_grid_request(&mut self) { + let app_name = self + .image_selected_state + .selected_shortcut + .as_ref() + .map(|s| s.app_name.clone()) + .unwrap_or_default(); + let auth_key = self + .settings + .steamgrid_db + .auth_key + .clone() + .unwrap_or_default(); + let client = steamgriddb_api::Client::new(&auth_key); + let search_results = self.rt.block_on(client.search(&app_name)); + self.image_selected_state.possible_names = search_results.ok(); + } + fn handle_grid_change(&mut self, grid_id: usize) { self.image_selected_state.grid_id = Some(grid_id); + self.image_selected_state.possible_names = None; if let Some(auth_key) = &self.settings.steamgrid_db.auth_key { let client = steamgriddb_api::Client::new(auth_key); let mut cache = CachedSearch::new(&client); @@ -270,6 +301,7 @@ impl MyEguiApp { fn handle_user_selected(&mut self, user: SteamUsersInfo) { let state = &mut self.image_selected_state; + state.user_shortcuts = Some(crate::steam::get_shortcuts_for_user(&user).shortcuts); state.steam_user = Some(user); } @@ -332,29 +364,36 @@ impl MyEguiApp { app_name, image_type: *selected_image_type, }; - //TODO make this actually parallel self.rt.spawn_blocking(move || { let _ = block_on(crate::steamgriddb::download_to_download(&to_download)); }); - let image_ref = match selected_image_type { - ImageType::Hero => &mut self.image_selected_state.hero_image, - ImageType::Grid => &mut self.image_selected_state.grid_image, - ImageType::WideGrid => &mut self.image_selected_state.wide_image, - ImageType::Logo => &mut self.image_selected_state.logo_image, - ImageType::BigPicture => &mut self.image_selected_state.wide_image, - ImageType::Icon => &mut self.image_selected_state.icon_image, - }; - let image_key = image.thumbnail_path.as_path().to_string_lossy().to_string(); - let texture_handle = self - .image_selected_state - .image_handles - .get(&image_key) - .unwrap(); - if let TextureState::Loaded(texture_handle) = texture_handle.value() { - *image_ref = Some(texture_handle.clone()); + + { + let image_ref = match selected_image_type { + ImageType::Hero => &mut self.image_selected_state.hero_image, + ImageType::Grid => &mut self.image_selected_state.grid_image, + ImageType::WideGrid => &mut self.image_selected_state.wide_image, + ImageType::Logo => &mut self.image_selected_state.logo_image, + ImageType::BigPicture => &mut self.image_selected_state.wide_image, + ImageType::Icon => &mut self.image_selected_state.icon_image, + }; + let image_key = image.thumbnail_path.as_path().to_string_lossy().to_string(); + let texture_handle = self + .image_selected_state + .image_handles + .get(&image_key) + .unwrap(); + if let TextureState::Loaded(texture_handle) = texture_handle.value() { + *image_ref = Some(texture_handle.clone()); + } + self.image_selected_state.image_type_selected = None; + self.image_selected_state.image_options = watch::channel(FetcStatus::NeedsFetched).1; } - self.image_selected_state.image_type_selected = None; - self.image_selected_state.image_options = watch::channel(FetcStatus::NeedsFetched).1; + self.clear_loaded_images(); + } + + fn clear_loaded_images(&mut self) { + self.image_selected_state.image_handles.clear(); } fn handle_shortcut_selected(&mut self, shortcut: ShortcutOwned, ui: &mut egui::Ui) { @@ -364,7 +403,6 @@ impl MyEguiApp { if let Some(auth_key) = &self.settings.steamgrid_db.auth_key { let client = steamgriddb_api::Client::new(auth_key); let search = CachedSearch::new(&client); - //TODO make this multithreaded state.grid_id = self .rt .block_on(search.search(shortcut.app_id, &shortcut.app_name)) @@ -375,7 +413,6 @@ impl MyEguiApp { let folder = Path::new(&user.steam_user_data_folder) .join("config") .join("grid"); - //TODO put this in seperate thread state.hero_image = get_image(ui, &shortcut, &folder, &ImageType::Hero); state.grid_image = get_image(ui, &shortcut, &folder, &ImageType::Grid); state.icon_image = get_image(ui, &shortcut, &folder, &ImageType::Icon); @@ -383,6 +420,38 @@ impl MyEguiApp { state.wide_image = get_image(ui, &shortcut, &folder, &ImageType::WideGrid); state.selected_shortcut = Some(shortcut); } + + fn handle_back_button_action(&mut self) { + let state = &mut self.image_selected_state; + if state.possible_names.is_some() { + state.possible_names = None; + } else if state.image_type_selected.is_some() { + state.image_type_selected = None; + state.image_handles.clear(); + } else if state.selected_shortcut.is_some() { + state.selected_shortcut = None; + state.hero_image = None; + state.grid_image = None; + state.icon_image = None; + state.logo_image = None; + state.wide_image = None; + } else { + state.user_shortcuts = None; + state.steam_user = None; + } + } +} + +fn render_possible_names( + possible_names: &Vec, + ui: &mut egui::Ui, +) -> Option { + for possible in possible_names { + if ui.button(&possible.name).clicked() { + return Some(UserAction::GridIdChanged(possible.id)); + } + } + None } fn render_shortcut_images(ui: &mut egui::Ui, state: &ImageSelectState) -> Option { @@ -392,10 +461,16 @@ fn render_shortcut_images(ui: &mut egui::Ui, state: &ImageSelectState) -> Option return Some(UserAction::GridIdChanged(grid_id)); } }; + if ui + .button("Click here if the images are for a wrong game") + .clicked() + { + return Some(UserAction::CorrectGridId); + } for image_type in ImageType::all() { ui.label(image_type.name()); let image_ref = get_image_ref(image_type, state); - if render_thumbnail(ui, image_ref) { + if render_thumbnail(ui, image_ref, &image_type) { return Some(UserAction::ImageTypeSelected(*image_type)); } } @@ -415,29 +490,27 @@ fn render_user_select(state: &ImageSelectState, ui: &mut egui::Ui) -> UserAction UserAction::NoAction } -fn handle_back_button_action(state: &mut ImageSelectState) { - if state.image_type_selected.is_some() { - state.image_type_selected = None; - } else if state.selected_shortcut.is_some() { - state.selected_shortcut = None; - } else { - state.steam_user = None; - } -} - const MAX_WIDTH: f32 = 300.; -fn render_thumbnail(ui: &mut egui::Ui, image: &Option) -> bool { - match image { - Some(texture) => { - let mut size = texture.size_vec2(); - clamp_to_width(&mut size, MAX_WIDTH); - let image_button = ImageButton::new(texture, size); - ui.add(image_button) - .on_hover_text("Click to change image") - .clicked() +fn render_thumbnail(ui: &mut egui::Ui, image: &Option, image_type:&ImageType) -> bool { + if let Some(texture) = image { + let mut size = texture.size_vec2(); + clamp_to_width(&mut size, MAX_WIDTH); + let image_button = ImageButton::new(texture, size); + let added =ui.add(image_button); + match image_type{ + ImageType::Icon => false, + _=> added.on_hover_text("Click to change image").clicked() + } + } else { + match image_type{ + ImageType::Icon => { + ui.label("No icon found"); + false + }, + _=> + ui.button("Pick an image").clicked(), } - None => ui.button("Pick an image").clicked(), } } diff --git a/src/ui/ui_settings.rs b/src/ui/ui_settings.rs index 6c43d5d..31063fb 100644 --- a/src/ui/ui_settings.rs +++ b/src/ui/ui_settings.rs @@ -1,3 +1,4 @@ +use copypasta::ClipboardProvider; use eframe::egui; use egui::ScrollArea; @@ -181,10 +182,24 @@ impl MyEguiApp { .auth_key .as_mut() .unwrap_or(&mut empty_string); + let mut auth_key_clipboard = false; ui.label("Authentication key: "); + if let Ok(mut clipboard_ctx) = copypasta::ClipboardContext::new(){ + if let Ok(content) = clipboard_ctx.get_contents(){ + if auth_key.is_empty() && looks_like_auth_key(&content) { + *auth_key = content.clone(); + auth_key_clipboard = true; + }else if content.eq(auth_key){ + auth_key_clipboard = true; + } + } + } if ui.text_edit_singleline(auth_key).changed() { self.settings.steamgrid_db.auth_key = Some(auth_key.to_string()); } + if auth_key_clipboard{ + ui.label("Found API Key in clipboard"); + } }); ui.horizontal(|ui| { ui.label( @@ -275,3 +290,7 @@ impl MyEguiApp { } } } + +fn looks_like_auth_key(content: &String) -> bool { + content.len() == 32 && content.is_ascii() && content.chars().all(char::is_alphanumeric) && content.chars().all(char::is_lowercase) +}