Use bevy packages instead of main one

This commit is contained in:
Piotr Siuszko 2025-09-30 11:20:34 +02:00
parent 0f63f9860b
commit bc20f9b8b9
8 changed files with 191 additions and 105 deletions

View File

@ -12,15 +12,19 @@ exclude = ["assets", "tiles", "*.rpack_gen.json", "justfile"]
[features] [features]
default = ["bevy"] default = ["bevy"]
bevy = ["dep:bevy"] bevy = ["dep:bevy_app", "dep:bevy_platform", "dep:bevy_math", "dep:bevy_image", "dep:bevy_asset", "dep:bevy_ecs", "dep:bevy_reflect", "dep:bevy_ui", "dep:bevy_derive", "dep:bevy_sprite"]
[dependencies] [dependencies]
bevy = { version = "0.16", optional = true, default-features = false, features = [ bevy_math = { version = "0.16", optional = true }
"bevy_asset", bevy_image = { version = "0.16", optional = true }
"bevy_sprite", bevy_app = { version = "0.16", optional = true }
"bevy_image", bevy_asset = { version = "0.16", optional = true }
"bevy_ui" bevy_ecs = { version = "0.16", optional = true }
] } bevy_reflect = { version = "0.16", optional = true }
bevy_ui = { version = "0.16", optional = true }
bevy_derive = { version = "0.16", optional = true }
bevy_platform = { version = "0.16", optional = true }
bevy_sprite = { version = "0.16", optional = true }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
thiserror = "2" thiserror = "2"

View File

