mirror of https://github.com/Leinnan/rpack.git
UI rebuild
This commit is contained in:
parent
83e46ee710
commit
57159784eb
|
|
@ -12,6 +12,8 @@ use std::{
|
|||
use texture_packer::{TexturePacker, TexturePackerConfig, importer::ImageImporter};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod packer;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Spritesheet {
|
||||
pub image_data: DynamicImage,
|
||||
|
|
@ -79,7 +81,7 @@ where
|
|||
prefix
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Copy, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Default, Copy, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "cli", not(target_arch = "wasm32")),
|
||||
derive(clap::ValueEnum)
|
||||
|
|
@ -105,14 +107,14 @@ impl Display for SaveImageFormat {
|
|||
|
||||
/// Errors that can occur while building a `Spritesheet`.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum SpritesheetError {
|
||||
#[error("Cannot pack image: {0}")]
|
||||
CannotPackImage(String),
|
||||
#[error("Failed to export tilemap image")]
|
||||
FailedToExportImage,
|
||||
#[error("could not parse asset: {0}")]
|
||||
ParsingError(#[from] serde_json::Error),
|
||||
ParsingError(String),
|
||||
#[error("Failed to pack image into tilemap, tilemap to small")]
|
||||
FailedToPackImage,
|
||||
}
|
||||
|
|
@ -138,7 +140,7 @@ impl From<TexturePackerConfig> for SpritesheetBuildConfig {
|
|||
impl Spritesheet {
|
||||
pub fn build<P>(
|
||||
config: impl Into<SpritesheetBuildConfig>,
|
||||
images: &[&ImageFile],
|
||||
images: &[ImageFile],
|
||||
filename: P,
|
||||
) -> Result<Self, SpritesheetError>
|
||||
where
|
||||
|
|
@ -185,7 +187,8 @@ impl Spritesheet {
|
|||
.collect(),
|
||||
};
|
||||
atlas_asset.frames.sort_by(|a, b| a.key.cmp(&b.key));
|
||||
let atlas_asset_json = serde_json::to_value(&atlas_asset)?;
|
||||
let atlas_asset_json = serde_json::to_value(&atlas_asset)
|
||||
.map_err(|e| SpritesheetError::ParsingError(e.to_string()))?;
|
||||
|
||||
Ok(Spritesheet {
|
||||
image_data,
|
||||
|
|
@ -266,7 +269,7 @@ impl Spritesheet {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Default)]
|
||||
#[derive(Clone, Serialize, Deserialize, Default, PartialEq)]
|
||||
pub struct TilemapGenerationConfig {
|
||||
pub asset_patterns: Vec<String>,
|
||||
pub output_path: String,
|
||||
|
|
@ -372,7 +375,6 @@ impl TilemapGenerationConfig {
|
|||
ImageFile::at_path(f, id)
|
||||
})
|
||||
.collect();
|
||||
let borrowed_images: Vec<&ImageFile> = images.iter().map(|s| s).collect();
|
||||
let atlas_image_path = working_dir.join(format!(
|
||||
"{}{}",
|
||||
self.output_path,
|
||||
|
|
@ -384,7 +386,7 @@ impl TilemapGenerationConfig {
|
|||
.to_string_lossy()
|
||||
.to_string();
|
||||
let atlas_config_path = working_dir.join(format!("{}.rpack.json", self.output_path));
|
||||
let spritesheet = Spritesheet::build(self, &borrowed_images, &atlas_filename)?;
|
||||
let spritesheet = Spritesheet::build(self, &images, &atlas_filename)?;
|
||||
|
||||
if Path::new(&atlas_config_path).exists() {
|
||||
std::fs::remove_file(&atlas_config_path).expect("Could not remove the old file");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,177 @@
|
|||
use std::cmp::max;
|
||||
|
||||
use texture_packer::{Rect, TexturePackerConfig};
|
||||
|
||||
struct Skyline {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub w: u32,
|
||||
}
|
||||
|
||||
impl Skyline {
|
||||
#[inline(always)]
|
||||
pub fn left(&self) -> u32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn right(&self) -> u32 {
|
||||
self.x + self.w - 1
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SkylinePacker {
|
||||
config: TexturePackerConfig,
|
||||
border: Rect,
|
||||
|
||||
// the skylines are sorted by their `x` position
|
||||
skylines: Vec<Skyline>,
|
||||
}
|
||||
|
||||
impl SkylinePacker {
|
||||
pub fn new(config: TexturePackerConfig) -> Self {
|
||||
let skylines = vec![Skyline {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: config.max_width,
|
||||
}];
|
||||
|
||||
SkylinePacker {
|
||||
config,
|
||||
border: Rect::new(0, 0, config.max_width, config.max_height),
|
||||
skylines,
|
||||
}
|
||||
}
|
||||
|
||||
// return `rect` if rectangle (w, h) can fit the skyline started at `i`
|
||||
pub fn can_put(&self, mut i: usize, w: u32, h: u32) -> Option<Rect> {
|
||||
let mut rect = Rect::new(self.skylines[i].x, 0, w, h);
|
||||
let mut width_left = rect.w;
|
||||
loop {
|
||||
rect.y = max(rect.y, self.skylines[i].y);
|
||||
// the source rect is too large
|
||||
if !self.border.contains(&rect) {
|
||||
return None;
|
||||
}
|
||||
if self.skylines[i].w >= width_left {
|
||||
return Some(rect);
|
||||
}
|
||||
width_left -= self.skylines[i].w;
|
||||
i += 1;
|
||||
assert!(i < self.skylines.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_skyline(&self, w: u32, h: u32) -> Option<(usize, Rect)> {
|
||||
let mut bottom = std::u32::MAX;
|
||||
let mut width = std::u32::MAX;
|
||||
let mut index = None;
|
||||
let mut rect = Rect::new(0, 0, 0, 0);
|
||||
|
||||
// keep the `bottom` and `width` as small as possible
|
||||
for i in 0..self.skylines.len() {
|
||||
if let Some(r) = self.can_put(i, w, h) {
|
||||
if r.bottom() < bottom || (r.bottom() == bottom && self.skylines[i].w < width) {
|
||||
bottom = r.bottom();
|
||||
width = self.skylines[i].w;
|
||||
index = Some(i);
|
||||
rect = r;
|
||||
}
|
||||
}
|
||||
|
||||
if self.config.allow_rotation {
|
||||
if let Some(r) = self.can_put(i, h, w) {
|
||||
if r.bottom() < bottom || (r.bottom() == bottom && self.skylines[i].w < width) {
|
||||
bottom = r.bottom();
|
||||
width = self.skylines[i].w;
|
||||
index = Some(i);
|
||||
rect = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index.map(|x| (x, rect))
|
||||
}
|
||||
|
||||
pub fn split(&mut self, index: usize, rect: &Rect) {
|
||||
let skyline = Skyline {
|
||||
x: rect.left(),
|
||||
y: rect.bottom() + 1,
|
||||
w: rect.w,
|
||||
};
|
||||
|
||||
assert!(skyline.right() <= self.border.right());
|
||||
assert!(skyline.y <= self.border.bottom());
|
||||
|
||||
self.skylines.insert(index, skyline);
|
||||
|
||||
let i = index + 1;
|
||||
while i < self.skylines.len() {
|
||||
assert!(self.skylines[i - 1].left() <= self.skylines[i].left());
|
||||
|
||||
if self.skylines[i].left() <= self.skylines[i - 1].right() {
|
||||
let shrink = self.skylines[i - 1].right() - self.skylines[i].left() + 1;
|
||||
if self.skylines[i].w <= shrink {
|
||||
self.skylines.remove(i);
|
||||
} else {
|
||||
self.skylines[i].x += shrink;
|
||||
self.skylines[i].w -= shrink;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(&mut self) {
|
||||
let mut i = 1;
|
||||
while i < self.skylines.len() {
|
||||
if self.skylines[i - 1].y == self.skylines[i].y {
|
||||
self.skylines[i - 1].w += self.skylines[i].w;
|
||||
self.skylines.remove(i);
|
||||
i -= 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pack(&mut self, texture_rect: &Rect) -> Option<()> {
|
||||
let mut width = texture_rect.w;
|
||||
let mut height = texture_rect.h;
|
||||
|
||||
width += self.config.texture_padding + self.config.texture_extrusion * 2;
|
||||
height += self.config.texture_padding + self.config.texture_extrusion * 2;
|
||||
|
||||
if let Some((i, mut rect)) = self.find_skyline(width, height) {
|
||||
self.split(i, &rect);
|
||||
self.merge();
|
||||
|
||||
// let rotated = width != rect.w;
|
||||
|
||||
rect.w -= self.config.texture_padding + self.config.texture_extrusion * 2;
|
||||
rect.h -= self.config.texture_padding + self.config.texture_extrusion * 2;
|
||||
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_pack(&self, texture_rect: &Rect) -> bool {
|
||||
if let Some((_, rect)) = self.find_skyline(
|
||||
texture_rect.w + self.config.texture_padding + self.config.texture_extrusion * 2,
|
||||
texture_rect.h + self.config.texture_padding + self.config.texture_extrusion * 2,
|
||||
) {
|
||||
let skyline = Skyline {
|
||||
x: rect.left(),
|
||||
y: rect.bottom() + 1,
|
||||
w: rect.w,
|
||||
};
|
||||
|
||||
return skyline.right() <= self.border.right() && skyline.y <= self.border.bottom();
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,13 @@ repository = "https://github.com/Leinnan/rpack.git"
|
|||
homepage = "https://github.com/Leinnan/rpack"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
profiler = ["dep:puffin", "dep:puffin_http", "dep:profiling"]
|
||||
|
||||
[dependencies]
|
||||
egui = "0.32"
|
||||
eframe = { version = "0.32", default-features = false, features = [
|
||||
"persistence",
|
||||
"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".
|
||||
|
|
@ -27,9 +31,15 @@ egui_extras = { version = "0.32", features = ["all_loaders"] }
|
|||
rfd = { version = "0.15", features = [] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
anyhow = "1"
|
||||
crossbeam = "0.8"
|
||||
once_cell = "1"
|
||||
futures = "0.3.12"
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
puffin = {version = "0.19", optional = true}
|
||||
puffin_http = {version = "0.16", optional = true}
|
||||
profiling = {version = "1.0.16", optional = true, default-features = false , features = ["profile-with-puffin"] }
|
||||
env_logger = "0.11"
|
||||
rpack_cli = { default-features = false, features = ["config_ext"], path = "../rpack_cli", version = "0.3" }
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -42,6 +42,16 @@ impl DroppedFileHelper for std::fs::DirEntry {
|
|||
ImageImporter::import_from_file(&self.path()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl DroppedFileHelper for (Vec<u8>, String) {
|
||||
fn file_path(&self) -> String {
|
||||
self.1.clone()
|
||||
}
|
||||
|
||||
fn dynamic_image(&self) -> Option<DynamicImage> {
|
||||
ImageImporter::import_from_memory(&self.0).ok()
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl DroppedFileHelper for PathBuf {
|
||||
fn file_path(&self) -> String {
|
||||
|
|
@ -70,19 +80,10 @@ impl DroppedFileHelper for DroppedFile {
|
|||
}
|
||||
|
||||
fn dynamic_image(&self) -> Option<DynamicImage> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let bytes = self.bytes.as_ref().clone()?;
|
||||
|
||||
ImageImporter::import_from_memory(bytes)
|
||||
.ok()
|
||||
.map(|r| r.into())
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let path = self.path.as_ref()?;
|
||||
|
||||
ImageImporter::import_from_file(path).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@ mod app;
|
|||
mod fonts;
|
||||
mod helpers;
|
||||
pub use app::Application;
|
||||
pub use app::ICON_DATA;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,37 @@
|
|||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
fn start_puffin_server() {
|
||||
puffin::set_scopes_on(true); // tell puffin to collect data
|
||||
|
||||
match puffin_http::Server::new("127.0.0.1:8585") {
|
||||
Ok(puffin_server) => {
|
||||
log::info!("Run: cargo install puffin_viewer && puffin_viewer --url 127.0.0.1:8585");
|
||||
|
||||
std::process::Command::new("puffin_viewer")
|
||||
.arg("--url")
|
||||
.arg("127.0.0.1:8585")
|
||||
.spawn()
|
||||
.ok();
|
||||
|
||||
// We can store the server if we want, but in this case we just want
|
||||
// it to keep running. Dropping it closes the server, so let's not drop it!
|
||||
#[expect(clippy::mem_forget)]
|
||||
std::mem::forget(puffin_server);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start puffin server: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When compiling natively:
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() -> eframe::Result<()> {
|
||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||
#[cfg(feature = "profiler")]
|
||||
start_puffin_server();
|
||||
let file_arg: Option<String> = if std::env::args().len() > 1 {
|
||||
std::env::args().skip(1).next()
|
||||
} else {
|
||||
|
|
@ -13,10 +40,7 @@ fn main() -> eframe::Result<()> {
|
|||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([400.0, 300.0])
|
||||
.with_icon(
|
||||
eframe::icon_data::from_png_bytes(include_bytes!("../static/base_icon.png"))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.with_icon(eframe::icon_data::from_png_bytes(rpack_egui::ICON_DATA).unwrap_or_default())
|
||||
.with_min_inner_size([400.0, 300.0]),
|
||||
..Default::default()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<string>Rpack gen config</string>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>rpack_gen.json</string>
|
||||
<string>json</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue