summaryrefslogtreecommitdiff
path: root/docs/ui-and-interactions.md
blob: 28718e198ea8f1240fd57169dfa07b9e080fca23 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# UI & Interactions

## Design language (gthumb, but KISS)

The look and layout are reminiscent of **gthumb**: a libadwaita header bar with
prev/next + zoom + view-toggle, a thumbnail grid like gthumb's browser grid,
and a full-window dark viewer like gthumb's viewer mode — familiar to GNOME
image-app users.

The **KISS** part — things gthumb has that ggaze deliberately drops:
- No folder/sidebar tree (single folder; you point ggaze at a path).
- No catalogs, albums, tags, or search bar.
- No status bar clutter — the header subtitle carries `n / total` + filename.
- No batch-tools or editing toolbars; quick enhance is a popup, not a pane.
- No location/path entry; the current folder is shown, not editable.

Optional, off by default: a slim one-line status footer (filename · zoom ·
size) for those who want it — toggled in settings, hidden otherwise.

## Window layout

- `GtkApplicationWindow` holding a `GtkStack` with two views:
  - **Grid view** — `GgazeGrid`: a flow/grid of thumbnails for the whole folder.
  - **Large view** — `GgazeViewer`: the single-image canvas, fills the window.
- An overlay on top (auto-hide) carries the header bar — title = filename,
  subtitle = `n / total` (remaining) · `N marked` when marks exist.
- Header bar actions (icons): prev, next, zoom-fit, zoom-100, toggle-grid,
  open-file, slideshow, menu (sort, background, empty-`./Trash`, about).
- No sidebar, no tree, no toolbar beyond the header bar.
- Transparent background behind the image (configurable: black / dark / grey
  / checkered for transparency).

## Opening files & folders

- **CLI arg**: `ggaze IMG_0001.jpg` opens that file (large view, its folder as
  the navigator); `ggaze ~/Downloads/Camera/` opens the folder directly in the
  grid.
- **Drag-and-drop**: drop a file or a folder onto the window to open it — a
  folder opens the grid; a file opens it (large view, parent folder as
  navigator); multiple files open the first file's folder in the grid with the
  first selected. A drop highlight shows the window is a drop target.
- **`o`** opens a file dialog that can pick a file or a folder.

## Views & modes

- **Grid view** — default on launch (when opening a file, jump straight to
  large view on that file; `Esc` returns to grid). Thumbnails from the cache,
  selection follows the navigator cursor.
- **Large view** — one image, header bar visible (auto-hide after inactivity).
- **Fullscreen** — `f` toggles (from large view); header hidden, image centered
  on a dark backdrop. `Esc` exits fullscreen back to large; `q` quits.
- **Slideshow** — auto-advance every N seconds (GSettings), fullscreen-only by
  default; any manual key pauses/resumes.

View switching never loses the cursor: grid→large opens on the selected cell;
large→grid scrolls that cell into view and focuses it. **Marks persist across
views** (a check badge in grid, an indicator in large), so you can mark in
detail view and move from either.

- **Move popup** — transient, opened by `m`. Lists configured destinations,
  each with an auto-assigned hotkey (`1`-`9`, `0`, then `a`-`z` in order). Type
  the hotkey to move the marked set (or the current image if none marked);
  `Esc` cancels. Order in settings = hotkey order.

## Keybindings (default, all reassignable later via GSettings)

**Every GUI element has a corresponding hotkey, and it is shown on the
element itself** — menu items print their key (e.g. `Move …   m`),
header-bar buttons show it in their tooltip, the move popup prints the digit
beside each destination. Conversely, **every action that has a hotkey is also
reachable through the GUI** (a header-bar button or an entry in the `F10`
menu). Keyboard and mouse are equally first-class; neither is a fallback.
Navigation is **vi-style plus cursor keys**: `h`/`l` and `←`/`→` move
prev/next through the shoot; `j`/`k` and `↑`/`↓` pan when zoomed.

