Give the app an icon, and a script that installs it
The launcher pointed at `Icon=story-editor`, a name that only the Breeze theme happened to ship, and only at 16 and 22 px -- so anywhere the desktop wanted a larger icon it fell back to something generic. assets/md-manuscript.svg is the source: a sheaf of manuscript pages, an amber chapter-title line over three of prose. Drawn in plain rounded rectangles because a finer design silts up into a grey smear at 16 px. It is rasterised to the eight sizes a desktop asks for, and the 256px PNG is compiled into the binary as the window icon, so the title bar and the task switcher carry it whether or not anything is installed. That costs no new dependency: eframe already pulls `image` with png decoding. A test decodes the bundled PNG, since a rename would otherwise show up only as a blank icon at runtime. Installation is now `./assets/install.sh` -- binary, icons, launcher and both cache refreshes, safe to re-run after a rebuild. The launcher is a file in the repository rather than a here-document in the instructions: pasting one into a terminal leaves the shell sat at a `>` prompt waiting for a terminator that never arrives, which is exactly what happened, and this way the launcher is versioned along with everything else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSGPDwkSzhm4qLqjCbqxU
This commit is contained in:
+40
-4
@@ -18,12 +18,33 @@ mod spell;
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
/// The window icon, for the title bar, the task switcher and the dock.
|
||||
///
|
||||
/// A desktop usually finds an app's icon by matching the window class against a
|
||||
/// `.desktop` file, but that only works once one is installed; setting it here
|
||||
/// means the window carries its own icon whatever it was launched from.
|
||||
///
|
||||
/// The PNG is generated from `assets/md-manuscript.svg` — see the icon section
|
||||
/// of INSTALL-debian12-surface.md for the command that regenerates the set.
|
||||
fn window_icon() -> Option<egui::IconData> {
|
||||
const PNG: &[u8] = include_bytes!("../assets/icons/hicolor/256x256/apps/md-manuscript.png");
|
||||
// A broken icon is not worth refusing to start over.
|
||||
eframe::icon_data::from_png_bytes(PNG).ok()
|
||||
}
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
let mut viewport = egui::ViewportBuilder::default()
|
||||
.with_inner_size([1100.0, 720.0])
|
||||
.with_min_inner_size([700.0, 400.0])
|
||||
.with_title("md-manuscript")
|
||||
// Matched against the .desktop file's name by the desktop shell, which
|
||||
// is what lets a launcher pin and group the window.
|
||||
.with_app_id("md-manuscript");
|
||||
if let Some(icon) = window_icon() {
|
||||
viewport = viewport.with_icon(icon);
|
||||
}
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([1100.0, 720.0])
|
||||
.with_min_inner_size([700.0, 400.0])
|
||||
.with_title("md-manuscript"),
|
||||
viewport,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -33,3 +54,18 @@ fn main() -> eframe::Result<()> {
|
||||
Box::new(|cc| Ok(Box::new(app::App::new(cc)))),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/// The window icon is an asset loaded by path, so a rename or a bad export
|
||||
/// would only show up as a missing icon at runtime. Decode it in a test.
|
||||
#[test]
|
||||
fn the_window_icon_decodes() {
|
||||
let icon = super::window_icon().expect("the bundled icon must decode");
|
||||
assert_eq!((icon.width, icon.height), (256, 256));
|
||||
assert_eq!(icon.rgba.len(), 256 * 256 * 4);
|
||||
// Not a blank square: the tile has both ink and paper in it.
|
||||
let opaque = icon.rgba.chunks(4).filter(|p| p[3] > 0).count();
|
||||
assert!(opaque > 256 * 256 / 2, "icon looks empty");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user