Pick image component (#295)

* Extract render_possible_image into function

* Seperate render_image into reusable functions

* Seperate game image into component

* Use image component in shortcut overview

* Reuse image pick component in type select

* Reuse component across select_image_type

* Use image type to scale image buttons

Use image type to scale

* Remove old functions
This commit is contained in:
Philip Kristoffersen
2022-12-30 09:22:33 +01:00
committed by GitHub
parent ef72451289
commit 90c0185c8f
8 changed files with 251 additions and 217 deletions
+11
View File
@@ -22,6 +22,17 @@ impl ImageType {
&ALL_TYPES
}
pub fn ratio(&self) -> f32{
match self {
ImageType::Hero => 0.3,
ImageType::Grid => 1.6,
ImageType::WideGrid => 0.5,
ImageType::Logo => 0.2,
ImageType::BigPicture => 0.5,
ImageType::Icon => 1.0,
}
}
pub fn name(&self) -> &str {
match self {
ImageType::Hero => "Hero",
+147
View File
@@ -0,0 +1,147 @@
use std::path::Path;
use egui::{Button, ImageButton};
use futures::executor::block_on;
use tokio::runtime::Runtime;
use crate::steamgriddb::{ImageType, ToDownload};
use crate::ui::images::{clamp_to_width, ImageHandles, TextureDownloadState};
use crate::ui::ui_images::load_image_from_path;
pub fn render_image_from_path(
ui: &mut egui::Ui,
image_handles: &ImageHandles,
path: &Path,
max_width: f32,
text: &str,
) -> bool {
render_possible_image(ui, image_handles, path, max_width, text, &ImageType::Grid, None, None)
}
pub fn render_image_from_path_image_type(
ui: &mut egui::Ui,
image_handles: &ImageHandles,
path: &Path,
max_width: f32,
text: &str,
image_type: &ImageType,
) -> bool {
render_possible_image(ui, image_handles, path, max_width, text, image_type, None, None)
}
pub fn render_image_from_path_or_url(
ui: &mut egui::Ui,
image_handles: &ImageHandles,
path: &Path,
max_width: f32,
text: &str,
image_type: &ImageType,
rt: &Runtime,
url: &str,
) -> bool {
render_possible_image(
ui,
image_handles,
path,
max_width,
text,
image_type,
Some(rt),
Some(url),
)
}
fn render_possible_image(
ui: &mut egui::Ui,
image_handles: &ImageHandles,
path: &Path,
max_width: f32,
text: &str,
image_type: &ImageType,
rt: Option<&Runtime>,
url: Option<&str>,
) -> bool {
let image_key = path.to_string_lossy().to_string();
match image_handles.get_mut(&image_key) {
Some(mut state) => {
match state.value() {
TextureDownloadState::Downloading => {
ui.ctx().request_repaint();
//nothing to do,just wait
ui.spinner();
}
TextureDownloadState::Downloaded => {
//Need to load
let image_data = load_image_from_path(&path);
match image_data {
Ok(image_data) => {
let handle = ui.ctx().load_texture(
&image_key,
image_data,
egui::TextureOptions::LINEAR,
);
*state.value_mut() = TextureDownloadState::Loaded(handle);
ui.spinner();
}
Err(_) => *state.value_mut() = TextureDownloadState::Failed,
}
ui.ctx().request_repaint();
}
TextureDownloadState::Loaded(texture_handle) => {
//need to show
let mut size = texture_handle.size_vec2();
clamp_to_width(&mut size, max_width);
let image_button = ImageButton::new(texture_handle, size);
if ui
.add_sized(size, image_button)
.on_hover_text(text)
.clicked()
{
return true;
}
}
TextureDownloadState::Failed => {
let button =
ui.add_sized([max_width, max_width * image_type.ratio()], Button::new(text).wrap(true));
if button.clicked() {
return true;
}
}
}
}
None => {
if !path.exists() && url.is_none() {
image_handles.insert(image_key, TextureDownloadState::Failed);
} else {
//We need to start a download
//Redownload if file is too small
if !path.exists()
|| std::fs::metadata(path).map(|m| m.len()).unwrap_or_default() < 2
{
image_handles.insert(image_key.clone(), TextureDownloadState::Downloading);
let to_download = ToDownload {
path: path.to_path_buf(),
url: url.unwrap().to_string(),
app_name: "Thumbnail".to_string(),
image_type: *image_type,
};
let image_handles = image_handles.clone();
let image_key = image_key.clone();
if let Some(rt) = rt {
rt.spawn_blocking(move || {
block_on(crate::steamgriddb::download_to_download(&to_download))
.unwrap();
image_handles.insert(image_key, TextureDownloadState::Downloaded);
});
}
} else {
image_handles.insert(image_key.clone(), TextureDownloadState::Downloaded);
}
}
}
}
false
}
+4
View File
@@ -1,3 +1,7 @@
mod steam_user_select;
mod game_image_button;
pub use steam_user_select::render_user_select;
pub use game_image_button::render_image_from_path;
pub use game_image_button::render_image_from_path_or_url;
pub use game_image_button::render_image_from_path_image_type;
+4 -1
View File
@@ -8,6 +8,9 @@ use crate::{steam::SteamUsersInfo, steamgriddb::ImageType, ui::FetcStatus};
use super::{ gamemode::GameMode, possible_image::PossibleImage, texturestate::TextureDownloadState, gametype::GameType};
use tokio::sync::watch::{self, Receiver};
pub type ImageHandles = std::sync::Arc<DashMap<String, TextureDownloadState>>;
pub struct ImageSelectState {
pub selected_shortcut: Option<GameType>,
pub grid_id: Option<usize>,
@@ -20,7 +23,7 @@ pub struct ImageSelectState {
pub image_type_selected: Option<ImageType>,
pub image_options: Receiver<FetcStatus<Vec<PossibleImage>>>,
pub steam_games: Option<Vec<crate::steam::SteamGameInfo>>,
pub image_handles: std::sync::Arc<DashMap<String, TextureDownloadState>>,
pub image_handles: ImageHandles,
pub possible_names: Option<Vec<steamgriddb_api::search::SearchResult>>,
}
+3
View File
@@ -12,3 +12,6 @@ mod constants;
mod pages;
pub use image_select_state::ImageSelectState;
pub use image_select_state::ImageHandles;
pub use texturestate::TextureDownloadState;
pub use image_resize::clamp_to_width;
+22 -92
View File
@@ -1,19 +1,20 @@
use std::path::Path;
use egui::{Grid, ImageButton};
use egui::{Grid, };
use futures::executor::block_on;
use tokio::sync::watch;
use tokio::{ sync::watch};
use crate::{
steamgriddb::{get_image_extension, ImageType, ToDownload},
ui::{
images::{
constants::MAX_WIDTH, image_resize::clamp_to_width,
image_select_state::ImageSelectState, possible_image::PossibleImage,
texturestate::TextureDownloadState, useraction::UserAction, hasimagekey::HasImageKey,
constants::MAX_WIDTH,
hasimagekey::HasImageKey,
image_select_state::{ ImageSelectState},
possible_image::PossibleImage,
useraction::UserAction,
},
ui_images::load_image_from_path,
FetcStatus, MyEguiApp,
FetcStatus, MyEguiApp, components::render_image_from_path_or_url,
},
};
@@ -59,88 +60,18 @@ pub fn render_page_pick_image(
.spacing([column_padding, column_padding])
.show(ui, |ui| {
for image in images {
let image_key =
image.thumbnail_path.as_path().to_string_lossy().to_string();
match state.image_handles.get_mut(&image_key) {
Some(mut state) => {
match state.value() {
TextureDownloadState::Downloading => {
ui.ctx().request_repaint();
//nothing to do,just wait
ui.spinner();
}
TextureDownloadState::Downloaded => {
//Need to load
let image_data =
load_image_from_path(&image.thumbnail_path);
match image_data {
Ok(image_data) => {
let handle = ui.ctx().load_texture(
&image_key,
image_data,
egui::TextureOptions::LINEAR,
);
*state.value_mut() =
TextureDownloadState::Loaded(handle);
ui.spinner();
}
Err(_) => {
*state.value_mut() = TextureDownloadState::Failed
}
}
ui.ctx().request_repaint();
}
TextureDownloadState::Loaded(texture_handle) => {
//need to show
let mut size = texture_handle.size_vec2();
clamp_to_width(&mut size, column_width);
let image_button = ImageButton::new(texture_handle, size);
if ui.add_sized(size, image_button).clicked() {
return Some(UserAction::ImageSelected(image.clone()));
}
}
TextureDownloadState::Failed => {
ui.label("Failed to load image");
}
}
}
None => {
//We need to start a download
let image_handles = &app.image_selected_state.image_handles;
let path = &image.thumbnail_path;
//Redownload if file is too small
if !path.exists()
|| std::fs::metadata(path).map(|m| m.len()).unwrap_or_default()
< 2
{
image_handles.insert(
image_key.clone(),
TextureDownloadState::Downloading,
);
let to_download = ToDownload {
path: path.clone(),
url: image.thumbnail_url.clone(),
app_name: "Thumbnail".to_string(),
image_type: *image_type,
};
let image_handles = image_handles.clone();
let image_key = image_key.clone();
app.rt.spawn_blocking(move || {
block_on(crate::steamgriddb::download_to_download(
&to_download,
))
.unwrap();
image_handles
.insert(image_key, TextureDownloadState::Downloaded);
});
} else {
image_handles.insert(
image_key.clone(),
TextureDownloadState::Downloaded,
);
}
}
let path = image.thumbnail_path.as_path();
if render_image_from_path_or_url(
ui,
&state.image_handles,
&path,
column_width,
&image.full_url,
image_type,
&app.rt,
&image.thumbnail_url,
) {
return Some(image.clone());
}
column += 1;
if column >= columns {
@@ -148,12 +79,11 @@ pub fn render_page_pick_image(
ui.end_row();
}
}
None
})
.inner;
if x.is_some() {
return x;
if let Some(x) = x {
return Some(UserAction::ImageSelected(x));
}
}
_ => {
+46 -90
View File
@@ -1,78 +1,61 @@
use std::path::Path;
use egui::ImageButton;
use crate::{ui::images::{image_select_state::ImageSelectState, useraction::UserAction, gametype::GameType, texturestate::TextureDownloadState, hasimagekey::HasImageKey, image_resize::clamp_to_width}, steamgriddb::ImageType};
use crate::ui::images::{
gametype::GameType, hasimagekey::HasImageKey, image_select_state::ImageSelectState,
useraction::UserAction, ImageHandles,
};
use crate::{steamgriddb::ImageType, ui::components::render_image_from_path_image_type};
const MAX_WIDTH: f32 = 300.;
pub fn render_page_shortcut_select_image_type(ui: &mut egui::Ui, state: &ImageSelectState) -> Option<UserAction> {
pub fn render_page_shortcut_select_image_type(
ui: &mut egui::Ui,
state: &ImageSelectState,
) -> Option<UserAction> {
let shortcut = state.selected_shortcut.as_ref().unwrap();
let user_path = &state.steam_user.as_ref().unwrap().steam_user_data_folder;
let thumbnail = |ui: &mut egui::Ui, image_type: &ImageType| {
if render_thumbnail(ui, &state.image_handles, shortcut, image_type, user_path) {
Some(UserAction::ImageTypeSelected(*image_type))
} else {
None
}
};
let x = if ui.available_width() > MAX_WIDTH * 3. {
ui.horizontal(|ui| {
let x = ui
.vertical(|ui| {
let texture =
texture_from_iamge_type(shortcut, &ImageType::Grid, user_path, state);
ui.label(ImageType::Grid.name());
if render_thumbnail(ui, texture).clicked() {
return Some(UserAction::ImageTypeSelected(ImageType::Grid));
}
None
})
.inner;
let x = ui.vertical(|ui| thumbnail(ui, &ImageType::Grid)).inner;
if x.is_some() {
return x;
}
let x = ui
.vertical(|ui| {
let texture =
texture_from_iamge_type(shortcut, &ImageType::Hero, user_path, state);
ui.label(ImageType::Hero.name());
if render_thumbnail(ui, texture).clicked() {
return Some(UserAction::ImageTypeSelected(ImageType::Hero));
}
let texture =
texture_from_iamge_type(shortcut, &ImageType::WideGrid, user_path, state);
ui.label(ImageType::WideGrid.name());
if render_thumbnail(ui, texture).clicked() {
return Some(UserAction::ImageTypeSelected(ImageType::WideGrid));
}
let texture =
texture_from_iamge_type(shortcut, &ImageType::Logo, user_path, state);
ui.label(ImageType::Logo.name());
if render_thumbnail(ui, texture).clicked() {
return Some(UserAction::ImageTypeSelected(ImageType::Logo));
}
None
let types = &[ImageType::Hero, ImageType::WideGrid, ImageType::Logo];
types
.iter()
.flat_map(|image_type| thumbnail(ui, image_type))
.next()
})
.inner;
if x.is_some() {
return x;
}
ui.vertical(|ui| {
let texture = texture_from_iamge_type(shortcut, &ImageType::Icon, user_path, state);
ui.label(ImageType::Icon.name());
if render_thumbnail(ui, texture).clicked() {
return Some(UserAction::ImageTypeSelected(ImageType::Icon));
}
let texture =
texture_from_iamge_type(shortcut, &ImageType::BigPicture, user_path, state);
ui.label(ImageType::BigPicture.name());
if render_thumbnail(ui, texture).clicked() {
return Some(UserAction::ImageTypeSelected(ImageType::BigPicture));
}
None
let types = &[ImageType::Icon, ImageType::BigPicture];
types
.iter()
.flat_map(|image_type| thumbnail(ui, image_type))
.next()
})
.inner
})
.inner
} else {
render_image_types_as_list(shortcut, user_path, state, ui)
let types = ImageType::all();
types
.iter()
.flat_map(|image_type| thumbnail(ui, image_type))
.next()
};
if ui
@@ -84,48 +67,21 @@ pub fn render_page_shortcut_select_image_type(ui: &mut egui::Ui, state: &ImageSe
x
}
fn render_image_types_as_list(
shortcut: &GameType,
user_path: &String,
state: &ImageSelectState,
fn render_thumbnail(
ui: &mut egui::Ui,
) -> Option<UserAction> {
let types = ImageType::all();
for image_type in types {
let texture = texture_from_iamge_type(shortcut, image_type, user_path, state);
let response = ui
.vertical(|ui| {
ui.label(image_type.name());
render_thumbnail(ui, texture)
})
.inner;
if response.clicked() {
return Some(UserAction::ImageTypeSelected(*image_type));
}
}
None
}
fn texture_from_iamge_type(
image_handles: &ImageHandles,
shortcut: &GameType,
image_type: &ImageType,
user_path: &String,
state: &ImageSelectState,
) -> Option<egui::TextureHandle> {
let (_path, key) = shortcut.key(image_type, Path::new(&user_path));
state.image_handles.get(&key).and_then(|k| match k.value() {
TextureDownloadState::Loaded(texture) => Some(texture.clone()),
_ => None,
})
}
fn render_thumbnail(ui: &mut egui::Ui, image: Option<egui::TextureHandle>) -> egui::Response {
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);
ui.add(image_button)
} else {
ui.button("Pick an image")
}
) -> bool {
let (path, _key) = shortcut.key(image_type, Path::new(&user_path));
let text = format!("Pick {} image", image_type.name());
render_image_from_path_image_type(
ui,
image_handles,
path.as_path(),
MAX_WIDTH,
&text,
image_type,
)
}
@@ -1,6 +1,5 @@
use std::path::Path;
use egui::{Button, ImageButton};
use steam_shortcuts_util::shortcut::ShortcutOwned;
use crate::{
@@ -8,10 +7,10 @@ use crate::{
steamgriddb::{ImageType, CachedSearch},
ui::{
images::{
gametype::GameType, hasimagekey::HasImageKey, image_resize::clamp_to_width,
gametype::GameType, hasimagekey::HasImageKey,
texturestate::TextureDownloadState, useraction::UserAction,
},
MyEguiApp, ui_images::load_image_from_path,
MyEguiApp, ui_images::load_image_from_path, components::render_image_from_path,
},
};
@@ -65,27 +64,8 @@ fn render_image(
&ImageType::Grid,
Path::new(&user_info.steam_user_data_folder),
);
let mut clicked = false;
let texture = app.image_selected_state.image_handles.get(&key);
if let Some(texture) = texture {
if let TextureDownloadState::Loaded(texture) = &texture.value() {
let mut size = texture.size_vec2();
clamp_to_width(&mut size, column_width);
let image_button = ImageButton::new(texture, size);
clicked = ui
.add(image_button)
.on_hover_text(&shortcut.app_name)
.clicked();
}
} else {
let button = ui.add_sized(
[column_width, column_width * 1.6],
Button::new(&shortcut.app_name).wrap(true),
);
clicked = clicked || button.clicked();
}
let clicked= render_image_from_path(ui, &app.image_selected_state.image_handles, Path::new(&key), column_width, &shortcut.app_name);
if clicked {
return Some(Some(UserAction::ShortcutSelected(GameType::Shortcut(
shortcut.clone(),