| Key            | Action |
|----------------|--------|
| `h` / `Left`   | previous image |
| `l` / `Right`  | next image |
| `j` / `Down`   | pan down (when zoomed) |
| `k` / `Up`     | pan up (when zoomed) |
| `H` / `L`      | pan left / right (when zoomed) |
| `v`            | toggle mark on current image |
| `V`            | range-mark from last mark to current |
| `Ctrl+a`       | mark all images |
| `Ctrl+c`       | copy image (or marked files) to clipboard |
| `g`            | first image |
| `G`            | last image |
| `Enter`        | grid → large (open selected) |
| `Esc` / `Back` | context back: fullscreen → large → grid → quit |
| `t`            | toggle grid ↔ large |
| `+` / `=`      | zoom in (large) / grow thumbnails (grid) |
| `-` / `_`      | zoom out (large) / shrink thumbnails (grid) |
| `0`            | zoom 100% / fit toggle (large) / reset thumbnail size (grid) |
| `Space`        | hold to compare original vs modified (large) |
| `f`            | toggle fullscreen |
| `s`            | save enhanced copy (GEGL); no auto-save    |
| `S`            | toggle slideshow                          |
| `i`            | toggle info overlay |
| `r`            | reload (re-read file from disk) |
| `d`            | move to `./Trash` (confirm via toast), then next; undoable |
| `D`            | delete permanently (no trash), then next; no undo |
| `m`            | move marks (or current) → destination popup |
| `e`            | open current image in an external program → popup |
| `!`            | run a shell script → popup (e.g. `usbimport`) |
| `a`            | quick enhance → preset popup (GEGL; non-destructive) |
| `c`            | crop tool (GEGL; non-destructive, large view) |
| `R`            | straighten tool (GEGL; non-destructive, large view) |
| `[` / `]`      | rotate 90° CCW / CW (GEGL; non-destructive, one-shot) |
| `u`            | undo last `d` / `m` (restore from `./Trash` or move back) |
| `o`            | open file dialog |
| `,`            | preferences (destinations, sort, background, …) |
| `F10`           | open app menu (sort, background, empty `./Trash`, about) |
| `?`            | shortcuts overlay |
| `q`            | quit (exits fullscreen first) |

`Esc` is *contextual back*: if there are marks, it clears them first; then in
fullscreen it returns to large view, in large view it returns to the grid, in
the grid it quits. `q` always quits outright (exiting fullscreen first).

## Mouse / touch

- **Scroll** — `zoom` (default), `pan-when-zoomed`, or `navigate` next/prev
  — `scroll-behavior` setting.
- **Click-drag** — pan when zoomed in.
- **Double-click** — toggle fit ↔ 100%.
- **Middle-click** — toggle mark on a grid cell (grid view) / toggle
  fullscreen (large view).
- **Touch pinch** — zoom; **swipe** — next/prev; **two-finger tap** — info.

## Zoom behavior

- Fit-to-window is the default on load.
- `0` toggles fit ↔ 100% (double-click also toggles).
- Zoom centers on cursor (mouse) / pinch midpoint (touch) / window center (keys).
- Panning clamps so the image can't drift off-screen.

## Info overlay (`i`)

Small card, top-left or bottom-right:
- filename, dimensions, format, file size
- EXIF: camera, lens, focal length, aperture, shutter, ISO, date taken,
  orientation (auto-applied on load)
- shot number within the current burst group (once burst grouping lands)
- color space (once color management lands)
Loaded lazily; never blocks display of the pixels.

## Grid view behavior

- Thumbnails load from the `thumbnail` cache (M7), decoding lazily as cells
  scroll into view; never block the grid on a full decode.
- Trashed (`./Trash`) and permanently-deleted items stay listed but **dimmed**
  with a small badge, so you can see culling progress at a glance. (Toggle to
  hide them entirely via a menu option / setting.)
- `h`/`l`/`j`/`k`, arrow keys, `g`/`G`, click, and type-to-search (jump by
  filename prefix) all move the cursor: `h`/`l` prev/next cell, `j`/`k` row
  down/up. `Enter` opens large; `d`/`D` work here too.
