mirror of
https://github.com/djibux/BoilR.git
synced 2026-09-01 05:53:41 +02:00
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:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>>,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user