fix(assets,theme): remove assert in svg_loader, log theme failures, fix default theme id (#58, #63, #64)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -280,7 +280,7 @@ fn default_music_volume() -> f32 {
|
||||
}
|
||||
|
||||
fn default_theme_id() -> String {
|
||||
"classic".to_string()
|
||||
"dark".to_string()
|
||||
}
|
||||
|
||||
/// Default tooltip-hover dwell delay in seconds. Mirrors
|
||||
|
||||
@@ -24,6 +24,7 @@ use std::sync::{Arc, OnceLock};
|
||||
use bevy::asset::io::Reader;
|
||||
use bevy::asset::{AssetLoader, LoadContext, RenderAssetUsages};
|
||||
use bevy::image::Image;
|
||||
use bevy::log::warn;
|
||||
use bevy::math::UVec2;
|
||||
use bevy::reflect::TypePath;
|
||||
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
|
||||
@@ -156,7 +157,7 @@ pub fn rasterize_svg(svg_bytes: &[u8], target: UVec2) -> Result<Image, SvgLoader
|
||||
/// share the same canonical face.
|
||||
const BUNDLED_FONT_BYTES: &[u8] = include_bytes!("../../../assets/fonts/main.ttf");
|
||||
|
||||
/// Returns a process-wide font database holding only the bundled
|
||||
/// Returns a process-wide font database that tries to load the bundled
|
||||
/// FiraMono-Medium face. Initialised lazily on first SVG that references
|
||||
/// text, then shared (via `Arc`) across every subsequent rasterisation.
|
||||
///
|
||||
@@ -165,17 +166,19 @@ const BUNDLED_FONT_BYTES: &[u8] = include_bytes!("../../../assets/fonts/main.ttf
|
||||
/// such request directly to FiraMono so rasterisation is deterministic
|
||||
/// across machines and the system font path is never consulted.
|
||||
///
|
||||
/// Aborts the program if the embedded bytes don't parse — bundled at
|
||||
/// compile time, so a parse failure means the binary is corrupt.
|
||||
/// If the embedded bytes fail to yield any faces, log a warning and
|
||||
/// fall back to an empty database so startup can continue.
|
||||
fn shared_fontdb() -> Arc<fontdb::Database> {
|
||||
static DB: OnceLock<Arc<fontdb::Database>> = OnceLock::new();
|
||||
DB.get_or_init(|| {
|
||||
let mut db = fontdb::Database::new();
|
||||
db.load_font_data(BUNDLED_FONT_BYTES.to_vec());
|
||||
assert!(
|
||||
db.faces().next().is_some(),
|
||||
"bundled FiraMono failed to parse — binary is corrupt"
|
||||
);
|
||||
let loaded_faces = db.load_font_source(fontdb::Source::Binary(Arc::new(
|
||||
BUNDLED_FONT_BYTES.to_vec(),
|
||||
)));
|
||||
if loaded_faces.is_empty() {
|
||||
let e = "no faces loaded from bundled bytes";
|
||||
warn!("Failed to load bundled FiraMono font: {e}");
|
||||
}
|
||||
Arc::new(db)
|
||||
})
|
||||
.clone()
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use bevy::log::warn;
|
||||
use bevy::prelude::{App, Plugin, Resource, Startup};
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::ThemeMeta;
|
||||
use crate::assets::{user_theme_dir, DARK_THEME_MANIFEST_URL};
|
||||
use crate::assets::{DARK_THEME_MANIFEST_URL, user_theme_dir};
|
||||
|
||||
/// One entry in the [`ThemeRegistry`] — the data the picker UI needs
|
||||
/// to render a row and load the theme on selection.
|
||||
@@ -143,7 +144,7 @@ fn classic_entry() -> ThemeEntry {
|
||||
/// Walks `user_dir`, treating every immediate subdirectory as a
|
||||
/// candidate theme. A subdirectory contributes one entry if and only
|
||||
/// if it contains a `theme.ron` whose `meta` block parses cleanly and
|
||||
/// passes `ThemeMeta::validate`. Failed candidates are silently
|
||||
/// passes `ThemeMeta::validate`. Failed candidates are warned and
|
||||
/// skipped — broken themes don't poison discovery.
|
||||
fn discover_user_themes(user_dir: &Path) -> Vec<ThemeEntry> {
|
||||
let mut out = Vec::new();
|
||||
@@ -184,9 +185,29 @@ struct ManifestMetaOnly {
|
||||
/// [`ThemeEntry`]. Returns `None` for any I/O / parse / validation
|
||||
/// failure — discovery is best-effort.
|
||||
fn read_meta_only(manifest_path: &Path) -> Option<ThemeEntry> {
|
||||
let bytes = std::fs::read(manifest_path).ok()?;
|
||||
let parsed: ManifestMetaOnly = ron::de::from_bytes(&bytes).ok()?;
|
||||
parsed.meta.validate().ok()?;
|
||||
let theme_id = manifest_path
|
||||
.parent()
|
||||
.and_then(Path::file_name)
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("<unknown>");
|
||||
let bytes = match std::fs::read(manifest_path) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
warn!("Skipping theme '{}': {}", theme_id, e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let parsed: ManifestMetaOnly = match ron::de::from_bytes(&bytes) {
|
||||
Ok(parsed) => parsed,
|
||||
Err(e) => {
|
||||
warn!("Skipping theme '{}': {}", theme_id, e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if let Err(e) = parsed.meta.validate() {
|
||||
warn!("Skipping theme '{}': {}", theme_id, e);
|
||||
return None;
|
||||
}
|
||||
let id = parsed.meta.id.clone();
|
||||
let display_name = parsed.meta.name.clone();
|
||||
let manifest_url = format!("themes://{id}/theme.ron");
|
||||
|
||||
Reference in New Issue
Block a user