- Re-sorting (name / capture time / size) reflows the grid and keeps the
  current image visible.
- **Resizable thumbnails**: `+`/`-` grow/shrink the thumbnails, and the grid
  auto-reflows (GtkFlowBox/GtkGridView) to fit the window — more columns when
  small, fewer when large. Size persists in GSettings (`thumbnail-size`) and
  is restored on next launch; `0` resets to default.
- Marks: `v` toggles a check badge on the current cell; `V` range-marks;
  `Ctrl+a` marks all; middle-click a cell toggles its mark. `d`/`D`/`m` act on the
  marked set (or current if none).

## Selection & moving

- **Marks** are a lightweight multi-select, shared by grid and large views.
  Toggle with `v`, range with `V`, all with `Ctrl+a`, clear with `Esc`.
- **`m` → move popup**: a small popover listing the configured destinations.
  Each entry shows an auto-assigned hotkey in order — `1`, `2`, `3` … (then `0`,
  then `a`-`z`). Example:
  ```
  Move 3 images to:
   1  irregular ninja
   2  alt irregular ninja
   3  something else
  ```
  Press `2` to move to "alt irregular ninja"; `Esc` cancels. Destinations
  are configured in Preferences (`,`) as an ordered list of name → path pairs.
- **Move semantics**: `g_file_move` (rename on same filesystem, else copy +
  delete). On name collision in the destination, suffix `-1`, `-2`, …. After
  a move, files leave the current folder: the navigator drops them, the grid
  removes their cells, and the counter updates.
- **Undo**: `u` undoes the last `d` (restore from `./Trash`) **or** the last
  `m` (move the set back to their original paths). One level of undo to start.

## Copy to clipboard

- **`Ctrl+c`** (or menu *Copy*) puts the current picture on the clipboard so
  you can paste it into other apps (Katogram, GIMP, chat clients) — like gthumb.
- **No marks** → copies the **displayed** image **pixels** as PNG (modified
  if a preview is active, else original) via `GdkClipboard` +
  `GdkContentProvider` for `image/png`; pastes as an image. Decoding runs in a
  `GTask` thread so the UI doesn't block.
- **Marks present** → copies the marked **files** as `text/uri-list` (plus a
  `text/plain` path list), so file-aware apps and file managers can paste them.
- A toast confirms ("Copied image" / "Copied 3 files"). `Ctrl+Shift+c` (later)
  copies the **original** (un-modified) image or the path.

## Opening in an external program

- **`e` → external-program popup**: same popover pattern as `m`. Lists the
  configured programs, each with an auto-assigned hotkey (`1`, `2`, … then `0`,
  `a`-`z`). Example:
  ```
  Open IMG_0001.jpg in:
   1  GIMP
   2  ImageMagick identify
   3  Nomacs
  ```
  Press `2` to launch that program with the current image's path; `Esc`
  cancels.
- Acts on the **current image** (the **original file on disk**, not the
  preview); a later option may pass the marked set (`%F`). To open the modified
  version, export it first (`s`).
- Programs are configured in Preferences (`,`) as an ordered list of
  name → command pairs. The command uses freedesktop `Exec` placeholders:
  `%f` = the single current file path (e.g. `gimp %f`, `identify %f`).
- Launch is **detached and non-blocking** (GSubprocess); ggaze stays
  responsive and the image stays open. Failures show a toast.

## Running shell scripts

- **`!` → scripts popup**: same popover pattern as `m`/`e`. Lists the
  configured shell scripts, each with an auto-assigned hotkey (`1`, `2`, …
  then `0`, `a`-`z`). Example:
  ```
  Run script:
   1  usbimport (import from camera)
   2  build contact sheet
  ```
  Press `1` to run that script; `Esc` cancels.
- Scripts run **asynchronously** via a shell (`/bin/sh -c`), so pipes,
  redirection, and `~/` expansion all work. ggaze never blocks on them; a
  toast shows "running usbimport…" while it runs.
- Placeholders: `%f` = current image path, `%d` = current folder (e.g.
  `~/scripts/usbimport %d`, or just `~/scripts/usbimport` with no args).
  Substituted paths are single-quoted to avoid shell injection from filenames.
- On completion, ggaze **rescans the directory** (scripts like `usbimport`
  add files); a toast reports success/failure and the exit status.
- Scripts are configured in Preferences (`,`) as an ordered list of
  name → command pairs (`a(ss)`), separate from `editors`.

## Quick enhance (GEGL, optional)

- **`a` → enhance popup**: same popover pattern as `m`/`e`/`!`. Lists
  configurable presets, each with an auto-assigned hotkey (`1`, `2`, … then
  `0`, `a`-`z`). Example:
  ```
  Enhance IMG_0001.jpg:
   1  Auto-fix
   2  Brightness
   3  Contrast
   4  Saturation
   5  Sharpen
   6  Denoise
  ```
  Press `1` to apply "Auto-fix" as a **non-destructive live preview**; press
  it again (or `Esc`) to turn the preview off.
- Presets are GEGL op graphs (e.g. Auto-fix = `gegl:stretch-contrast` →
  `gegl:color-enhance`; Brightness = `gegl:exposure`; Contrast =
  `gegl:brightness-contrast`; Saturation = `gegl:saturation`; Sharpen =
  `gegl:sharpen`). Configurable in Preferences (`,`) as `enhance-presets`
  (`a(ss)` name → gegl-graph); the strength is just a number in the graph
  text — tune it there, no slider UI needed.
- **`s`** (or menu *Save enhanced copy…*) writes the enhanced result to
  `<name>-enhanced.<ext>` via a GEGL saver — the **original is never
  touched**. ggaze **never auto-saves**: an enhance preview is a live overlay
  only.
- **Dirty state + prompt on navigate:** an active (un-exported) enhance
  preview is "dirty". Navigating to another image (`h`/`l`/`g`/`G`/click) or
  quitting with a dirty preview prompts **Save** (export the copy, then
  proceed), **Discard** (drop the preview, proceed), or **Cancel** (stay).
  `s` saves and clears dirty; toggling the preview off (`Esc`/re-press)
  discards it directly (explicit, no prompt).
- GEGL runs only when a preset is active or on export; the fast decode path
  is unchanged, and enhance is **not** applied during `h`/`l` scrubbing (only
  when settled on an image). If the build has no GEGL, `a` shows a "GEGL not
  built in" toast. See [gegl.md](gegl.md).

## Crop, straighten & rotate tools (GEGL)

Non-destructive, like enhance — they add ops to the live preview graph; `s`
exports the result and navigating away prompts Save/Discard/Cancel. Large view
only; in grid, `c`/`R` first switch to large on the selected cell. If GEGL is
not built in, all show the "GEGL not built in" toast.

- **`c` → crop tool:** overlay an adjustable crop rectangle on the image.
  - Mouse: drag inside to move, drag edges/corners to resize.
  - Keyboard: `h`/`l`/`j`/`k` move the rectangle; `H`/`L`/`J`/`K` resize the
    edges; `1`-`4` set aspect ratio (1:1, 3:2, 4:3, 16:9), `0` free.
  - `Enter` applies (`gegl:crop`), `Esc` cancels.
- **`R` → straighten tool:** level the horizon.
  - Mouse: drag a line along the horizon; the image rotates to align it.
  - Keyboard: `h`/`l` (or `+`/`-`) nudge the angle by ±0.5°; a grid overlay
    helps. Optional auto-crop to remove the rotated corners.
  - `Enter` applies (`gegl:rotate`), `Esc` cancels.
- **`[` / `]` → rotate 90°:** one-shot, no overlay — `]` clockwise, `[`
  counterclockwise; repeat to reach 180°/270°. Non-destructive
  (`gegl:rotate-on-center`); `s` exports the rotated copy.
- All compose with enhance presets in the same preview graph.

## Compare original vs modified (hold)

- **Hold `Space`** to momentarily show the **original** image; **release** to
  return to the **modified** (preview-graph) image — a quick before/after to
  decide whether to `s` save. Only meaningful when a preview (enhance / crop /
  straighten / rotate) is active; otherwise original == modified, no-op.
- Large view only. GUI: menu *Show original* (toggle) for mouse users.

## Hotkey visibility

Hotkeys are not hidden — each is printed on the element it triggers:

- **Menu items** show their key right-aligned, e.g. `Copy   Ctrl+c`,
  `Move …   m`, `Open in …   e`, `Scripts …   !`, `Enhance …   a`,
  `Crop …   c`, `Straighten …   R`, `Rotate 90°   ] / [`, `Save enhanced copy …
  s`, `Show original (hold)   Space`, `Slideshow   S`, `Trash   d`, `Delete   D`,
  `Preferences …   ,`, `Fullscreen   f`.
- **Header-bar buttons** show the key in the tooltip (plus an underline
  mnemonic where GTK draws one).
- **Popup** entries (move / open-in / scripts / enhance) lead with the
  hotkey: `1  irregular ninja`, `1  GIMP`, `1  usbimport`, `1  Auto-fix`.
- **Shortcuts overlay** (`?`) lists everything in one place.

If an element has no direct key, it lives in the `F10` menu (navigable with
arrows + `Enter` and mnemonic underlines).

## Reachability (keyboard + GUI)

Every action is reachable **two ways**:

- **By keyboard** — a direct hotkey, or via the `F10` menu (arrows + `Enter`).
- **By mouse/GUI** — a header-bar button for the common ones, or an entry in
  the `F10` app menu for the rest.

So the keyboard alone, or the mouse alone, can reach everything. Header-bar
buttons and their hotkeys:

| Element            | Hotkey        |
|--------------------|---------------|
| prev / next        | `h` / `l`     |
| zoom fit / 100%    | `Space` / `0` |
| zoom in / out      | `+` / `-`     |
| toggle grid/large  | `t`           |
| open file          | `o`           |
| slideshow          | `S`           |
| fullscreen         | `f`           |
| info overlay       | `i`           |
| app menu           | `F10`         |
| preferences        | `,`           |

App menu (via `F10`) items — each reachable by mnemonic, by arrows + `Enter`,
and by click: Copy, Move…, Open in…, Scripts…, Enhance…, Crop…, Straighten…,
Rotate 90° CW/CCW, Save enhanced copy…, Show original, Trash, Delete, Sort (by
name / capture time / size), background colour, hide-trashed toggle, empty
`./Trash`, About, Preferences….
The move popup and all dialogs (open, preferences, shortcuts overlay) are
likewise fully operable by keyboard and by mouse.

## Self-documenting

The UI explains itself; the manual is a bonus, not a requirement.

- Hotkeys are printed on their elements (see Hotkey visibility).
- Tooltips on every header-bar button and menu item: name + key + a one-line
  hint.
- `?` opens a shortcuts overlay listing every action and its key.
- The header shows `n / total` + filename (+ `N marked` when marks exist);
  the grid dim-marks trashed/deleted items and check-badges marks; the move
  popup shows each destination's hotkey and the count being moved ("Move 3
  images to:").
- The info overlay (`i`) shows EXIF, format, and size on demand.
- Empty states ("no images in this folder") and toasts ("Moved 3 → irregular
  ninja", "Undo") narrate every side-effect.

If a user can't tell what a key or button does by looking, that's a bug.

## Culling workflow notes
- `d` moves the file to `<folder>/.Trash/` (created lazily), advancing to the
  next image automatically, so `d d d` clears a run of rejects without
  re-aiming. A transient toast confirms and offers **Undo** (`u`).
- `D` permanently deletes (unlinks) — fast path for obvious garbage; **no
  undo**, so the toast warns and the grid badge marks it. Deleting **>1 marked**
  image asks for a confirm dialog first.
- Counter in the header (`n / total`) reflects *remaining* images so you can
  see the folder shrinking as you cull.
- `./Trash` lives with the shoot: easy to inspect, empty via the menu, or
  `rsync`/`rm -rf` from a shell. Never the system trash.