@ -1,6 +1,10 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
extern crate alloc; extern crate alloc;
use alloc::borrow::Cow; use alloc::borrow::Cow;
#[cfg(feature = "bevy")]
use bevy_asset::Asset;
#[cfg(feature = "bevy")]
use bevy_reflect::Reflect;
#[cfg(feature = "bevy")] #[cfg(feature = "bevy")]
/// Contains the Bevy plugin for handling `Rpack` assets and atlases. /// Contains the Bevy plugin for handling `Rpack` assets and atlases.
@ -20,7 +24,7 @@ pub mod prelude {
/// Defines a rectangle in pixels with the origin at the top-left of the texture atlas. /// Defines a rectangle in pixels with the origin at the top-left of the texture atlas.
#[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "bevy", derive(bevy::prelude::Reflect))] #[cfg_attr(feature = "bevy", derive(Reflect))]
pub struct SerializableRect { pub struct SerializableRect {
/// Horizontal position the rectangle begins at. /// Horizontal position the rectangle begins at.
pub x: u32, pub x: u32,
@ -34,7 +38,7 @@ pub struct SerializableRect {
/// Represents a single frame within a texture atlas, including its identifier and position. /// Represents a single frame within a texture atlas, including its identifier and position.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "bevy", derive(bevy::prelude::Reflect))] #[cfg_attr(feature = "bevy", derive(Reflect))]
pub struct AtlasFrame { pub struct AtlasFrame {
/// A unique identifier for the frame. /// A unique identifier for the frame.
pub key: String, pub key: String,
@ -44,7 +48,7 @@ pub struct AtlasFrame {
/// Represents an entire texture atlas asset, including its metadata and frames. /// Represents an entire texture atlas asset, including its metadata and frames.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "bevy", derive(bevy::prelude::Asset, bevy::prelude::Reflect))] #[cfg_attr(feature = "bevy", derive(Asset, Reflect))]
pub struct AtlasAsset { pub struct AtlasAsset {
/// The overall dimensions of the texture atlas in pixels (width, height). /// The overall dimensions of the texture atlas in pixels (width, height).
pub size: [u32; 2], pub size: [u32; 2],
@ -60,7 +64,7 @@ pub struct AtlasAsset {
/// Represents metadata associated with the texture atlas format. /// Represents metadata associated with the texture atlas format.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "bevy", derive(bevy::prelude::Reflect))] #[cfg_attr(feature = "bevy", derive(Reflect))]
pub struct AtlasMetadata { pub struct AtlasMetadata {
/// The version of the texture atlas format. /// The version of the texture atlas format.
pub format_version: u32, pub format_version: u32,

View File

@ -1,9 +1,15 @@
use crate::{AtlasAsset, SerializableRect}; use crate::{AtlasAsset, SerializableRect};
use bevy::asset::{AssetLoader, AsyncReadExt}; use bevy_app::{App, Plugin};
use bevy::ecs::system::SystemParam; use bevy_asset::{Asset, AssetApp, Assets, Handle, ReflectAsset};
use bevy::image::ImageSampler; use bevy_asset::{AssetLoader, AsyncReadExt};
use bevy::platform::collections::HashMap; use bevy_derive::{Deref, DerefMut};
use bevy::prelude::*; use bevy_ecs::system::{Res, SystemParam};
use bevy_image::{Image, ImageSampler, TextureAtlas, TextureAtlasLayout};
use bevy_math::{URect, UVec2};
use bevy_platform::collections::HashMap;
use bevy_reflect::Reflect;
use bevy_sprite::Sprite;
use bevy_ui::widget::ImageNode;
use thiserror::Error; use thiserror::Error;
/// Errors that can occur while accessing and creating components from [`RpackAtlasAsset`]. /// Errors that can occur while accessing and creating components from [`RpackAtlasAsset`].
@ -19,6 +25,7 @@ pub enum RpackAtlasError {
/// 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. /// 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)] #[derive(Asset, Debug, Reflect)]
#[reflect(Asset, Debug)]
pub struct RpackAtlasAsset { pub struct RpackAtlasAsset {
/// The texture atlas image. /// The texture atlas image.
pub image: Handle<Image>, pub image: Handle<Image>,
@ -180,7 +187,7 @@ pub enum RpackAtlasAssetError {
/// A Bevy [`LoadDirectError`](bevy::asset::LoadDirectError) that occured /// A Bevy [`LoadDirectError`](bevy::asset::LoadDirectError) that occured
/// while loading a [`RpackAtlasAsset::image`](crate::RpackAtlasAsset::image). /// while loading a [`RpackAtlasAsset::image`](crate::RpackAtlasAsset::image).
#[error("could not load asset: {0}")] #[error("could not load asset: {0}")]
LoadDirect(Box<bevy::asset::LoadDirectError>), LoadDirect(Box<bevy_asset::LoadDirectError>),
/// An error that can occur if there is /// An error that can occur if there is
/// trouble loading the image asset of /// trouble loading the image asset of
/// an atlas. /// an atlas.
@ -188,8 +195,8 @@ pub enum RpackAtlasAssetError {
LoadingImageAsset(String), LoadingImageAsset(String),
} }
impl From<bevy::asset::LoadDirectError> for RpackAtlasAssetError { impl From<bevy_asset::LoadDirectError> for RpackAtlasAssetError {
fn from(value: bevy::asset::LoadDirectError) -> Self { fn from(value: bevy_asset::LoadDirectError) -> Self {
Self::LoadDirect(Box::new(value)) Self::LoadDirect(Box::new(value))
} }
} }
@ -209,7 +216,7 @@ pub struct RpackAtlasAssetLoaderSettings {
impl Default for RpackAtlasAssetLoaderSettings { impl Default for RpackAtlasAssetLoaderSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
image_sampler: ImageSampler::Descriptor(bevy::image::ImageSamplerDescriptor::nearest()), image_sampler: ImageSampler::Descriptor(bevy_image::ImageSamplerDescriptor::nearest()),
} }
} }
} }
@ -229,9 +236,9 @@ impl AssetLoader for RpackAtlasAssetLoader {
async fn load( async fn load(
&self, &self,
reader: &mut dyn bevy::asset::io::Reader, reader: &mut dyn bevy_asset::io::Reader,
settings: &RpackAtlasAssetLoaderSettings, settings: &RpackAtlasAssetLoaderSettings,
load_context: &mut bevy::asset::LoadContext<'_>, load_context: &mut bevy_asset::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> { ) -> Result<Self::Asset, Self::Error> {
let mut file = String::new(); let mut file = String::new();
reader.read_to_string(&mut file).await?; reader.read_to_string(&mut file).await?;

View File

@ -63,8 +63,8 @@ impl SkylinePacker {
} }
pub fn find_skyline(&self, w: u32, h: u32) -> Option<(usize, Rect)> { pub fn find_skyline(&self, w: u32, h: u32) -> Option<(usize, Rect)> {
let mut bottom = std::u32::MAX; let mut bottom = u32::MAX;
let mut width = std::u32::MAX; let mut width = u32::MAX;
let mut index = None; let mut index = None;
let mut rect = Rect::new(0, 0, 0, 0); let mut rect = Rect::new(0, 0, 0, 0);

View File

@ -1,3 +1,5 @@
use crate::helpers::DroppedFileHelper;
use crate::view_settings::ViewSettings;
use crossbeam::queue::SegQueue; use crossbeam::queue::SegQueue;
use egui::containers::menu::MenuButton; use egui::containers::menu::MenuButton;
use egui::{ use egui::{
@ -12,9 +14,6 @@ use rpack_cli::{
packer::SkylinePacker, ImageFile, Spritesheet, SpritesheetBuildConfig, SpritesheetError, packer::SkylinePacker, ImageFile, Spritesheet, SpritesheetBuildConfig, SpritesheetError,
}; };
use texture_packer::{Rect, TexturePackerConfig}; use texture_packer::{Rect, TexturePackerConfig};
use crate::helpers::DroppedFileHelper;
use crate::view_settings::ViewSettings;
static INPUT_QUEUE: Lazy<SegQueue<AppImageAction>> = Lazy::new(SegQueue::new); static INPUT_QUEUE: Lazy<SegQueue<AppImageAction>> = Lazy::new(SegQueue::new);
pub const MY_ACCENT_COLOR32: Color32 = Color32::from_rgb(230, 102, 1); pub const MY_ACCENT_COLOR32: Color32 = Color32::from_rgb(230, 102, 1);
pub const GIT_HASH: &str = env!("GIT_HASH"); pub const GIT_HASH: &str = env!("GIT_HASH");
@ -198,7 +197,6 @@ impl Application {
/// Called once before the first frame. /// Called once before the first frame.
#[allow(dead_code, unused_variables, unused_mut)] #[allow(dead_code, unused_variables, unused_mut)]
pub fn new(cc: &eframe::CreationContext<'_>, config_file: Option<String>) -> Self { pub fn new(cc: &eframe::CreationContext<'_>, config_file: Option<String>) -> Self {
crate::fonts::setup_custom_fonts(&cc.egui_ctx);
// This is also where you can customize the look and feel of egui using // This is also where you can customize the look and feel of egui using
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`. // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
egui_extras::install_image_loaders(&cc.egui_ctx); egui_extras::install_image_loaders(&cc.egui_ctx);
@ -213,6 +211,13 @@ impl Application {
} else { } else {
Default::default() Default::default()
}; };
cc.egui_ctx.all_styles_mut(|style| {
for font_id in style.text_styles.values_mut() {
font_id.size *= 1.4;
}
});
#[cfg(not(target_arch = "wasm32"))]
crate::fonts::load_fonts(&cc.egui_ctx);
let mut app = Self { let mut app = Self {
last_editor_paths, last_editor_paths,
view_settings, view_settings,

View File

@ -1,4 +1,7 @@
pub fn setup_custom_fonts(ctx: &egui::Context) { use std::{ffi::OsString, path::PathBuf, slice::Iter, str::FromStr};
#[cfg(not(target_arch = "wasm32"))]
pub fn load_fonts(ctx: &egui::Context) {
// Start with the default fonts (we will be adding to them rather than replacing them). // Start with the default fonts (we will be adding to them rather than replacing them).
let mut fonts = egui::FontDefinitions::default(); let mut fonts = egui::FontDefinitions::default();
if let Ok((regular, semibold)) = get_fonts() { if let Ok((regular, semibold)) = get_fonts() {
@ -29,35 +32,33 @@ pub fn setup_custom_fonts(ctx: &egui::Context) {
.entry(egui::FontFamily::Monospace) .entry(egui::FontFamily::Monospace)
.or_default() .or_default()
.push("regular".to_owned()); .push("regular".to_owned());
// Tell egui to use these fonts: // Tell egui to use these fonts:
ctx.set_fonts(fonts); ctx.set_fonts(fonts);
} }
ctx.all_styles_mut(|style| {
for font_id in style.text_styles.values_mut() {
font_id.size *= 1.4;
}
});
}
#[cfg(target_arch = "wasm32")]
fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
let regular = include_bytes!("../static/JetBrainsMonoNL-Regular.ttf").to_vec();
let semibold = include_bytes!("../static/JetBrainsMono-SemiBold.ttf").to_vec();
Ok((regular, semibold))
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> { fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
let Some(regular) = get_fonts_from_list(
try_get_font_from_list(&["JetBrainsMonoNL-Regular", "SFNSRounded", "aptos"]) &["SFNSRounded", "aptos"],
&["SFNSRounded", "aptos-semibold"],
)
}
#[cfg(not(target_arch = "wasm32"))]
fn get_fonts_from_list(
regular: &[&'static str],
semibold: &[&'static str],
) -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
let Some(regular) = regular
.iter()
.find_map(|f| try_get_font_path_from_os(f).and_then(|path| std::fs::read(path).ok()))
else { else {
anyhow::bail!("Failed to find a suitable font"); anyhow::bail!("Failed to find a suitable font");
}; };
let Some(semibold) = let Some(semibold) = semibold
try_get_font_from_list(&["JetBrainsMono-SemiBold", "SFNSRounded", "aptos-semibold"]) .iter()
.find_map(|f| try_get_font_path_from_os(f).and_then(|path| std::fs::read(path).ok()))
else { else {
anyhow::bail!("Failed to find a suitable font"); anyhow::bail!("Failed to find a suitable font");
}; };
@ -65,75 +66,140 @@ fn get_fonts() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
Ok((regular, semibold)) Ok((regular, semibold))
} }
#[cfg(not(target_arch = "wasm32"))] #[allow(unused)]
fn try_get_font_from_list(font_names: &[&str]) -> Option<Vec<u8>> { trait FontLoader {
for font_name in font_names { fn check_for_font(self, font_file_names: &[PathBuf]) -> Option<PathBuf>;
if let Some(font) = try_get_font(font_name) { }
return Some(font);
impl FontLoader for &PathBuf {
fn check_for_font(self, font_file_names: &[PathBuf]) -> Option<PathBuf> {
font_file_names.iter().find_map(|f| {
if self.join(f).exists() {
Some(self.join(f))
} else {
None
}
})
}
}
impl FontLoader for &str {
fn check_for_font(self, font_file_names: &[PathBuf]) -> Option<PathBuf> {
let path = PathBuf::from_str(self).ok()?;
font_file_names.iter().find_map(|f| {
if path.join(f).exists() {
Some(path.join(f))
} else {
None
}
})
}
}
impl FontLoader for (&str, &str) {
fn check_for_font(self, font_file_names: &[PathBuf]) -> Option<PathBuf> {
let path = PathBuf::from_str(self.0).ok()?;
let path = path.join(self.1);
path.check_for_font(font_file_names)
}
}
impl FontLoader for Iter<'_, &'static str> {
fn check_for_font(self, font_file_names: &[PathBuf]) -> Option<PathBuf> {
for path in self.into_iter().flat_map(|s| PathBuf::from_str(s).ok()) {
if let Some(font_path) = path.check_for_font(font_file_names) {
return Some(font_path);
} }
} }
None None
} }
}
impl FontLoader for (Option<OsString>, &str) {
fn check_for_font(self, font_file_names: &[PathBuf]) -> Option<PathBuf> {
let path = self
.0
.and_then(|f| PathBuf::from_str(&f.to_string_lossy()).ok())?;
let path = path.join(self.1);
path.check_for_font(font_file_names)
}
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn try_get_font(font_name: &str) -> Option<Vec<u8>> { pub fn try_get_font_path_from_os(font_name: &str) -> Option<PathBuf> {
use std::path::Path; let file_paths_to_check = if font_name.ends_with(".ttf") || font_name.ends_with(".otf") {
vec![PathBuf::from(font_name)]
} else {
vec![
PathBuf::from(format!("{}.ttf", font_name)),
PathBuf::from(format!("{}.otf", font_name)),
]
};
get_font_paths()
.iter()
.find_map(|dir| dir.check_for_font(&file_paths_to_check))
}
for dir in font_dirs() { #[cfg(not(target_arch = "wasm32"))]
if let Ok(font) = std::fs::read(Path::new(&dir).join(format!("{}.ttf", font_name))) { fn build_path_from_env(env_var: &str, subpath: &str) -> Option<PathBuf> {
return Some(font); std::env::var(env_var)
} .ok()
if let Ok(font) = std::fs::read(Path::new(&dir).join(format!("{}.otf", font_name))) { .and_then(|d| PathBuf::from_str(d.as_str()).map(|p| p.join(subpath)).ok())
return Some(font);
}
} }
#[cfg(not(target_arch = "wasm32"))]
#[allow(unused)]
pub fn get_system_fonts() -> Vec<String> {
get_font_paths()
.iter()
.flat_map(|s| {
let dir = std::fs::read_dir(s).ok()?;
let dir_entries: Vec<String> = dir
.flat_map(|e| {
let entry = e.ok()?;
if entry.file_name().to_string_lossy().ends_with("ttf")
|| entry.file_name().to_string_lossy().ends_with("otf")
{
Some(
entry
.file_name()
.to_string_lossy()
.replace(".ttf", "")
.replace(".otf", ""),
)
} else {
None None
} }
})
.collect();
Some(dir_entries)
})
.flatten()
.collect()
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn font_dirs() -> Vec<String> { pub fn get_font_paths() -> Vec<PathBuf> {
let mut dirs = Vec::new();
#[cfg(target_os = "linux")]
{
dirs.push("/usr/share/fonts".into());
dirs.push("/usr/share/fonts/truetype".into());
}
#[cfg(unix)]
{
use std::{path::PathBuf, str::FromStr};
#[cfg(target_os = "macos")]
{
dirs.push("/System/Library/Fonts".into());
if let Some(resources_font_dir) = std::env::current_exe().ok().and_then(|p| {
p.ancestors()
.nth(2)
.map(|p| p.join("Resources/fonts").to_string_lossy().into_owned())
}) {
dirs.push(resources_font_dir);
}
}
if let Some(home) =
std::env::var_os("HOME").and_then(|s| PathBuf::from_str(&s.to_string_lossy()).ok())
{
#[cfg(target_os = "macos")]
{
dirs.push(format!("{}/Library/Fonts", home.display()));
}
#[cfg(target_os = "linux")]
{
dirs.push(format!("{}/.local/share/fonts", home.display()));
}
}
}
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
if let Some(path) = build_path_from_env("LOCALAPPDATA", "Microsoft/Windows/Fonts") {
vec![path]
} else {
vec![]
}
#[cfg(target_os = "linux")]
{ {
if let Ok(dir) = std::env::var("APPDATA") { let mut results = vec![
let font_path = std::path::Path::new(&dir).join("../Local/Microsoft/Windows/Fonts/"); PathBuf::from_str("/usr/share/fonts").unwrap_or_default(),
dirs.push(font_path.display().to_string()); PathBuf::from_str("/usr/share/fonts/truetype").unwrap_or_default(),
];
if let Some(path) = build_path_from_env("HOME", ".local/share/fonts") {
results.push(path);
}
results
}
#[cfg(target_os = "macos")]
{
let mut results = vec![PathBuf::from_str("/System/Library/Fonts").unwrap_or_default()];
if let Some(path) = build_path_from_env("HOME", "Library/Fonts") {
results.push(path);
}
results
} }
} }
dirs
}