diff --git a/crates/bevy_rpack/Cargo.toml b/crates/bevy_rpack/Cargo.toml index 8232dd1..59a9a6f 100644 --- a/crates/bevy_rpack/Cargo.toml +++ b/crates/bevy_rpack/Cargo.toml @@ -12,15 +12,19 @@ exclude = ["assets", "tiles", "*.rpack_gen.json", "justfile"] [features] 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] -bevy = { version = "0.16", optional = true, default-features = false, features = [ - "bevy_asset", - "bevy_sprite", - "bevy_image", - "bevy_ui" -] } +bevy_math = { version = "0.16", optional = true } +bevy_image = { version = "0.16", optional = true } +bevy_app = { version = "0.16", optional = true } +bevy_asset = { version = "0.16", optional = true } +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_json = "1" thiserror = "2" diff --git a/crates/bevy_rpack/src/lib.rs b/crates/bevy_rpack/src/lib.rs index 69e6227..7a6b883 100644 --- a/crates/bevy_rpack/src/lib.rs +++ b/crates/bevy_rpack/src/lib.rs @@ -1,6 +1,10 @@ #![doc = include_str!("../README.md")] extern crate alloc; use alloc::borrow::Cow; +#[cfg(feature = "bevy")] +use bevy_asset::Asset; +#[cfg(feature = "bevy")] +use bevy_reflect::Reflect; #[cfg(feature = "bevy")] /// 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. #[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 { /// Horizontal position the rectangle begins at. pub x: u32, @@ -34,7 +38,7 @@ pub struct SerializableRect { /// 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))] +#[cfg_attr(feature = "bevy", derive(Reflect))] pub struct AtlasFrame { /// A unique identifier for the frame. pub key: String, @@ -44,7 +48,7 @@ pub struct AtlasFrame { /// 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))] +#[cfg_attr(feature = "bevy", derive(Asset, Reflect))] pub struct AtlasAsset { /// The overall dimensions of the texture atlas in pixels (width, height). pub size: [u32; 2], @@ -60,7 +64,7 @@ pub struct AtlasAsset { /// Represents metadata associated with the texture atlas format. #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[cfg_attr(feature = "bevy", derive(bevy::prelude::Reflect))] +#[cfg_attr(feature = "bevy", derive(Reflect))] pub struct AtlasMetadata { /// The version of the texture atlas format. pub format_version: u32, diff --git a/crates/bevy_rpack/src/plugin.rs b/crates/bevy_rpack/src/plugin.rs index 0f74758..4bc125e 100644 --- a/crates/bevy_rpack/src/plugin.rs +++ b/crates/bevy_rpack/src/plugin.rs @@ -1,9 +1,15 @@ use crate::{AtlasAsset, SerializableRect}; -use bevy::asset::{AssetLoader, AsyncReadExt}; -use bevy::ecs::system::SystemParam; -use bevy::image::ImageSampler; -use bevy::platform::collections::HashMap; -use bevy::prelude::*; +use bevy_app::{App, Plugin}; +use bevy_asset::{Asset, AssetApp, Assets, Handle, ReflectAsset}; +use bevy_asset::{AssetLoader, AsyncReadExt}; +use bevy_derive::{Deref, DerefMut}; +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; /// 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. #[derive(Asset, Debug, Reflect)] +#[reflect(Asset, Debug)] pub struct RpackAtlasAsset { /// The texture atlas image. pub image: Handle, @@ -180,7 +187,7 @@ pub enum RpackAtlasAssetError { /// A Bevy [`LoadDirectError`](bevy::asset::LoadDirectError) that occured /// while loading a [`RpackAtlasAsset::image`](crate::RpackAtlasAsset::image). #[error("could not load asset: {0}")] - LoadDirect(Box), + LoadDirect(Box), /// An error that can occur if there is /// trouble loading the image asset of /// an atlas. @@ -188,8 +195,8 @@ pub enum RpackAtlasAssetError { LoadingImageAsset(String), } -impl From for RpackAtlasAssetError { - fn from(value: bevy::asset::LoadDirectError) -> Self { +impl From for RpackAtlasAssetError { + fn from(value: bevy_asset::LoadDirectError) -> Self { Self::LoadDirect(Box::new(value)) } } @@ -209,7 +216,7 @@ pub struct RpackAtlasAssetLoaderSettings { impl Default for RpackAtlasAssetLoaderSettings { fn default() -> 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( &self, - reader: &mut dyn bevy::asset::io::Reader, + reader: &mut dyn bevy_asset::io::Reader, settings: &RpackAtlasAssetLoaderSettings, - load_context: &mut bevy::asset::LoadContext<'_>, + load_context: &mut bevy_asset::LoadContext<'_>, ) -> Result { let mut file = String::new(); reader.read_to_string(&mut file).await?; diff --git a/crates/rpack_cli/src/packer.rs b/crates/rpack_cli/src/packer.rs index f3eb3cd..f9102de 100644 --- a/crates/rpack_cli/src/packer.rs +++ b/crates/rpack_cli/src/packer.rs @@ -63,8 +63,8 @@ impl SkylinePacker { } 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 bottom = u32::MAX; + let mut width = u32::MAX; let mut index = None; let mut rect = Rect::new(0, 0, 0, 0); diff --git a/crates/rpack_egui/src/app.rs b/crates/rpack_egui/src/app.rs index 7b3d8b2..995c410 100644 --- a/crates/rpack_egui/src/app.rs +++ b/crates/rpack_egui/src/app.rs @@ -1,3 +1,5 @@ +use crate::helpers::DroppedFileHelper; +use crate::view_settings::ViewSettings; use crossbeam::queue::SegQueue; use egui::containers::menu::MenuButton; use egui::{ @@ -12,9 +14,6 @@ use rpack_cli::{ packer::SkylinePacker, ImageFile, Spritesheet, SpritesheetBuildConfig, SpritesheetError, }; 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"); @@ -198,7 +197,6 @@ impl Application { /// Called once before the first frame. #[allow(dead_code, unused_variables, unused_mut)] pub fn new(cc: &eframe::CreationContext<'_>, config_file: Option) -> Self { - crate::fonts::setup_custom_fonts(&cc.egui_ctx); // 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`. egui_extras::install_image_loaders(&cc.egui_ctx); @@ -213,6 +211,13 @@ impl Application { } else { 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 { last_editor_paths, view_settings, diff --git a/crates/rpack_egui/src/fonts.rs b/crates/rpack_egui/src/fonts.rs index 064bf4a..52c9ac3 100644 --- a/crates/rpack_egui/src/fonts.rs +++ b/crates/rpack_egui/src/fonts.rs @@ -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). let mut fonts = egui::FontDefinitions::default(); if let Ok((regular, semibold)) = get_fonts() { @@ -29,35 +32,33 @@ pub fn setup_custom_fonts(ctx: &egui::Context) { .entry(egui::FontFamily::Monospace) .or_default() .push("regular".to_owned()); - // Tell egui to use these 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, Vec)> { - 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"))] fn get_fonts() -> anyhow::Result<(Vec, Vec)> { - let Some(regular) = - try_get_font_from_list(&["JetBrainsMonoNL-Regular", "SFNSRounded", "aptos"]) + get_fonts_from_list( + &["SFNSRounded", "aptos"], + &["SFNSRounded", "aptos-semibold"], + ) +} + +#[cfg(not(target_arch = "wasm32"))] +fn get_fonts_from_list( + regular: &[&'static str], + semibold: &[&'static str], +) -> anyhow::Result<(Vec, Vec)> { + let Some(regular) = regular + .iter() + .find_map(|f| try_get_font_path_from_os(f).and_then(|path| std::fs::read(path).ok())) else { anyhow::bail!("Failed to find a suitable font"); }; - let Some(semibold) = - try_get_font_from_list(&["JetBrainsMono-SemiBold", "SFNSRounded", "aptos-semibold"]) + let Some(semibold) = semibold + .iter() + .find_map(|f| try_get_font_path_from_os(f).and_then(|path| std::fs::read(path).ok())) else { anyhow::bail!("Failed to find a suitable font"); }; @@ -65,75 +66,140 @@ fn get_fonts() -> anyhow::Result<(Vec, Vec)> { Ok((regular, semibold)) } -#[cfg(not(target_arch = "wasm32"))] -fn try_get_font_from_list(font_names: &[&str]) -> Option> { - for font_name in font_names { - if let Some(font) = try_get_font(font_name) { - return Some(font); - } +#[allow(unused)] +trait FontLoader { + fn check_for_font(self, font_file_names: &[PathBuf]) -> Option; +} + +impl FontLoader for &PathBuf { + fn check_for_font(self, font_file_names: &[PathBuf]) -> Option { + 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 { + 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 { + 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 { + 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 + } +} + +impl FontLoader for (Option, &str) { + fn check_for_font(self, font_file_names: &[PathBuf]) -> Option { + 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) } - None } #[cfg(not(target_arch = "wasm32"))] -fn try_get_font(font_name: &str) -> Option> { - use std::path::Path; - - for dir in font_dirs() { - if let Ok(font) = std::fs::read(Path::new(&dir).join(format!("{}.ttf", font_name))) { - return Some(font); - } - if let Ok(font) = std::fs::read(Path::new(&dir).join(format!("{}.otf", font_name))) { - return Some(font); - } - } - None +pub fn try_get_font_path_from_os(font_name: &str) -> Option { + 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)) } #[cfg(not(target_arch = "wasm32"))] -fn font_dirs() -> Vec { - let mut dirs = Vec::new(); +fn build_path_from_env(env_var: &str, subpath: &str) -> Option { + std::env::var(env_var) + .ok() + .and_then(|d| PathBuf::from_str(d.as_str()).map(|p| p.join(subpath)).ok()) +} +#[cfg(not(target_arch = "wasm32"))] +#[allow(unused)] +pub fn get_system_fonts() -> Vec { + get_font_paths() + .iter() + .flat_map(|s| { + let dir = std::fs::read_dir(s).ok()?; + let dir_entries: Vec = 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 + } + }) + .collect(); + Some(dir_entries) + }) + .flatten() + .collect() +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn get_font_paths() -> Vec { + #[cfg(target_os = "windows")] + if let Some(path) = build_path_from_env("LOCALAPPDATA", "Microsoft/Windows/Fonts") { + vec![path] + } else { + vec![] + } #[cfg(target_os = "linux")] { - dirs.push("/usr/share/fonts".into()); - dirs.push("/usr/share/fonts/truetype".into()); + let mut results = vec![ + PathBuf::from_str("/usr/share/fonts").unwrap_or_default(), + 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(unix)] + #[cfg(target_os = "macos")] { - 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())); - } + 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 } - #[cfg(target_os = "windows")] - { - if let Ok(dir) = std::env::var("APPDATA") { - let font_path = std::path::Path::new(&dir).join("../Local/Microsoft/Windows/Fonts/"); - dirs.push(font_path.display().to_string()); - } - } - - dirs } diff --git a/crates/rpack_egui/static/JetBrainsMono-SemiBold.ttf b/crates/rpack_egui/static/JetBrainsMono-SemiBold.ttf deleted file mode 100644 index a70e69b..0000000 Binary files a/crates/rpack_egui/static/JetBrainsMono-SemiBold.ttf and /dev/null differ diff --git a/crates/rpack_egui/static/JetBrainsMonoNL-Regular.ttf b/crates/rpack_egui/static/JetBrainsMonoNL-Regular.ttf deleted file mode 100644 index 70d2ec9..0000000 Binary files a/crates/rpack_egui/static/JetBrainsMonoNL-Regular.ttf and /dev/null differ