Bevy crate improvements, example, docs

This commit is contained in:
Piotr Siuszko 2025-01-09 17:39:04 +01:00
parent e0945af626
commit a33dd60392
13 changed files with 825 additions and 124 deletions

377
Cargo.lock generated
View File

@ -253,6 +253,15 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "arbitrary"
version = "1.4.1"
@ -811,6 +820,7 @@ version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b747210d7db09dfacc237707d4fd31c8b43d7744cd5e5829e2c4ca86b9e47baf"
dependencies = [
"arrayvec",
"bevy_ecs_macros",
"bevy_ptr",
"bevy_reflect",
@ -941,6 +951,7 @@ version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd7fc4db9a1793ee71f79abb15e7a8fcfe4e2081e5f18ed91b802bf6cf30e088"
dependencies = [
"bevy_a11y",
"bevy_app",
"bevy_asset",
"bevy_color",
@ -955,16 +966,22 @@ dependencies = [
"bevy_input",
"bevy_log",
"bevy_math",
"bevy_pbr",
"bevy_picking",
"bevy_ptr",
"bevy_reflect",
"bevy_render",
"bevy_scene",
"bevy_sprite",
"bevy_state",
"bevy_tasks",
"bevy_text",
"bevy_time",
"bevy_transform",
"bevy_ui",
"bevy_utils",
"bevy_window",
"bevy_winit",
]
[[package]]
@ -1043,6 +1060,35 @@ dependencies = [
"glam",
]
[[package]]
name = "bevy_pbr"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7f17067399cf00f4441e93d39fb4c391a16cc223e0d35346ac388e66712c418"
dependencies = [
"bevy_app",
"bevy_asset",
"bevy_color",
"bevy_core_pipeline",
"bevy_derive",
"bevy_ecs",
"bevy_image",
"bevy_math",
"bevy_reflect",
"bevy_render",
"bevy_transform",
"bevy_utils",
"bevy_window",
"bitflags 2.6.0",
"bytemuck",
"derive_more",
"fixedbitset 0.5.7",
"nonmax",
"radsort",
"smallvec",
"static_assertions",
]
[[package]]
name = "bevy_picking"
version = "0.15.1"
@ -1223,19 +1269,75 @@ dependencies = [
"rectangle-pack",
]
[[package]]
name = "bevy_state"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd10c8b01a982642596406fc4486fcd52239aa9c4aa47fed27abab93a69fba59"
dependencies = [
"bevy_app",
"bevy_ecs",
"bevy_hierarchy",
"bevy_reflect",
"bevy_state_macros",
"bevy_utils",
]
[[package]]
name = "bevy_state_macros"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23773797bf8077a6ad9299f10b063b6947f22dad311d855c4b3523102ab4381b"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
"quote",
"syn 2.0.95",
]
[[package]]
name = "bevy_tasks"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c28f2db2619203aa82342dbbe77e49aeea4f933212c0b7a1f285e94c4008e5b"
dependencies = [
"async-channel",
"async-executor",
"concurrent-queue",
"futures-channel",
"futures-lite",
"pin-project",
"wasm-bindgen-futures",
]
[[package]]
name = "bevy_text"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ee0b5f52946d222521f93773a6230f42e868548f881c4c5bddb1393a96298b"
dependencies = [
"bevy_app",
"bevy_asset",
"bevy_color",
"bevy_derive",
"bevy_ecs",
"bevy_hierarchy",
"bevy_image",
"bevy_math",
"bevy_reflect",
"bevy_render",
"bevy_sprite",
"bevy_transform",
"bevy_utils",
"bevy_window",
"cosmic-text",
"derive_more",
"serde",
"smallvec",
"sys-locale",
"unicode-bidi",
]
[[package]]
name = "bevy_time"
version = "0.15.1"
@ -1263,6 +1365,39 @@ dependencies = [
"derive_more",
]
[[package]]
name = "bevy_ui"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4556fc2202c6339f95e0c24ca4c96ee959854b702e23ecf73e05fb20e67d67b0"
dependencies = [
"accesskit",
"bevy_a11y",
"bevy_app",
"bevy_asset",
"bevy_color",
"bevy_core_pipeline",
"bevy_derive",
"bevy_ecs",
"bevy_hierarchy",
"bevy_image",
"bevy_input",
"bevy_math",
"bevy_picking",
"bevy_reflect",
"bevy_render",
"bevy_sprite",
"bevy_text",
"bevy_transform",
"bevy_utils",
"bevy_window",
"bytemuck",
"derive_more",
"nonmax",
"smallvec",
"taffy",
]
[[package]]
name = "bevy_utils"
version = "0.15.1"
@ -1307,6 +1442,35 @@ dependencies = [
"smol_str",
]
[[package]]
name = "bevy_winit"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e84e7f94583cac93de4ba641029eb0b6551d35e559c829209f2b1b9fe532d8"
dependencies = [
"accesskit",
"accesskit_winit",
"approx",
"bevy_a11y",
"bevy_app",
"bevy_derive",
"bevy_ecs",
"bevy_hierarchy",
"bevy_input",
"bevy_log",
"bevy_math",
"bevy_reflect",
"bevy_tasks",
"bevy_utils",
"bevy_window",
"cfg-if",
"crossbeam-channel",
"raw-window-handle",
"wasm-bindgen",
"web-sys",
"winit",
]
[[package]]
name = "bindgen"
version = "0.70.1"
@ -1787,6 +1951,29 @@ dependencies = [
"libc",
]
[[package]]
name = "cosmic-text"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2"
dependencies = [
"bitflags 2.6.0",
"fontdb",
"log",
"rangemap",
"rayon",
"rustc-hash",
"rustybuzz",
"self_cell",
"swash",
"sys-locale",
"ttf-parser 0.21.1",
"unicode-bidi",
"unicode-linebreak",
"unicode-script",
"unicode-segmentation",
]
[[package]]
name = "cpufeatures"
version = "0.2.16"
@ -2421,6 +2608,38 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "font-types"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492"
dependencies = [
"bytemuck",
]
[[package]]
name = "fontconfig-parser"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7"
dependencies = [
"roxmltree 0.20.0",
]
[[package]]
name = "fontdb"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3"
dependencies = [
"fontconfig-parser",
"log",
"memmap2",
"slotmap",
"tinyvec",
"ttf-parser 0.20.0",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@ -2730,6 +2949,12 @@ dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "grid"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be136d9dacc2a13cc70bb6c8f902b414fb2641f8db1314637c6b7933411a8f82"
[[package]]
name = "guillotiere"
version = "0.6.2"
@ -3855,7 +4080,7 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
dependencies = [
"ttf-parser",
"ttf-parser 0.25.1",
]
[[package]]
@ -4164,6 +4389,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
[[package]]
name = "rangemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684"
[[package]]
name = "rav1e"
version = "0.7.1"
@ -4246,6 +4477,16 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f"
[[package]]
name = "read-fonts"
version = "0.22.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f"
dependencies = [
"bytemuck",
"font-types",
]
[[package]]
name = "rectangle-pack"
version = "0.4.2"
@ -4400,6 +4641,12 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rpack"
version = "0.1.0"
@ -4495,6 +4742,23 @@ version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]]
name = "rustybuzz"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c"
dependencies = [
"bitflags 2.6.0",
"bytemuck",
"libm",
"smallvec",
"ttf-parser 0.21.1",
"unicode-bidi-mirroring",
"unicode-ccc",
"unicode-properties",
"unicode-script",
]
[[package]]
name = "ryu"
version = "1.0.18"
@ -4522,6 +4786,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "self_cell"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
[[package]]
name = "send_wrapper"
version = "0.6.0"
@ -4645,6 +4915,16 @@ version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "skrifa"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe"
dependencies = [
"bytemuck",
"read-fonts",
]
[[package]]
name = "slab"
version = "0.4.9"
@ -4806,6 +5086,17 @@ dependencies = [
"siphasher",
]
[[package]]
name = "swash"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2"
dependencies = [
"skrifa",
"yazi",
"zeno",
]
[[package]]
name = "syn"
version = "1.0.109"
@ -4839,6 +5130,15 @@ dependencies = [
"syn 2.0.95",
]
[[package]]
name = "sys-locale"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
dependencies = [
"libc",
]
[[package]]
name = "system-deps"
version = "6.2.2"
@ -4852,6 +5152,19 @@ dependencies = [
"version-compare",
]
[[package]]
name = "taffy"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cb893bff0f80ae17d3a57e030622a967b8dbc90e38284d9b4b1442e23873c94"
dependencies = [
"arrayvec",
"grid",
"num-traits",
"serde",
"slotmap",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
@ -5132,6 +5445,18 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "ttf-parser"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
[[package]]
name = "ttf-parser"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
[[package]]
name = "ttf-parser"
version = "0.25.1"
@ -5167,12 +5492,48 @@ version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-bidi-mirroring"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86"
[[package]]
name = "unicode-ccc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656"
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-linebreak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-properties"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unicode-script"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
@ -5256,7 +5617,7 @@ dependencies = [
"imagesize",
"kurbo",
"log",
"roxmltree",
"roxmltree 0.19.0",
"simplecss",
"siphasher",
"svgtypes",
@ -6095,6 +6456,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "yazi"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1"
[[package]]
name = "yoke"
version = "0.7.5"
@ -6281,6 +6648,12 @@ dependencies = [
"zvariant 4.2.0",
]
[[package]]
name = "zeno"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
[[package]]
name = "zerocopy"
version = "0.7.35"

View File

@ -1,12 +1,26 @@
# rpack [![Build Status](https://github.com/Leinnan/rpack/workflows/CI/badge.svg)](https://github.com/Leinnan/rpack/actions?workflow=CI)
Create spritesheets in seconds!
## rPack egui
To open it in browser click one of the icons below:
[![Itch.io](https://img.shields.io/badge/Itch-%23FF0B34.svg?style=for-the-badge&logo=Itch.io&logoColor=white)](https://mevlyshkin.itch.io/rpack)
[![Github Pages](https://img.shields.io/badge/github%20pages-121013?style=for-the-badge&logo=github&logoColor=white)](http://rpack.mevlyshkin.com/)
A both desktop and web frontend for generating tilemaps. Just drag and drop images into the program and generate tilemaps.
Create spritesheets in seconds!
Attempt to build texture atlas packer GUI.
Source code is available in `crates/rpack` directory of the repo.
![rpack_ebVVrMf3wm](https://github.com/user-attachments/assets/bb015348-3c1f-46be-9312-963b4f39f9c0)
## Bevy rPack
A Bevy plugin with support for the `rpack.json` atlases.
More info available at [crates/bevy_rpack](https://github.com/Leinnan/rpack/tree/master/crates/bevy_rpack).
## rPack CLI
Command line interface for generating tilemaps. Code stored in `crates/rpack_cli` directory. Right now this is the part of the project that could change the most.

View File

@ -17,7 +17,27 @@ bevy = { version = "0.15", optional = true, default-features = false, features =
"bevy_asset",
"bevy_sprite",
"bevy_image",
"bevy_ui",
] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "2.0"
thiserror = "2.0"
[dev-dependencies]
bevy = { version = "0.15", default-features = false, features = [
"bevy_asset",
"bevy_core_pipeline",
"bevy_render",
"bevy_sprite",
"bevy_state",
"bevy_window",
"bevy_winit",
"bevy_ui",
"multi_threaded",
"png",
"webgl2",
] }
[lints.rust]
unsafe_code = "forbid"
missing_docs = "warn"

View File

@ -0,0 +1,59 @@
# Bevy_rPack
A Bevy plugin with support for the `rpack.json` atlases.
## Example
```rust
use bevy::prelude::*;
use bevy_rpack::prelude::*;
#[allow(dead_code)]
#[derive(Resource)]
struct Holder(pub Handle<RpackAtlasAsset>);
fn main() {
App::new()
.add_plugins((DefaultPlugins, RpackAssetPlugin))
.add_systems(Startup, setup)
.add_systems(Update, on_loaded)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.insert_resource(Holder(asset_server.load("Tilemap.rpack.json")));
commands.spawn(Camera2d);
}
fn on_loaded(
mut ev_asset: EventReader<AssetEvent<RpackAtlasAsset>>,
assets: Res<Assets<RpackAtlasAsset>>,
mut commands: Commands,
) {
for ev in ev_asset.read() {
let AssetEvent::LoadedWithDependencies { id: _ } = ev else {
continue;
};
if let Ok(sprite) = assets.make_sprite_from_atlas("Sword006") {
commands.spawn(Sprite {
color: Color::linear_rgb(1.0, 0.0, 0.0),
..sprite
});
};
if let Ok(image_node) = assets.make_image_node_from_atlas("Axe010") {
commands.spawn(image_node);
}
}
}
```
## Licence
`bevy_rpack` is dual-licensed under MIT and Apache 2.0 at your option.
## Bevy compatibility table
Bevy version | Crate version
--- | ---
0.15 | 0.1

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -0,0 +1,72 @@
{
"filename": "tilemap.png",
"frames": [
{
"frame": {
"h": 34,
"w": 34,
"x": 74,
"y": 2
},
"key": "Sword007"
},
{
"frame": {
"h": 34,
"w": 34,
"x": 38,
"y": 2
},
"key": "Axe011"
},
{
"frame": {
"h": 34,
"w": 34,
"x": 2,
"y": 74
},
"key": "Axe010"
},
{
"frame": {
"h": 34,
"w": 34,
"x": 2,
"y": 2
},
"key": "Sword006"
},
{
"frame": {
"h": 34,
"w": 34,
"x": 2,
"y": 38
},
"key": "Sword009"
},
{
"frame": {
"h": 34,
"w": 34,
"x": 74,
"y": 38
},
"key": "Axe009"
},
{
"frame": {
"h": 34,
"w": 34,
"x": 38,
"y": 38
},
"key": "Sword008"
}
],
"size": [
128,
128
]
}

View File

@ -0,0 +1,43 @@
//! Simple example that loads the tilemap and once is loaded it creates a sprite with it.
use bevy::prelude::*;
use bevy_rpack::prelude::*;
#[allow(dead_code)]
#[derive(Resource)]
struct Holder(pub Handle<RpackAtlasAsset>);
fn main() {
App::new()
.add_plugins((DefaultPlugins, RpackAssetPlugin))
.add_systems(Startup, setup)
.add_systems(Update, on_loaded)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.insert_resource(Holder(asset_server.load("tilemap.rpack.json")));
commands.spawn(Camera2d);
}
fn on_loaded(
mut ev_asset: EventReader<AssetEvent<RpackAtlasAsset>>,
assets: Res<Assets<RpackAtlasAsset>>,
mut commands: Commands,
) {
for ev in ev_asset.read() {
let AssetEvent::LoadedWithDependencies { id: _ } = ev else {
continue;
};
if let Ok(sprite) = assets.make_sprite_from_atlas("Sword006") {
commands.spawn(Sprite {
color: Color::linear_rgb(1.0, 0.0, 0.0),
..sprite
});
};
if let Ok(image_node) = assets.make_image_node_from_atlas("Axe010") {
commands.spawn(image_node);
}
}
}

View File

@ -1,16 +1,24 @@
#[cfg(feature = "bevy")]
mod bevy;
#![doc = include_str!("../README.md")]
#[cfg(feature = "bevy")]
/// Contains the Bevy plugin for handling `Rpack` assets and atlases.
mod plugin;
/// Re-exports all types for working with texture atlases.
pub mod prelude {
#[cfg(feature = "bevy")]
pub use super::bevy::{
RpackAssetPlugin, RpackAtlasAsset, RpackAtlasAssetError, RpackAtlasAssetLoader,
/// Provides easy access to `Rpack` asset-related functionality in a Bevy application.
pub use super::plugin::{
RpackAssetHelper, RpackAssetPlugin, RpackAtlasAsset, RpackAtlasAssetError,
RpackAtlasAssetLoader, RpackAtlasError,
};
/// Re-exports core types for working with texture atlases.
pub use super::{AtlasAsset, AtlasFrame, SerializableRect};
}
/// Defines a rectangle in pixels with the origin at the top-left of the texture atlas.
#[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "bevy", derive(bevy::prelude::Reflect))]
pub struct SerializableRect {
/// Horizontal position the rectangle begins at.
pub x: u32,
@ -22,15 +30,24 @@ pub struct SerializableRect {
pub h: u32,
}
/// Represents a single frame within a texture atlas, including its identifier and position.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "bevy", derive(bevy::prelude::Reflect))]
pub struct AtlasFrame {
/// A unique identifier for the frame.
pub key: String,
/// The rectangular area of the frame within the texture atlas.
pub frame: SerializableRect,
}
/// Represents an entire texture atlas asset, including its metadata and frames.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "bevy", derive(bevy::prelude::Asset, bevy::prelude::Reflect))]
pub struct AtlasAsset {
/// The overall dimensions of the texture atlas in pixels (width, height).
pub size: [u32; 2],
/// The filename associated with the atlas, typically used for loading or debugging.
pub filename: String,
/// A collection of frames contained within the texture atlas.
pub frames: Vec<AtlasFrame>,
}

View File

@ -4,6 +4,17 @@ use bevy::image::ImageSampler;
use bevy::{prelude::*, utils::HashMap};
use thiserror::Error;
/// Errors that can occur while accessing and creating components from [`RpackAtlasAsset`].
#[derive(Debug, Error)]
pub enum RpackAtlasError {
/// An error that occured due to no atlas being loaded yet
#[error("There is no atlas.")]
NoAtlas,
/// An error that occured because atlas does not contain provided key.
#[error("There is no frame with provided key.")]
WrongKey,
}
/// This is an asset containing the texture atlas image, the texture atlas layout, and a map of the original file names to their corresponding indices in the texture atlas.
#[derive(Asset, Debug, Reflect)]
pub struct RpackAtlasAsset {
@ -27,29 +38,100 @@ impl From<SerializableRect> for URect {
}
}
impl RpackAtlasAsset {
// When atlas contains the given key returns a copy of TextureAtlas and Image
pub fn get_atlas_data<T: AsRef<str>>(&self, key: T) -> Option<(TextureAtlas, Handle<Image>)> {
self.files.get(key.as_ref()).map(|s| {
(
/// A helper trait for accessing and creating components from `Rpack` atlas data.
#[allow(dead_code)]
pub trait RpackAssetHelper {
/// Retrieves the atlas data (texture atlas and image) for the given atlas key, if available in any of the loaded Atlases.
fn find_atlas_data_by_key<T: AsRef<str>>(
&self,
key: T,
) -> Result<(TextureAtlas, Handle<Image>), RpackAtlasError>;
/// Creates a [`Sprite`] component for the given atlas key, if available in any of the loaded Atlases.
fn make_sprite_from_atlas<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError>;
/// Creates a [`ImageNode`] component for the given atlas key, if available in any of the loaded Atlases.
fn make_image_node_from_atlas<T: AsRef<str>>(
&self,
key: T,
) -> Result<ImageNode, RpackAtlasError>;
}
impl RpackAssetHelper for Assets<RpackAtlasAsset> {
fn find_atlas_data_by_key<T: AsRef<str>>(
&self,
key: T,
) -> Result<(TextureAtlas, Handle<Image>), RpackAtlasError> {
if self.is_empty() {
return Err(RpackAtlasError::NoAtlas);
}
for (_, a) in self.iter() {
if let Ok(atlas_data) = a.find_atlas_data_by_key(key.as_ref()) {
return Ok(atlas_data);
}
}
Err(RpackAtlasError::WrongKey)
}
fn make_sprite_from_atlas<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError> {
if self.is_empty() {
return Err(RpackAtlasError::NoAtlas);
}
for (_, a) in self.iter() {
if let Ok(sprite) = a.make_sprite_from_atlas(key.as_ref()) {
return Ok(sprite);
}
}
Err(RpackAtlasError::WrongKey)
}
fn make_image_node_from_atlas<T: AsRef<str>>(
&self,
key: T,
) -> Result<ImageNode, RpackAtlasError> {
if self.is_empty() {
return Err(RpackAtlasError::NoAtlas);
}
for (_, a) in self.iter() {
if let Ok(image_node) = a.make_image_node_from_atlas(key.as_ref()) {
return Ok(image_node);
}
}
Err(RpackAtlasError::WrongKey)
}
}
impl RpackAssetHelper for RpackAtlasAsset {
fn find_atlas_data_by_key<T: AsRef<str>>(
&self,
key: T,
) -> Result<(TextureAtlas, Handle<Image>), RpackAtlasError> {
match self.files.get(key.as_ref()) {
Some(s) => Ok((
TextureAtlas {
index: *s,
layout: self.atlas.clone(),
},
self.image.clone(),
)
})
)),
None => Err(RpackAtlasError::WrongKey),
}
}
// When atlas contains the given key creates a Sprite component
pub fn make_sprite<T: AsRef<str>>(&self, key: T) -> Option<Sprite> {
if let Some((atlas, image)) = self.get_atlas_data(key) {
Some(Sprite {
image,
texture_atlas: Some(atlas),
..Default::default()
})
fn make_sprite_from_atlas<T: AsRef<str>>(&self, key: T) -> Result<Sprite, RpackAtlasError> {
if let Ok((atlas, image)) = self.find_atlas_data_by_key(key) {
Ok(Sprite::from_atlas_image(image, atlas))
} else {
None
Err(RpackAtlasError::WrongKey)
}
}
fn make_image_node_from_atlas<T: AsRef<str>>(
&self,
key: T,
) -> Result<ImageNode, RpackAtlasError> {
if let Ok((atlas, image)) = self.find_atlas_data_by_key(key) {
Ok(ImageNode::from_atlas_image(image, atlas))
} else {
Err(RpackAtlasError::WrongKey)
}
}
}
@ -69,12 +151,14 @@ pub struct RpackAssetPlugin;
impl Plugin for RpackAssetPlugin {
fn build(&self, app: &mut App) {
app.register_type::<super::AtlasAsset>();
app.register_type::<RpackAtlasAsset>();
app.init_asset::<RpackAtlasAsset>();
app.init_asset_loader::<RpackAtlasAssetLoader>();
}
}
/// Errors that can occur while loading or processing a `RpackAtlasAsset`.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum RpackAtlasAssetError {
@ -82,6 +166,7 @@ pub enum RpackAtlasAssetError {
/// during parsing of a `.rpack.json` file.
#[error("could not load asset: {0}")]
Io(#[from] std::io::Error),
/// An error that occurred while parsing the `.rpack.json` file into an asset structure.
#[error("could not parse asset: {0}")]
ParsingError(#[from] serde_json::Error),
/// A Bevy [`LoadDirectError`](bevy::asset::LoadDirectError) that occured
@ -101,6 +186,7 @@ impl From<bevy::asset::LoadDirectError> for RpackAtlasAssetError {
}
}
/// The loader responsible for loading `RpackAtlasAsset` files from `.rpack.json` files.
#[derive(Default)]
pub struct RpackAtlasAssetLoader;

View File

@ -1,9 +1,11 @@
use std::{collections::HashMap, path::Path};
use std::collections::HashMap;
use egui::{CollapsingHeader, Color32, DroppedFile, FontFamily, FontId, Image, RichText};
use image::{DynamicImage, GenericImageView};
use image::GenericImageView;
use rpack_cli::{ImageFile, Spritesheet};
use texture_packer::{importer::ImageImporter, TexturePackerConfig};
use texture_packer::TexturePackerConfig;
use crate::helpers::DroppedFileHelper;
pub const MY_ACCENT_COLOR32: Color32 = Color32::from_rgb(230, 102, 1);
pub const TOP_SIDE_MARGIN: f32 = 10.0;
pub const HEADER_HEIGHT: f32 = 45.0;
@ -13,7 +15,7 @@ pub const GIT_HASH: &str = env!("GIT_HASH");
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)] // if we add new fields, give them default values when deserializing old state
pub struct TemplateApp {
pub struct Application {
#[serde(skip)]
dropped_files: Vec<DroppedFile>,
#[serde(skip)]
@ -33,7 +35,7 @@ pub struct TemplateApp {
#[serde(skip)]
image_data: HashMap<String, ImageFile>,
}
impl Default for TemplateApp {
impl Default for Application {
fn default() -> Self {
Self {
dropped_files: vec![],
@ -57,13 +59,14 @@ impl Default for TemplateApp {
}
}
impl TemplateApp {
impl Application {
pub fn rebuild_image_data(&mut self) {
let prefix = Self::get_common_prefix(&self.dropped_files);
let prefix = crate::helpers::get_common_prefix(&self.dropped_files);
self.image_data = self
.dropped_files
.iter()
.flat_map(|f| Self::image_from_dropped_file(f, &prefix))
.flat_map(|f| f.create_image(&prefix))
.collect();
self.update_min_size();
}
@ -102,48 +105,6 @@ impl TemplateApp {
Default::default()
}
fn get_common_prefix(paths: &[DroppedFile]) -> String {
if paths.is_empty() {
return String::new();
} else if paths.len() == 1 {
let full_name = paths[0].file_path();
let path = Path::new(&full_name)
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
return full_name.strip_suffix(&path).unwrap_or_default().to_owned();
}
let mut prefix = paths[0].file_path();
for s in paths.iter().skip(1) {
let s = s.file_path();
while !s.starts_with(&prefix) {
prefix.pop(); // Remove the last character of the prefix
if prefix.is_empty() {
return String::new();
}
}
}
prefix
}
pub fn image_from_dropped_file<P>(file: &DroppedFile, prefix: P) -> Option<(String, ImageFile)>
where
P: AsRef<str>,
{
let path = file.file_path();
let base_id = path.replace(".png", "");
let id = base_id
.strip_prefix(prefix.as_ref())
.unwrap_or(&base_id)
.to_owned()
.replace("\\", "/");
let image: DynamicImage = dynamic_image_from_file(file)?;
Some((path, ImageFile { id, image }))
}
fn build_atlas(&mut self, ctx: &egui::Context) {
self.data = None;
@ -232,7 +193,7 @@ impl TemplateApp {
}
}
impl eframe::App for TemplateApp {
impl eframe::App for Application {
/// Called by the frame work to save state before shutdown.
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, self);
@ -273,6 +234,10 @@ impl eframe::App for TemplateApp {
.min_width(200.0)
.frame(egui::Frame::canvas(&ctx.style()))
.show_animated(ctx, !self.image_data.is_empty(), |ui| {
egui::ScrollArea::vertical()
.id_salt("leftPanel_scroll")
.show(ui, |ui| {
CollapsingHeader::new("Settings")
.default_open(true)
.show(ui, |ui| {
@ -350,6 +315,7 @@ impl eframe::App for TemplateApp {
}
}
});
});
});
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::vertical()
@ -415,8 +381,10 @@ impl eframe::App for TemplateApp {
.add(egui::Button::new("Copy JSON to Clipboard"))
.clicked()
{
let s = data.atlas_asset_json.to_string();
println!("JSON: {s}");
ui.output_mut(|o| {
o.copied_text = data.atlas_asset_json.to_string()
o.copied_text = s;
});
};
ui.add_space(10.0);
@ -442,49 +410,6 @@ impl eframe::App for TemplateApp {
}
}
trait FilePath {
fn file_path(&self) -> String;
}
impl FilePath 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();
}
#[cfg(target_arch = "wasm32")]
{
id = self.name.clone();
}
id.replace(".png", "")
}
}
fn dynamic_image_from_file(file: &DroppedFile) -> Option<DynamicImage> {
#[cfg(target_arch = "wasm32")]
{
let bytes = file.bytes.as_ref().clone()?;
if let Ok(r) = ImageImporter::import_from_memory(bytes) {
Some(r.into())
} else {
None
}
}
#[cfg(not(target_arch = "wasm32"))]
{
let path = file.path.as_ref()?;
if let Ok(r) = ImageImporter::import_from_file(path) {
Some(r)
} else {
None
}
}
}
fn powered_by_egui_and_eframe(ui: &mut egui::Ui) {
ui.horizontal(|ui| {
ui.hyperlink_to(format!("Build: {}", GIT_HASH), env!("CARGO_PKG_HOMEPAGE"));

View File

@ -0,0 +1,91 @@
use std::path::Path;
use egui::DroppedFile;
use image::DynamicImage;
use rpack_cli::ImageFile;
use texture_packer::importer::ImageImporter;
pub fn get_common_prefix(paths: &[DroppedFile]) -> String {
if paths.is_empty() {
return String::new();
}
let full_name = paths[0].file_path();
let path = Path::new(&full_name)
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
let mut prefix = full_name.strip_suffix(&path).unwrap_or_default().to_owned();
for s in paths.iter().skip(1) {
let s = s.file_path();
while !(s.starts_with(&prefix) || prefix.is_empty()) {
prefix.pop();
}
}
prefix
}
pub trait DroppedFileHelper {
fn file_path(&self) -> String;
fn create_image<P>(&self, prefix: P) -> Option<(String, ImageFile)>
where
P: AsRef<str>;
fn dynamic_image(&self) -> Option<DynamicImage>;
}
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();
}
#[cfg(target_arch = "wasm32")]
{
id = self.name.clone();
}
id.replace(".png", "").replace("\\", "/")
}
fn create_image<P>(&self, prefix: P) -> Option<(String, ImageFile)>
where
P: AsRef<str>,
{
let path = self.file_path();
let base_id = path.replace(".png", "");
let id = base_id
.strip_prefix(prefix.as_ref())
.unwrap_or(&base_id)
.to_owned();
let image: DynamicImage = self.dynamic_image()?;
Some((path, ImageFile { id, image }))
}
fn dynamic_image(&self) -> Option<DynamicImage> {
#[cfg(target_arch = "wasm32")]
{
let bytes = self.bytes.as_ref().clone()?;
if let Ok(r) = ImageImporter::import_from_memory(bytes) {
Some(r.into())
} else {
None
}
}
#[cfg(not(target_arch = "wasm32"))]
{
let path = self.path.as_ref()?;
if let Ok(r) = ImageImporter::import_from_file(path) {
Some(r)
} else {
None
}
}
}
}

View File

@ -2,4 +2,5 @@
mod app;
mod fonts;
pub use app::TemplateApp;
mod helpers;
pub use app::Application;

View File

@ -15,7 +15,7 @@ fn main() -> eframe::Result<()> {
eframe::run_native(
"rPack",
native_options,
Box::new(|cc| Ok(Box::new(rpack::TemplateApp::new(cc)))),
Box::new(|cc| Ok(Box::new(rpack::Application::new(cc)))),
)
}
@ -40,7 +40,7 @@ fn main() {
.start(
canvas,
web_options,
Box::new(|cc| Ok(Box::new(rpack::TemplateApp::new(cc)))),
Box::new(|cc| Ok(Box::new(rpack::Application::new(cc)))),
)
.await;