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 texture_packer::{TexturePacker, TexturePackerConfig, importer::ImageImporter};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod packer;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Spritesheet {
|
pub struct Spritesheet {
|
||||||
pub image_data: DynamicImage,
|
pub image_data: DynamicImage,
|
||||||
|
|
@ -79,7 +81,7 @@ where
|
||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Copy, Serialize, Deserialize, PartialEq)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
all(feature = "cli", not(target_arch = "wasm32")),
|
all(feature = "cli", not(target_arch = "wasm32")),
|
||||||
derive(clap::ValueEnum)
|
derive(clap::ValueEnum)
|
||||||
|
|
@ -105,14 +107,14 @@ impl Display for SaveImageFormat {
|
||||||
|
|
||||||
/// Errors that can occur while building a `Spritesheet`.
|
/// Errors that can occur while building a `Spritesheet`.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, Clone)]
|
||||||
pub enum SpritesheetError {
|
pub enum SpritesheetError {
|
||||||
#[error("Cannot pack image: {0}")]
|
#[error("Cannot pack image: {0}")]
|
||||||
CannotPackImage(String),
|
CannotPackImage(String),
|
||||||
#[error("Failed to export tilemap image")]
|
#[error("Failed to export tilemap image")]
|
||||||
FailedToExportImage,
|
FailedToExportImage,
|
||||||
#[error("could not parse asset: {0}")]
|
#[error("could not parse asset: {0}")]
|
||||||
ParsingError(#[from] serde_json::Error),
|
ParsingError(String),
|
||||||
#[error("Failed to pack image into tilemap, tilemap to small")]
|
#[error("Failed to pack image into tilemap, tilemap to small")]
|
||||||
FailedToPackImage,
|
FailedToPackImage,
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +140,7 @@ impl From<TexturePackerConfig> for SpritesheetBuildConfig {
|
||||||
impl Spritesheet {
|
impl Spritesheet {
|
||||||
pub fn build<P>(
|
pub fn build<P>(
|
||||||
config: impl Into<SpritesheetBuildConfig>,
|
config: impl Into<SpritesheetBuildConfig>,
|
||||||
images: &[&ImageFile],
|
images: &[ImageFile],
|
||||||
filename: P,
|
filename: P,
|
||||||
) -> Result<Self, SpritesheetError>
|
) -> Result<Self, SpritesheetError>
|
||||||
where
|
where
|
||||||
|
|
@ -185,7 +187,8 @@ impl Spritesheet {
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
atlas_asset.frames.sort_by(|a, b| a.key.cmp(&b.key));
|
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 {
|
Ok(Spritesheet {
|
||||||
image_data,
|
image_data,
|
||||||
|
|
@ -266,7 +269,7 @@ impl Spritesheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Default)]
|
#[derive(Clone, Serialize, Deserialize, Default, PartialEq)]
|
||||||
pub struct TilemapGenerationConfig {
|
pub struct TilemapGenerationConfig {
|
||||||
pub asset_patterns: Vec<String>,
|
pub asset_patterns: Vec<String>,
|
||||||
pub output_path: String,
|
pub output_path: String,
|
||||||
|
|
@ -372,7 +375,6 @@ impl TilemapGenerationConfig {
|
||||||
ImageFile::at_path(f, id)
|
ImageFile::at_path(f, id)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let borrowed_images: Vec<&ImageFile> = images.iter().map(|s| s).collect();
|
|
||||||
let atlas_image_path = working_dir.join(format!(
|
let atlas_image_path = working_dir.join(format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
self.output_path,
|
self.output_path,
|
||||||
|
|
@ -384,7 +386,7 @@ impl TilemapGenerationConfig {
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
let atlas_config_path = working_dir.join(format!("{}.rpack.json", self.output_path));
|
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() {
|
if Path::new(&atlas_config_path).exists() {
|
||||||
std::fs::remove_file(&atlas_config_path).expect("Could not remove the old file");
|
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"
|
homepage = "https://github.com/Leinnan/rpack"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
profiler = ["dep:puffin", "dep:puffin_http", "dep:profiling"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = "0.32"
|
egui = "0.32"
|
||||||
eframe = { version = "0.32", default-features = false, features = [
|
eframe = { version = "0.32", default-features = false, features = [
|
||||||
|
"persistence",
|
||||||
"accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
|
"accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
|
||||||
"default_fonts", # Embed the default egui fonts.
|
"default_fonts", # Embed the default egui fonts.
|
||||||
"glow", # Use the glow rendering backend. Alternative: "wgpu".
|
"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 = [] }
|
rfd = { version = "0.15", features = [] }
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
crossbeam = "0.8"
|
||||||
|
once_cell = "1"
|
||||||
|
futures = "0.3.12"
|
||||||
|
|
||||||
# native:
|
# native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[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"
|
env_logger = "0.11"
|
||||||
rpack_cli = { default-features = false, features = ["config_ext"], path = "../rpack_cli", version = "0.3" }
|
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()
|
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"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
impl DroppedFileHelper for PathBuf {
|
impl DroppedFileHelper for PathBuf {
|
||||||
fn file_path(&self) -> String {
|
fn file_path(&self) -> String {
|
||||||
|
|
@ -70,19 +80,10 @@ impl DroppedFileHelper for DroppedFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dynamic_image(&self) -> Option<DynamicImage> {
|
fn dynamic_image(&self) -> Option<DynamicImage> {
|
||||||
#[cfg(target_arch = "wasm32")]
|
let bytes = self.bytes.as_ref().clone()?;
|
||||||
{
|
|
||||||
let bytes = self.bytes.as_ref().clone()?;
|
|
||||||
|
|
||||||
ImageImporter::import_from_memory(bytes)
|
ImageImporter::import_from_memory(bytes)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|r| r.into())
|
.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 fonts;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
pub use app::Application;
|
pub use app::Application;
|
||||||
|
pub use app::ICON_DATA;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,37 @@
|
||||||
#![warn(clippy::all, rust_2018_idioms)]
|
#![warn(clippy::all, rust_2018_idioms)]
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![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:
|
// When compiling natively:
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn main() -> eframe::Result<()> {
|
fn main() -> eframe::Result<()> {
|
||||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
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 {
|
let file_arg: Option<String> = if std::env::args().len() > 1 {
|
||||||
std::env::args().skip(1).next()
|
std::env::args().skip(1).next()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -13,10 +40,7 @@ fn main() -> eframe::Result<()> {
|
||||||
let native_options = eframe::NativeOptions {
|
let native_options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default()
|
viewport: egui::ViewportBuilder::default()
|
||||||
.with_inner_size([400.0, 300.0])
|
.with_inner_size([400.0, 300.0])
|
||||||
.with_icon(
|
.with_icon(eframe::icon_data::from_png_bytes(rpack_egui::ICON_DATA).unwrap_or_default())
|
||||||
eframe::icon_data::from_png_bytes(include_bytes!("../static/base_icon.png"))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.with_min_inner_size([400.0, 300.0]),
|
.with_min_inner_size([400.0, 300.0]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<string>Rpack gen config</string>
|
<string>Rpack gen config</string>
|
||||||
<key>CFBundleTypeExtensions</key>
|
<key>CFBundleTypeExtensions</key>
|
||||||
<array>
|
<array>
|
||||||
<string>rpack_gen.json</string>
|
<string>json</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Editor</string>
|
<string>Editor</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue