Skip to content

UI polish: themed labels, colormap swatches, drop feedback, Open Recent, draggable clim - #37

Merged
LiukDiihMieu merged 5 commits into
mainfrom
feature/ui-polish-round2
Aug 9, 2026
Merged

UI polish: themed labels, colormap swatches, drop feedback, Open Recent, draggable clim#37
LiukDiihMieu merged 5 commits into
mainfrom
feature/ui-polish-round2

Conversation

@LiukDiihMieu

Copy link
Copy Markdown
Owner

A round of visual and interaction polish for the viewer. Five independent, low-risk changes, each in its own commit with tests; all 354 tests pass. Verified in the real GUI on the pip build (Snap-specific paths noted below, to be checked once merged).

Theme warning-label colors, tracking runtime switches

The anomaly/error (red) and sampled/downsampled/truncated (#b8860b) labels were hardcoded inline in five files and read poorly on dark themes. New ErrorLabel/CautionLabel (in views/base.py) carry per-scheme colors and re-resolve them on PaletteChange, so they follow an OS light/dark switch live — unlike the matplotlib canvas, whose style is fixed at startup, a label restyle is free. "Dark" uses the same colorScheme() criterion as the canvas theme, by convention.

Colormap gradient swatches in the View menu

Each of the 11 colormap entries now shows a small square dark-to-light swatch, rendered from the matplotlib map. A native QAction icon (not a QWidgetAction bar) keeps the exclusive-group check marks and native macOS menus; the swatches flip when Reverse is toggled.

Drop-target overlay

Dragging a .npy/.npz over the window now shows a translucent "Drop to open" overlay with a dashed border, hidden on leave/drop. It is mouse/drop-transparent so it can't intercept the drag it announces, and paints from the Highlight palette role so it follows the theme. Invalid drags show nothing.

File › Open Recent

Keeps the last 8 successfully opened files in QSettings, shown in a submenu rebuilt on open. Entries use ~-abbreviated full paths with the absolute path as tooltip, plus Clear Recent. Missing files are greyed rather than pruned, so entries on a temporarily unmounted drive return on their own. Two Snap guards: XDG document-portal paths (/run/user/<uid>/doc/…) are never recorded, and greyed entries under confinement explain that File › Open reaches files the sandbox cannot.

Draggable vmin/vmax markers on the histogram

The histogram already drew the image's clim as dashed markers; now they can be dragged to adjust image brightness directly, with the Image tab's vmin/vmax fields syncing live. Hit testing and cursor feedback mirror the cross-section endpoints; vmin/vmax stay strictly ordered. The drag drives a new histogram→image callback via a silent sink (the same no-echo convention as the linked-view code), so it never chains with the existing image→histogram sync. Complex/RGB modes have no markers and are unaffected.

Not included

An empty-state page icon was prototyped and shelved: the packaged icon has a transparent background and needs the light/dark asset pair to look right — deferred with the dark-icon backlog item.

🤖 Generated with Claude Code

LiukDiihMieu and others added 5 commits July 4, 2026 22:56
The anomaly/error (red) and sampled/downsampled/truncated (#b8860b)
label colors were hardcoded inline in five files and did not adapt to
dark themes, where darkgoldenrod turns muddy and pure red glares.

Add ErrorLabel/CautionLabel (in views/base.py) carrying per-scheme
colors. QPalette has no error/caution roles to ride the way the empty-
state text uses PlaceholderText, so they re-resolve their color from
changeEvent(PaletteChange) — an OS theme switch updates the palette,
so the event doubles as the scheme-change hook. Unlike the matplotlib
canvas (whose style is fixed at startup), a label restyle is free, so
these follow theme switches live. "Dark" uses the same colorScheme()
criterion as the canvas theme, by convention; Unknown counts as light,
leaving the light-theme colors unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpLDoWW6e5sdXFd7B572Ck
The 11 colormap entries were text-only, so picking one meant knowing
its gradient by name. Render each map as a small square dark-to-light
swatch (matplotlib RGBA row -> QImage -> QIcon) at the style's menu
icon size, device-pixel-ratio aware.

A square native QAction icon is used instead of a wide QWidgetAction
gradient bar: it keeps the exclusive-group check marks and native
macOS menu rendering. The swatches re-render when Reverse is toggled,
reusing _effective_colormap as the single source of the _r suffix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpLDoWW6e5sdXFd7B572Ck
Dragging a .npy/.npz over the window gave no visual feedback until the
drop landed. Add a DropOverlay child of the central container: shown on
dragEnter of a valid drag, hidden on dragLeave/drop.

The overlay is mouse/drop-transparent (WA_TransparentForMouseEvents) so
it can never intercept the drag it announces, tracks the container size
via an event filter, and paints entirely from the Highlight palette
role so it follows light/dark themes with no color constants. Invalid
drags are not accepted and show no overlay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpLDoWW6e5sdXFd7B572Ck
Reopening yesterday's data file meant walking the file dialog every
time. Keep the last 8 successfully opened paths in QSettings and show
them in a File > Open Recent submenu, rebuilt on aboutToShow like the
dynamic Export menu. Entries show ~-abbreviated full paths (same-named
files in different dirs stay distinguishable) with the absolute path
as tooltip, plus a Clear Recent action.

Missing files are greyed rather than pruned, so entries on a
temporarily unmounted drive come back on their own. Two Snap guards:
XDG document-portal paths (/run/user/<uid>/doc/...) are never
recorded, since their grants may not survive a restart; and greyed
entries under confinement explain that File > Open reaches files the
sandbox cannot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpLDoWW6e5sdXFd7B572Ck
The histogram already draws the image's vmin/vmax as dashed markers,
but adjusting them meant going back to the Image tab and typing
numbers. Make the two markers draggable: pixel-radius hit testing
mirrors the cross-section endpoints in ImageCanvas, the artists move
in place during the drag, and vmin/vmax are clamped to stay strictly
ordered so imshow's normalization remains valid.

The drag drives a new histogram -> app -> image callback, the reverse
of the existing image -> histogram marker sync. The image side applies
it through a silent sink (same no-echo convention as set_view /
set_endpoints) that also refills the vmin/vmax edits, so the two
directions never chain. Complex/RGB modes are unaffected: they never
have markers, and the sink guards them defensively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpLDoWW6e5sdXFd7B572Ck
@LiukDiihMieu
LiukDiihMieu force-pushed the feature/ui-polish-round2 branch from 9a5691d to 11c4c87 Compare July 4, 2026 14:01
@LiukDiihMieu
LiukDiihMieu merged commit 6be16fd into main Aug 9, 2026
9 of 10 checks passed
@LiukDiihMieu
LiukDiihMieu deleted the feature/ui-polish-round2 branch August 9, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant