diff --git a/crates/bevy_rpack/Cargo.toml b/crates/bevy_rpack/Cargo.toml index 5b25cc3..e1883fd 100644 --- a/crates/bevy_rpack/Cargo.toml +++ b/crates/bevy_rpack/Cargo.toml @@ -2,7 +2,7 @@ name = "bevy_rpack" description = "Bevy plugin with rpack atlas support" version = "0.2.0" -edition = "2021" +edition = "2024" repository = "https://github.com/Leinnan/rpack.git" homepage = "https://github.com/Leinnan/rpack" authors = ["Piotr Siuszko "] @@ -21,7 +21,7 @@ bevy = { version = "0.16", optional = true, default-features = false, features = "bevy_image", "bevy_ui" ] } -serde = { version = "1.0", features = ["derive"] } +serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "2" diff --git a/crates/rpack_cli/Cargo.toml b/crates/rpack_cli/Cargo.toml index ca5b924..c84e18a 100644 --- a/crates/rpack_cli/Cargo.toml +++ b/crates/rpack_cli/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/Leinnan/rpack.git" homepage = "https://github.com/Leinnan/rpack" license = "MIT OR Apache-2.0" version = "0.2.0" -edition = "2021" +edition = "2024" [features] default = ["cli", "dds"] diff --git a/crates/rpack_egui/Cargo.toml b/crates/rpack_egui/Cargo.toml index 7a30419..4faef7d 100644 --- a/crates/rpack_egui/Cargo.toml +++ b/crates/rpack_egui/Cargo.toml @@ -1,24 +1,23 @@ [package] name = "rpack_egui" -version = "0.2.0" +version = "0.3.0" description = "GUI application for generating rpack atlases" authors = ["Piotr Siuszko "] -edition = "2021" -rust-version = "1.81" +edition = "2024" repository = "https://github.com/Leinnan/rpack.git" homepage = "https://github.com/Leinnan/rpack" license = "MIT OR Apache-2.0" [dependencies] -egui = "0.30" -eframe = { version = "0.30", default-features = false, features = [ +egui = "0.32" +eframe = { version = "0.32", default-features = false, features = [ "accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies. "default_fonts", # Embed the default egui fonts. "glow", # Use the glow rendering backend. Alternative: "wgpu". "persistence", # Enable restoring app state when restarting the app. ] } log = "0.4" -egui_json_tree = "0.10" +egui_json_tree = "0.13" rpack_cli = { default-features = false, path = "../rpack_cli", version = "0.2" } # You only need serde if you want app persistence: @@ -26,7 +25,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" texture_packer = { workspace = true } image = { workspace = true } -egui_extras = { version = "0.30", features = ["all_loaders"] } +egui_extras = { version = "0.32", features = ["all_loaders"] } rfd = { version = "0.15", features = [] } wasm-bindgen-futures = "0.4" anyhow = "1" diff --git a/crates/rpack_egui/rust-toolchain b/crates/rpack_egui/rust-toolchain deleted file mode 100644 index 6df2d73..0000000 --- a/crates/rpack_egui/rust-toolchain +++ /dev/null @@ -1,10 +0,0 @@ -# If you see this, run "rustup self update" to get rustup 1.23 or newer. - -# NOTE: above comment is for older `rustup` (before TOML support was added), -# which will treat the first line as the toolchain name, and therefore show it -# to the user in the error, instead of "error: invalid channel name '[toolchain]'". - -[toolchain] -channel = "1.81" -components = [ "rustfmt", "clippy" ] -targets = [ "wasm32-unknown-unknown" ] diff --git a/crates/rpack_egui/src/app.rs b/crates/rpack_egui/src/app.rs index 6ef2605..e024346 100644 --- a/crates/rpack_egui/src/app.rs +++ b/crates/rpack_egui/src/app.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; -use egui::{CollapsingHeader, Color32, DroppedFile, FontFamily, FontId, Image, RichText}; -use image::GenericImageView; +use egui::{ + CollapsingHeader, Color32, DroppedFile, FontFamily, FontId, Grid, Image, Label, RichText, +}; use rpack_cli::{ImageFile, Spritesheet, SpritesheetError}; use texture_packer::TexturePackerConfig; @@ -33,8 +34,22 @@ pub struct Application { #[serde(skip)] max_size: u32, #[serde(skip)] - image_data: HashMap, + image_data: HashMap, } + +pub struct AppImageData { + pub width: u32, + pub height: u32, + pub data: ImageFile, + pub path: String, +} + +impl AppImageData { + pub fn id(&self) -> &str { + self.data.id.as_str() + } +} + impl Default for Application { fn default() -> Self { Self { @@ -71,7 +86,7 @@ impl Application { self.image_data = self .dropped_files .iter() - .flat_map(|f| f.create_image(&prefix)) + .flat_map(|f| f.create_image(&prefix).map(|i| (i.id().to_string(), i))) .collect(); self.update_min_size(); } @@ -79,18 +94,18 @@ impl Application { if let Some(file) = self .image_data .values() - .max_by(|a, b| a.image.width().cmp(&b.image.width())) + .max_by(|a, b| a.width.cmp(&b.width)) { - self.min_size[0] = file.image.width(); + self.min_size[0] = file.width; } else { self.min_size[0] = 32; } if let Some(file) = self .image_data .values() - .max_by(|a, b| a.image.height().cmp(&b.image.height())) + .max_by(|a, b| a.height.cmp(&b.height)) { - self.min_size[1] = file.image.height(); + self.min_size[1] = file.height; } else { self.min_size[1] = 32; } @@ -114,7 +129,11 @@ impl Application { fn build_atlas(&mut self, ctx: &egui::Context) { self.data = None; self.image = None; - let images: Vec = self.image_data.values().cloned().collect(); + let images: Vec = self + .image_data + .values() + .map(|file| file.data.clone()) + .collect(); for size in [32, 64, 128, 256, 512, 1024, 2048, 4096] { if size < self.min_size[0] || size < self.min_size[1] { @@ -328,25 +347,41 @@ impl eframe::App for Application { CollapsingHeader::new("Image list") .default_open(true) .show(ui, |ui| { - if !self.image_data.is_empty() && ui.button("clear list").clicked() { - self.image_data.clear(); - self.dropped_files.clear(); - self.data = None; - self.update_min_size(); - } - let mut to_remove: Option = None; - for (id, file) in self.image_data.iter() { - ui.horizontal_top(|ui| { - ui.add_space(10.0); - if ui.button("x").clicked() { - to_remove = Some(id.clone()); + ui.horizontal(|ui|{ + + if !self.image_data.is_empty() && ui.button("clear list").clicked() { + self.image_data.clear(); + self.dropped_files.clear(); + self.data = None; + self.update_min_size(); + } + ui.add_space(10.0); + #[cfg(not(target_arch = "wasm32"))] + if ui.button("Add").clicked() { + if let Some(files) = rfd::FileDialog::new().set_title("Add images").add_filter("Images", &["png", "jpg", "jpeg","dds"]).pick_files(){ + for file in files.iter() { + let Ok(image) = texture_packer::importer::ImageImporter::import_from_file(file) else { continue }; + let id = crate::helpers::id_from_path(&file.to_string_lossy()); + self.image_data.insert(file.to_string_lossy().to_string(), AppImageData { width: image.width(), height: image.height(), data: ImageFile { id: id, image }, path: file.to_string_lossy().to_string() }); + } + self.update_min_size(); } - ui.add_space(10.0); - let (x, y) = file.image.dimensions(); - ui.label(&file.id) - .on_hover_text(format!("Dimensions: {}x{}", x, y)); - }); - } + } + }); + let mut to_remove: Option = None; + Grid::new("Image List").num_columns(4).striped(true).spacing((10.0,10.0)).show(ui, |ui|{ + + for (id, file) in self.image_data.iter() { + if ui.button("x").clicked() { + to_remove = Some(id.clone()); + } + + ui.image(format!("file://{}", file.path)); + ui.add(Label::new(file.id()).selectable(false)); + ui.add(Label::new(format!("{}x{}", file.width, file.height)).selectable(false)); + ui.end_row(); + } + }); if let Some(index) = to_remove { if let Some(i) = self .dropped_files @@ -377,10 +412,13 @@ impl eframe::App for Application { if self.dropped_files.is_empty() { ui.vertical_centered_justified(|ui| { ui.add_space(50.0); - ui.label( - RichText::new("Drop images here first") - .heading() - .color(MY_ACCENT_COLOR32), + ui.add( + Label::new( + RichText::new("Drop images here first") + .heading() + .color(MY_ACCENT_COLOR32), + ) + .selectable(false), ); }); } @@ -444,10 +482,8 @@ impl eframe::App for Application { .add(egui::Button::new("Copy JSON to Clipboard")) .clicked() { - ui.output_mut(|o| { - o.copied_text = - data.atlas_asset_json.to_string(); - }); + ui.ctx() + .copy_text(data.atlas_asset_json.to_string()); }; } }); diff --git a/crates/rpack_egui/src/fonts.rs b/crates/rpack_egui/src/fonts.rs index 00e1028..8d44cd1 100644 --- a/crates/rpack_egui/src/fonts.rs +++ b/crates/rpack_egui/src/fonts.rs @@ -55,8 +55,8 @@ fn get_fonts() -> anyhow::Result<(Vec, Vec)> { fn get_fonts() -> anyhow::Result<(Vec, Vec)> { let font_path = std::path::Path::new("/System/Library/Fonts"); - let regular = fs::read(font_path.join("SFNSRounded.ttf"))?; - let semibold = fs::read(font_path.join("SFCompact.ttf"))?; + let regular = std::fs::read(font_path.join("SFNSRounded.ttf"))?; + let semibold = std::fs::read(font_path.join("SFCompact.ttf"))?; Ok((regular, semibold)) } diff --git a/crates/rpack_egui/src/helpers.rs b/crates/rpack_egui/src/helpers.rs index d807b65..0bc9fc1 100644 --- a/crates/rpack_egui/src/helpers.rs +++ b/crates/rpack_egui/src/helpers.rs @@ -3,34 +3,37 @@ use image::DynamicImage; use rpack_cli::ImageFile; use texture_packer::importer::ImageImporter; +use crate::app::AppImageData; + pub trait DroppedFileHelper { fn file_path(&self) -> String; - fn create_image

