diff --git a/Cargo.toml b/Cargo.toml index 316521b..7a1a3e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,4 @@ opt-level = 2 [workspace.dependencies] texture_packer = { version = "0.30", features = ["common"] } -image = { version = "0.25", features = ["jpeg", "png"] } \ No newline at end of file +image = { version = "0.25", features = ["jpeg", "png"] } diff --git a/README.md b/README.md index aa20479..40bca40 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,11 @@ Example: "key": "tiles/agents/spaceAstronauts_004" }, ], + "metadata": { + "app": "rpack", + "app_version": "0.3.0", + "format_version": 1 + }, "size": [ 512, 512 @@ -148,11 +153,6 @@ Example: "size": 512, "texture_padding": 2, "border_padding": 2, - "metadata": { - "app": "rpack", - "app_version": "0.3.0", - "format_version": 1 - }, "size": [ 512, 512 diff --git a/crates/rpack_egui/src/app.rs b/crates/rpack_egui/src/app.rs index a3543de..7b3d8b2 100644 --- a/crates/rpack_egui/src/app.rs +++ b/crates/rpack_egui/src/app.rs @@ -2,9 +2,9 @@ use crossbeam::queue::SegQueue; use egui::containers::menu::MenuButton; use egui::{ util::undoer::Undoer, Button, Color32, FontFamily, FontId, Frame, Image, Label, Layout, - RichText, Sense, Slider, Ui, + RichText, Slider, Ui, }; -use egui::{Grid, Vec2}; +use egui::{Checkbox, Grid, Vec2}; use egui_extras::{Column, TableBuilder}; use once_cell::sync::Lazy; use rpack_cli::TilemapGenerationConfig; @@ -14,6 +14,7 @@ use rpack_cli::{ use texture_packer::{Rect, TexturePackerConfig}; use crate::helpers::DroppedFileHelper; +use crate::view_settings::ViewSettings; static INPUT_QUEUE: Lazy> = Lazy::new(SegQueue::new); pub const MY_ACCENT_COLOR32: Color32 = Color32::from_rgb(230, 102, 1); pub const GIT_HASH: &str = env!("GIT_HASH"); @@ -71,6 +72,8 @@ pub struct Application { last_error: Option, undoer: Undoer, last_editor_paths: Vec, + view_settings: ViewSettings, + show_modal: bool, } #[derive(serde::Deserialize, serde::Serialize, Default, Clone, PartialEq)] @@ -113,6 +116,8 @@ impl Default for Application { output: SpriteSheetState::Empty, last_error: None, last_editor_paths: Vec::new(), + view_settings: Default::default(), + show_modal: false, } } } @@ -203,8 +208,14 @@ impl Application { } else { Default::default() }; + let view_settings: ViewSettings = if let Some(storage) = cc.storage { + eframe::get_value(storage, "view_settings").unwrap_or_default() + } else { + Default::default() + }; let mut app = Self { last_editor_paths, + view_settings, ..Default::default() }; cc.egui_ctx.include_bytes("bytes://image.png", ICON_DATA); @@ -356,6 +367,7 @@ impl eframe::App for Application { /// Called by the framework to save state before shutdown. fn save(&mut self, storage: &mut dyn eframe::Storage) { eframe::set_value(storage, eframe::APP_KEY, &self.last_editor_paths); + eframe::set_value(storage, "view_settings", &self.view_settings); } /// Called each time the UI needs repainting, which may be many times per second. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { @@ -453,6 +465,39 @@ impl eframe::App for Application { self.build_atlas(ctx); } } + if self.show_modal { + let mut should_close = false; + + should_close |= egui::Modal::new("VisualSettings".into()) + .frame(egui::Frame::menu(&ctx.style()).inner_margin(10.0)) + .show(ctx, |ui| { + ui.style_mut().interaction.selectable_labels = false; + ui.vertical_centered(|ui| { + ui.heading("Settings"); + ui.add_space(15.0); + Grid::new("settings_grid") + .num_columns(2) + .striped(true) + .show(ui, |ui| { + ui.add(Label::new("Max Preview size")); + ui.add(Slider::new( + &mut self.view_settings.preview_max_size, + 256.0..=1024.0, + )); + ui.end_row(); + ui.add(Label::new("Display JSON")); + ui.add(Checkbox::new(&mut self.view_settings.display_json, "")); + ui.end_row(); + }); + ui.add_space(10.0); + should_close |= ui.button("Close").clicked(); + }); + }) + .should_close(); + if should_close { + self.show_modal = false; + } + } egui::TopBottomPanel::top("topPanel") .frame(egui::Frame::canvas(&ctx.style())) .show(ctx, |ui| { @@ -552,7 +597,12 @@ impl eframe::App for Application { #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))] puffin::profile_scope!("bottom_panel"); ui.add_space(5.0); + ui.horizontal(|ui| { + ui.add_space(5.0); + if ui.button("🛠").on_hover_text("Visual settings").clicked() { + self.show_modal = true; + } ui.add_space(5.0); ui.add_enabled_ui(self.undoer.has_undo(&self.data), |ui| { if ui.button("⮪").on_hover_text("Go back").clicked() { @@ -582,10 +632,11 @@ impl eframe::App for Application { .max_width(400.0) .frame(egui::Frame::canvas(&ctx.style()).inner_margin(10)) .show_animated(ctx, !self.data.image_data.is_empty(), |ui| { - egui::ScrollArea::vertical() - .id_salt("rightPanel_scroll") - .show(ui, |ui| { - ui.add_enabled_ui(!self.output.is_building(), |ui| { + ui.with_layout( + Layout::top_down(egui::Align::Min).with_cross_justify(true), + |ui| { + ui.style_mut().interaction.selectable_labels = false; + { #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))] puffin::profile_scope!("right_panel"); let mut changed = false; @@ -674,155 +725,94 @@ impl eframe::App for Application { } } }); - ui.vertical_centered_justified(|ui| { - ui.add_space(10.0); - - // changed |= ui.checkbox(&mut - // self.data.settings., "Force Max Dimensions").changed(); - // ui.checkbox(&mut self.config.allow_rotation, "Allow Rotation") - // .on_hover_text("True to allow rotation of the input images. Default value is `true`. Images rotated will be rotated 90 degrees clockwise."); - // ui.checkbox(&mut self.data.config.texture_outlines, "Texture Outlines") - // .on_hover_text("Draw the red line on the edge of the each frames. Useful for debugging."); - - ui.add_space(10.0); - }); if changed { INPUT_QUEUE.push(AppImageAction::RebuildAtlas); } + } + Grid::new("ImgesHeader") + .num_columns(2) + .spacing([50.0, 10.0]) + .show(ui, |ui| { + ui.menu_button("🖼", |ui| { + if !self.data.image_data.is_empty() + && ui + .add( + egui::Button::new("Remove all images").frame(false), + ) + .clicked() + { + INPUT_QUEUE.push(AppImageAction::Clear); + } + ui.add_space(10.0); + + if ui + .add(egui::Button::new("Add more images").frame(false)) + .clicked() + { + self.read_files(); + } + }); + ui.heading("Images"); + }); + + ui.add_enabled_ui(!self.output.is_building(), |ui| { ui.separator(); { - ui.style_mut().interaction.selectable_labels = false; #[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))] puffin::profile_scope!("image_list"); - ui.horizontal(|ui| { - ui.heading("Images"); - ui.with_layout( - Layout::right_to_left(egui::Align::Center), - |ui| { - if !self.data.image_data.is_empty() - && ui - .add(egui::Button::new("🗙").frame(false)) - .on_hover_text("Remove all images") - .clicked() - { - INPUT_QUEUE.push(AppImageAction::Clear); - } - ui.add_space(10.0); - - if ui - .add(egui::Button::new("⊞").frame(false)) - .on_hover_text("Add more images") - .clicked() - { - self.read_files(); - } - if ui.available_width() > 15.0 { - ui.add_space(ui.available_width() - 10.0); - } - }, - ) - }); let length = self.data.image_data.len(); - let text_height = - egui::TextStyle::Body.resolve(ui.style()).size * 1.5; - let table = TableBuilder::new(ui) .striped(true) .vscroll(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .column(Column::remainder().at_least(50.0)) .column(Column::exact(70.0)) - .column(Column::auto().at_most(150.0).at_least(30.0)) - .column(Column::auto()) + .column(Column::remainder().at_least(50.0)) .sense(egui::Sense::click()); - table - .header(text_height, |mut row| { - row.col(|col| { - col.vertical_centered_justified(|ui| { - ui.add( - egui::Label::new("Name") - .wrap_mode(egui::TextWrapMode::Truncate) - .selectable(false), // .sense(Sense::click()), - ) - }); - }); - row.col(|col| { - col.vertical_centered_justified(|ui| { - ui.add( - egui::Label::new("Preview") - .wrap_mode(egui::TextWrapMode::Truncate) - .selectable(false), - ) - }); - }); - row.col(|col| { - col.vertical_centered_justified(|ui| { - ui.add( - egui::Label::new("Dimensions") - .wrap_mode(egui::TextWrapMode::Truncate) - .selectable(false) - .sense(Sense::click()), - ) - }); - }); - - row.col(|col| { - col.vertical_centered_justified(|ui| { - ui.add( - egui::Label::new("") - .wrap_mode(egui::TextWrapMode::Truncate) - .selectable(false), - ) - }); - }); - }) - .body(|body| { - body.rows(64.0, length, |mut row| { - let index = row.index(); - let file = &self.data.image_data[index]; - row.col(|ui| { - ui.add( - Label::new(file.id()) - .selectable(false) - .wrap_mode(egui::TextWrapMode::Truncate), - ); - }); - row.col(|ui| { - ui.vertical_centered_justified(|ui| { - ui.add_sized( - (64.0, 64.0), - Image::from_uri(format!( - "bytes://{}", - file.path.as_str() - )) - .corner_radius(5u8), - ); - }); - }); - row.col(|ui| { - ui.add( - Label::new(format!( - "{}x{}", - file.width, file.height + table.body(|body| { + body.rows(64.0, length, |mut row| { + let index = row.index(); + let file = &self.data.image_data[index]; + row.col(|ui| { + ui.vertical_centered_justified(|ui| { + ui.add_sized( + (64.0, 64.0), + Image::from_uri(format!( + "bytes://{}", + file.path.as_str() )) - .selectable(false), - ); + .corner_radius(5u8), + ) + .on_hover_text(format!( + "{}x{}", + file.width, file.height + )); }); - row.col(|ui| { + }); + row.col(|ui| { + ui.add( + Label::new(file.id()) + .selectable(false) + .wrap_mode(egui::TextWrapMode::Truncate), + ); + }) + .1 + .context_menu( + |ui| { if ui - .add(Button::new("🗙").frame(false)) - .on_hover_text("Remove") + .add(Button::new("Remove").frame(false)) .clicked() { INPUT_QUEUE.push(AppImageAction::Remove(index)); } - }); - }); + }, + ); }); + }); } }); - }); + }, + ); }); egui::CentralPanel::default() .frame(Frame::central_panel(&ctx.style()).inner_margin(16i8)) @@ -908,19 +898,17 @@ impl eframe::App for Application { let SpriteSheetState::Ok(data) = &self.output else { return; }; - ui.add(Image::from_uri("bytes://output.png").bg_fill(Color32::from_black_alpha(200)).max_size(Vec2::new(512.0,512.0))); + ui.add(Image::from_uri("bytes://output.png").bg_fill(Color32::from_black_alpha(200)).max_size(Vec2::splat(self.view_settings.preview_max_size))).on_hover_text(format!( + "{} sprites\nsize: {}x{}", + data.atlas_asset.frames.len(), + data.atlas_asset.size[0], + data.atlas_asset.size[1] + )); ui.separator(); ui.add_space(10.0); ui.horizontal(|ui|{ - ui.label(format!( - "{} sprites\nsize: {}x{}", - data.atlas_asset.frames.len(), - data.atlas_asset.size[0], - data.atlas_asset.size[1] - )); - ui.separator(); + if self.view_settings.display_json { ui.vertical_centered_justified(|ui|{ - ui.add_space(10.0); if ui .add(egui::Button::new("Copy JSON to Clipboard")) @@ -929,13 +917,14 @@ impl eframe::App for Application { ui.ctx() .copy_text(data.atlas_asset_json.to_string()); }; - ui.add_space(10.0); - egui_json_tree::JsonTree::new( - "simple-tree", - &data.atlas_asset_json, - ) - .show(ui); + ui.add_space(10.0); + egui_json_tree::JsonTree::new( + "simple-tree", + &data.atlas_asset_json, + ) + .show(ui); }); + } }); diff --git a/crates/rpack_egui/src/lib.rs b/crates/rpack_egui/src/lib.rs index ae72e80..c419dfa 100644 --- a/crates/rpack_egui/src/lib.rs +++ b/crates/rpack_egui/src/lib.rs @@ -3,5 +3,6 @@ mod app; mod fonts; mod helpers; +mod view_settings; pub use app::Application; pub use app::ICON_DATA; diff --git a/crates/rpack_egui/src/view_settings.rs b/crates/rpack_egui/src/view_settings.rs new file mode 100644 index 0000000..d899ead --- /dev/null +++ b/crates/rpack_egui/src/view_settings.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ViewSettings { + pub preview_max_size: f32, + pub display_json: bool, +} + +impl Default for ViewSettings { + fn default() -> Self { + Self { + preview_max_size: 512.0, + display_json: true, + } + } +}