From aba7298ab323ba090a707005bf080101776bff4c Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Thu, 24 Sep 2026 19:44:21 +0800 Subject: [PATCH] app: package the UI icons and resolve assets at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the packager resources only carried assets/i18n, so installed builds showed blank toolbar glyphs: add assets/icons to the Windows/macOS packages and install it to /usr/share/oak/icons in the deb/rpm/pkg scripts; - icon_path resolved through the compile-time CARGO_MANIFEST_DIR (the CI checkout path), which never exists on a user machine: search the runtime layouts instead — dev checkout, next to the executable, Contents/Resources, usr/lib/oak-editor (cargo-packager's deb/AppImage layout) and /usr/share/oak — mirroring the i18n pack search, which gains the same usr/lib/oak-editor candidate so the AppImage finds its language packs too. Verified: oak-app 556 tests pass; the deb and rpm now carry /usr/share/oak/icons/{dark,light}/*.png. --- crates/oak-app/Cargo.toml | 4 +++ crates/oak-app/src/i18n.rs | 2 ++ crates/oak-app/src/oakui/icons.rs | 42 ++++++++++++++++++++++++++----- tooling/package/PKGBUILD | 8 ++++++ tooling/package/build-deb.sh | 4 +++ tooling/package/build-rpm.sh | 4 +++ 6 files changed, 58 insertions(+), 6 deletions(-) diff --git a/crates/oak-app/Cargo.toml b/crates/oak-app/Cargo.toml index 21ba45580..24f9cf34b 100644 --- a/crates/oak-app/Cargo.toml +++ b/crates/oak-app/Cargo.toml @@ -147,12 +147,16 @@ category = "Video" icons = ["../../icons/icon.png"] # The UI string tables (language packs); users can drop extra # .yaml files into the installed i18n directory. +# The theme-aware UI glyphs (dark/ + light/), resolved at runtime by +# crates/oak-app/src/oakui/icons.rs from `/icons` (Windows/Linux +# packages) or `Contents/Resources/icons` (macOS bundle). # The Windows DLL staging dir is filled by # tooling/package/bundle-dylibs-windows.sh before `cargo packager` runs # (NSIS/WiX place resources next to the executable, which is exactly # where Windows resolves DLLs from). Empty locally = no bundled DLLs. resources = [ "../../assets/i18n", + "../../assets/icons", { src = "../../target/pkg/win-dlls/*.dll", target = "." }, ] binaries = [ diff --git a/crates/oak-app/src/i18n.rs b/crates/oak-app/src/i18n.rs index e17ed21c6..76bc6439b 100644 --- a/crates/oak-app/src/i18n.rs +++ b/crates/oak-app/src/i18n.rs @@ -219,6 +219,8 @@ fn pack_dirs() -> Vec { if let Some(contents) = dir.parent() { // macOS bundle: /../Resources/i18n. dirs.push(contents.join("Resources/i18n")); + // cargo-packager deb/AppImage: /../lib/oak-editor/i18n. + dirs.push(contents.join("lib/oak-editor/i18n")); // System install (deb/rpm/pkg): /../share/oak/i18n. dirs.push(contents.join("share/oak/i18n")); } diff --git a/crates/oak-app/src/oakui/icons.rs b/crates/oak-app/src/oakui/icons.rs index 40f1dd984..d19ee7e0e 100644 --- a/crates/oak-app/src/oakui/icons.rs +++ b/crates/oak-app/src/oakui/icons.rs @@ -49,8 +49,12 @@ pub const ICON_NEXT: &str = "next"; pub const ICON_REW: &str = "rew"; pub const ICON_FF: &str = "ff"; -/// The theme-dependent filesystem path of an icon (`assets/icons/{dark,light}` -/// under the crate root). Absolute, so it works from any working directory. +/// The theme-dependent filesystem path of an icon. The icons root is +/// resolved at runtime — the packagers install `assets/icons` next to the +/// binary (Windows/Linux packages), into `Contents/Resources` (macOS +/// bundle) or `/usr/share/oak` (deb/rpm/pacman system installs) — with +/// the dev checkout as a fallback. Absolute, so it works from any working +/// directory. pub fn icon_path(name: &str, cx: &App) -> PathBuf { let theme = gpui_widgets::theme::current_theme(cx); let family = if theme.name == "Olive Dark" { @@ -58,10 +62,36 @@ pub fn icon_path(name: &str, cx: &App) -> PathBuf { } else { "light" }; - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../assets/icons") - .join(family) - .join(format!("{name}.png")) + icon_root().join(family).join(format!("{name}.png")) +} + +/// The first icons directory that exists, in precedence order (mirrors the +/// i18n pack search in `crate::i18n`). Cached: one process resolves one +/// directory. +fn icon_root() -> &'static PathBuf { + static ROOT: std::sync::OnceLock = std::sync::OnceLock::new(); + ROOT.get_or_init(|| { + let dev = || PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../assets/icons"); + let mut candidates = vec![dev()]; + if let Ok(exe) = std::env::current_exe() { + if let Some(dir) = exe.parent() { + candidates.push(dir.join("icons")); + candidates.push(dir.join("assets/icons")); + if let Some(contents) = dir.parent() { + candidates.push(contents.join("Resources/icons")); + candidates.push(contents.join("Resources/assets/icons")); + // cargo-packager deb/AppImage: /../lib/oak-editor/icons. + candidates.push(contents.join("lib/oak-editor/icons")); + candidates.push(contents.join("share/oak/icons")); + } + } + } + candidates.push(PathBuf::from("assets/icons")); + candidates + .into_iter() + .find(|dir| dir.join("dark").is_dir()) + .unwrap_or_else(dev) + }) } /// Registers the app's icon resolver, so widget-crate consumers (the viewer diff --git a/tooling/package/PKGBUILD b/tooling/package/PKGBUILD index 919f73376..8b55de507 100644 --- a/tooling/package/PKGBUILD +++ b/tooling/package/PKGBUILD @@ -40,4 +40,12 @@ package() { for f in "$OAK_ROOT"/assets/i18n/*.yaml; do install -Dm644 "$f" "$pkgdir/usr/share/oak/i18n/$(basename "$f")" done + # The theme-aware UI glyphs (resolved at runtime from + # /usr/share/oak/icons; see crates/oak-app/src/oakui/icons.rs). + for d in "$OAK_ROOT"/assets/icons/*/; do + fam=$(basename "$d") + for f in "$d"*.png; do + install -Dm644 "$f" "$pkgdir/usr/share/oak/icons/$fam/$(basename "$f")" + done + done } diff --git a/tooling/package/build-deb.sh b/tooling/package/build-deb.sh index cd0012c55..6129d41e9 100755 --- a/tooling/package/build-deb.sh +++ b/tooling/package/build-deb.sh @@ -39,6 +39,7 @@ STAGING=target/pkg/deb rm -rf "$STAGING" mkdir -p "$STAGING/usr/bin" "$STAGING/usr/share/applications" \ "$STAGING/usr/share/icons/hicolor/512x512/apps" "$STAGING/usr/share/oak/i18n" \ + "$STAGING/usr/share/oak/icons" \ "$STAGING/usr/share/icons/hicolor/scalable/apps" "$STAGING/DEBIAN" install -m755 target/release/oak-editor target/release/oak-cli target/release/oak-worker \ @@ -53,6 +54,9 @@ else fi install -m644 Oak_Icon.svg "$STAGING/usr/share/icons/hicolor/scalable/apps/oak.svg" install -m644 assets/i18n/*.yaml "$STAGING/usr/share/oak/i18n/" +# The theme-aware UI glyphs (resolved at runtime from +# /usr/share/oak/icons; see crates/oak-app/src/oakui/icons.rs). +cp -a assets/icons/. "$STAGING/usr/share/oak/icons/" # vcpkg's libva/libva-drm are shared libraries that FFmpeg links # dynamically. Debian 12 / openKylin carry an older libva than FFmpeg 8 diff --git a/tooling/package/build-rpm.sh b/tooling/package/build-rpm.sh index 7763fb59c..f533fe132 100755 --- a/tooling/package/build-rpm.sh +++ b/tooling/package/build-rpm.sh @@ -33,6 +33,7 @@ mkdir -p "$TOP"/{BUILD,RPMS,SOURCES,SPECS,BUILDROOT} STAGE="$TOP/stage" mkdir -p "$STAGE/usr/bin" "$STAGE/usr/share/applications" \ "$STAGE/usr/share/icons/hicolor/512x512/apps" "$STAGE/usr/share/oak/i18n" \ + "$STAGE/usr/share/oak/icons" \ "$STAGE/usr/share/icons/hicolor/scalable/apps" install -m755 target/release/oak-editor target/release/oak-cli target/release/oak-worker \ "$STAGE/usr/bin/" @@ -40,6 +41,9 @@ install -m644 packaging/oak.desktop "$STAGE/usr/share/applications/oak.desktop" install -m644 icons/icon.png "$STAGE/usr/share/icons/hicolor/512x512/apps/oak.png" install -m644 Oak_Icon.svg "$STAGE/usr/share/icons/hicolor/scalable/apps/oak.svg" install -m644 assets/i18n/*.yaml "$STAGE/usr/share/oak/i18n/" +# The theme-aware UI glyphs (resolved at runtime from +# /usr/share/oak/icons; see crates/oak-app/src/oakui/icons.rs). +cp -a assets/icons/. "$STAGE/usr/share/oak/icons/" rpmbuild -bb \ --define "_topdir $TOP" \