# 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/<loser>-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.