(&self, prefix: P) -> Option<(String, ImageFile)> + fn create_image

(&self, prefix: P) -> Option where P: AsRef; fn dynamic_image(&self) -> Option; } +pub fn id_from_path(path: &str) -> String { + match path.rfind('.') { + Some(index) => path[..index].to_string(), + None => path.to_string(), + } + .replace("\\", "/") +} + impl DroppedFileHelper for DroppedFile { fn file_path(&self) -> String { - let id; - #[cfg(not(target_arch = "wasm32"))] - { - let path = self.path.as_ref().unwrap().clone(); - id = path.to_str().unwrap().to_owned(); + match self.path.as_ref() { + Some(path) => path.to_string_lossy().to_string(), + None => self.name.clone(), } - #[cfg(target_arch = "wasm32")] - { - id = self.name.clone(); - } - id.replace(".png", "").replace("\\", "/") } - fn create_image

(&self, prefix: P) -> Option<(String, ImageFile)> + fn create_image

(&self, prefix: P) -> Option where P: AsRef, { let path = self.file_path(); - let base_id = path.replace(".png", ""); + let base_id = id_from_path(&path); let id = base_id .strip_prefix(prefix.as_ref()) @@ -38,7 +41,12 @@ impl DroppedFileHelper for DroppedFile { .to_owned(); let image: DynamicImage = self.dynamic_image()?; - Some((path, ImageFile { id, image })) + Some(AppImageData { + width: image.width(), + height: image.height(), + data: ImageFile { id: id, image }, + path, + }) } fn dynamic_image(&self) -> Option {