# Spike Comparison: iced vs egui Both prototypes implement the **same MVP slice** so we can compare apples-to-apples: - Open a folder of images via dialog (`O` key or button) - Display current image, fit-to-window - Scroll wheel = prev/next photo (ristretto-style, natural scrolling) - Arrow keys + hjkl navigate - `Q` to quit - Dark theme **Neither** spike implements: thumbnail bar, zoom/pan, right-click menu, fullscreen, EXIF panel. Those come after we pick a winner. --- ## How to run ```bash # iced spike cd /home/z/my-project/image-viewer/prototypes/iced-viewer cargo run --release # egui spike cd /home/z/my-project/image-viewer/prototypes/egui-viewer cargo run --release ``` In each: press `O`, pick a folder with some images, then scroll wheel / arrow keys to navigate. --- ## Comparison criteria Score each on a 1–5 scale (5 = best). Fill in after testing. ### 1. Visual polish (the ristretto+ bar) - Does the dark theme look refined out of the box, or does it look like a "default toolkit demo"? - Is the toolbar thin and unobtrusive? - Does the image area background blend cleanly with the chrome? - Score: iced __ / 5 · egui __ / 5 ### 2. Scroll-wheel feel - Is the scroll responsive, or is there noticeable lag? - Does fast scrolling skip photos or queue them up? - Does the cursor need to be over the image, or does any scroll anywhere navigate? - Score: iced __ / 5 · egui __ / 5 ### 3. Image rendering quality - Does the image render crisp at fit-to-window scale? - Are colors correct (no premultiplied-alpha artifacts on transparent PNGs)? - Does animated GIF play (it shouldn't in spike, but note if it does)? - Score: iced __ / 5 · egui __ / 5 ### 4. Code ergonomics - How easy was it to express the ristretto layout? (Lines of code, mental overhead) - How clean is the event loop? (declarative Msg vs immediate-mode input polling) - Async image loading: which approach felt more natural? - Score: iced __ / 5 · egui __ / 5 ### 5. Thumbnail bar viability (look ahead) - Can we plausibly build a horizontal scrollable thumbnail strip with custom styling? - Will it be easy to make thumbnails load lazily and replace placeholder textures? - Score: iced __ / 5 · egui __ / 5 ### 6. Right-click menu viability (look ahead) - How easy is a custom context menu with icons, separators, submenus? - Score: iced __ / 5 · egui __ / 5 ### 7. Fullscreen + auto-hide controls (look ahead) - Can we cleanly toggle chrome visibility at runtime? - Score: iced __ / 5 · egui __ / 5 ### 8. Build time / binary size - iced release binary: ~23 MB - egui release binary: __ MB (fill in) - Cold build time (`cargo clean && time cargo build --release`): - iced: __ s - egui: __ s --- ## Decision matrix | Criterion | Weight | iced | egui | |---|---|---|---| | Visual polish | 3 | | | | Scroll feel | 3 | | | | Image quality | 2 | | | | Code ergonomics | 2 | | | | Thumb bar viability | 3 | | | | Right-click menu | 2 | | | | Fullscreen toggle | 1 | | | | Build/binary | 1 | | | **Weighted total**: iced __ · egui __ --- ## Verdict > Filled in after runtime testing on Arch Linux (2026-08-02). **Winner**: **iced** **Why**: - iced ran smoothly out of the box; egui loaded but exhibited visible runtime issues (rendering/input quirks) that would have cost debugging time before any real feature work could begin. - iced's `Theme::Dark` was closer to the ristretto+ target aesthetic without manual overrides. - The declarative `Message` enum + `Task::perform` async story felt like a better fit for an app with this much interaction surface (scroll, zoom, pan, context menu, fullscreen, thumbnail clicks, configurable keymap). - egui's lack of a built-in async story was already forcing us into `std::thread` + `JoinHandle` polling for the file dialog — that pattern does not scale to a lazy-loaded thumbnail bar. **Trade-offs we accept**: - Larger binary (23 MB vs 15.5 MB). Acceptable for a desktop app; we'll trim later with `strip` + `lto = "fat"` if size becomes an issue. - No built-in context-menu widget — we'll roll our own with an overlay layer. This is actually a feature: we wanted full styling control for the gpicview-inspired menu anyway. - Fullscreen toggling will go through iced's `window` subsystem rather than egui's trivial `ViewportBuilder`. Slightly more code, same end result. **What we lose by not picking egui**: - `Response::context_menu()` for free right-click menus (we'll write our own — see above). - `ViewportBuilder` one-liner for fullscreen (we'll use iced's window commands). - Immediate-mode layout simplicity (declarative state is a net win for this app's complexity). - ~7 MB of binary size. The archived egui spike lives at `prototypes/egui-viewer-archived/` with a `README.md` explaining the call. We keep it as a reference and as a restart path if iced turns out to be the wrong choice after MVP. --- ## Notes from initial code-side impressions (pre-runtime) These are observations from writing both spikes, before any runtime testing. ### iced 0.13 — code-side notes - **Pros** - Declarative `Message` enum makes the state machine explicit and easy to reason about. - Built-in `Task::perform` for async file loading is clean and integrated with the runtime. - `Theme::Dark` is one-liner; refinement via `container::Style` closures is straightforward. - `image::Handle::from_bytes` lets us defer decoding to the runtime — no manual texture upload. - `Subscription` + `iced::event::listen()` is a clean way to capture global input. - **Cons** - 0.13 is a recent release; some docs still show 0.12 patterns. We already hit one breaking change (`center_x`/`align_x`). - The `image` widget doesn't expose a "fit mode + actual displayed size" API — we'd need to compute scaling ourselves for zoom/pan. - No built-in context-menu widget; we'll roll our own with overlays. - Custom thumbnail bar with lazy texture loading will require careful `Handle` management. ### egui 0.29 — code-side notes - **Pros** - Immediate mode makes the layout trivial — `TopBottomPanel` + `CentralPanel` is 5 lines. - `ColorImage` + `TextureHandle` is a textbook path; lazy thumbnail loading is natural (just stash handles in a Vec). - `Context::request_repaint_after` gives us precise repaint control (good for animated GIFs later). - Built-in `Response::context_menu()` gives us right-click menus for free. - `ViewportBuilder` makes fullscreen toggling trivial. - **Cons** - No async story — we spawn `std::thread` and poll `JoinHandle::is_finished()` ourselves. This will get messy for a real thumbnail bar. - `egui::Color32` is RGBA8 — HDR/16-bit workflows will need tone-mapping in our code. - Default dark theme is bluish; we had to override the panel fill manually to get the ristretto-feel dark. - Immediate mode means we re-emit the entire UI every frame; for a static viewer this is mostly fine but burns CPU on idle. --- ## Action after decision Once we pick: 1. Move winner to `image-viewer/src/` (single binary crate as agreed). 2. Archive loser under `image-viewer/prototypes/-archived/` with a `README.md` saying why. 3. Add a `DECISION.md` at repo root summarizing this doc. 4. Begin MVP build: modular `src/{image,nav,ui,config}/` structure.