7.1 KiB
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 (
Okey or button) - Display current image, fit-to-window
- Scroll wheel = prev/next photo (ristretto-style, natural scrolling)
- Arrow keys + hjkl navigate
Qto 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
# 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::Darkwas closer to the ristretto+ target aesthetic without manual overrides. - The declarative
Messageenum +Task::performasync 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+JoinHandlepolling 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
windowsubsystem rather than egui's trivialViewportBuilder. 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).ViewportBuilderone-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
Messageenum makes the state machine explicit and easy to reason about. - Built-in
Task::performfor async file loading is clean and integrated with the runtime. Theme::Darkis one-liner; refinement viacontainer::Styleclosures is straightforward.image::Handle::from_byteslets us defer decoding to the runtime — no manual texture upload.Subscription+iced::event::listen()is a clean way to capture global input.
- Declarative
- 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
imagewidget 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
Handlemanagement.
- 0.13 is a recent release; some docs still show 0.12 patterns. We already hit one breaking change (
egui 0.29 — code-side notes
- Pros
- Immediate mode makes the layout trivial —
TopBottomPanel+CentralPanelis 5 lines. ColorImage+TextureHandleis a textbook path; lazy thumbnail loading is natural (just stash handles in a Vec).Context::request_repaint_aftergives us precise repaint control (good for animated GIFs later).- Built-in
Response::context_menu()gives us right-click menus for free. ViewportBuildermakes fullscreen toggling trivial.
- Immediate mode makes the layout trivial —
- Cons
- No async story — we spawn
std::threadand pollJoinHandle::is_finished()ourselves. This will get messy for a real thumbnail bar. egui::Color32is 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.
- No async story — we spawn
Action after decision
Once we pick:
- Move winner to
image-viewer/src/(single binary crate as agreed). - Archive loser under
image-viewer/prototypes/<loser>-archived/with aREADME.mdsaying why. - Add a
DECISION.mdat repo root summarizing this doc. - Begin MVP build: modular
src/{image,nav,ui,config}/structure.