Tabbed control deck reorg of MainWindow: menu bar for rare actions, always-visible transport bar, 3-tab control deck (Export / Crop & Track / Scan), real status bar, plus a visual-polish pass. No behavior, shortcut, or core/ changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.2 KiB
Main Window UI Restructure — Design
Goal: Reorganize the MainWindow UI in main.py from a flat wall of ~50 always-visible controls into a legible, grouped layout — a menu bar for rare actions, a tabbed control deck for settings, an always-visible transport bar, and a real status bar — plus a visual polish pass. Keep every existing behavior, shortcut, and mouse interaction working.
Scope: Reorganization and visual polish. Not an interaction-model change — single-key shortcuts, timeline mouse overloading, and the export/scan logic are untouched.
Audience: Single power user. Optimize for density and speed. The goal is order, not hiding: keep everything fast to reach; push only genuinely rare actions into menus.
Runs in: Python/Qt client (main.py), MainWindow class only. No core/ changes.
Problem (from audit)
- No information architecture. No menu bar, no toolbar; status bar explicitly disabled (
setStatusBar(None), main.py:4440). Every function is a permanently-visible widget at equal weight. settings_rowoverloaded (main.py:4334–4370): 24 widgets in one non-wrappingQHBoxLayoutspanning three unrelated domains (encode/clip params, export variants, audio-scan ML). Needs >1500px; window opens at 1100px.- Stranded controls — e.g. the workers spinbox sits between Cancel and Delete in the transport row (main.py:4316).
- Weak feedback — only an 11px
#888status label at the far-right end of the overflowing settings row (main.py:4364). - Flat visual hierarchy — single Fusion stylesheet, scattered inline
setStyleSheetstate swaps, no primary/secondary distinction, no grouping.
Chosen approach: Tabbed control deck
The 3-pane horizontal splitter (Queue · Center · Scan results) is unchanged. The center column is restructured:
╔═ File Edit Scan View Help ═══════════════════ Profile:[default▾] [?] ╗ menu bar (+ corner widgets)
║ ┌Queue──┐ │ current_file.mp4 │ ┌ Scan results ─────┐ ║
║ │+Open │ │ ┌──────────────────────────────────────┐ │ │ [model tabs] │ ║
║ │filter │ │ │ VIDEO (mpv) │ │ │ version▾ │ ║
║ │┌List┬+┐│ │ │ │ │ │ start end score │ ║
║ ││f1 ││ │ │ └──────────────────────────────────────┘ │ │ ... │ ║
║ ││f2 ││ │ │ [════════════ timeline ════════════════] │ │ │ ║
║ │└────┘ ││ │ [════════════ crop bar ════════════════] │ │ [Neg] [Export] │ ║
║ └───────┘ │ ┌─ transport (always visible) ──────────┐ │ └───────────────────┘ ║
║ │ │▶ ⏸ x2 x4 🔒 --/-- ··· [Export] +₁+₂ Cancel Delete│ ║
║ │ ├─[ Export ]─[ Crop & Track ]─[ Scan ]──┤ ← control deck (tabs) ║
║ │ │ (controls for the active tab here) │ ║
║ │ └───────────────────────────────────────┘ ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ Ready. current file · profile: default · 8 wk ║ status bar
╚═══════════════════════════════════════════════════════════════════════════════╝
Why tabbed deck: Replaces the three stacked rows with a compact tab strip. The transport bar (most-used controls) stays always visible above the tabs; settings group by concern behind tabs. Trade-off accepted: viewing Scan + Export controls simultaneously costs a tab switch.
Control mapping
Every current control has an explicit home; nothing is removed.
Menu bar (rare / batch / management)
| Menu | Items |
|---|---|
| File | Open Files… · Set export folder… · Quit |
| Edit | Undo (Ctrl+Z → _scan_panel.undo) · Subprofiles ▸ (Add… / Remove…) |
| Scan | Scan current · Auto-export · Scan All… · Train classifier… |
| View | Review mode ✓ · Subcategory markers ▸ · Hide exported ✓ · Show hidden ✓ |
| Help | Keyboard shortcuts (? / F1) · What's new · About |
| corner (right) | Profile ▾ · ? |
Hard Negatives and Dataset Stats remain inside the Train dialog (main.py:682, 762) — not surfaced separately. Profile new/delete remains driven by the profile combo's activated handler.
Transport bar (always visible — playback + one-press export actions)
▶ Play · ⏸ Pause · x2 · x4 · 🔒 Lock · --/-- time · ⟨stretch⟩ · next-preview · **Export** · subprofile buttons ₁₂… · Cancel · Delete
Control deck — Export tab
Label · Category · Name · Folder + browse · Format · HW encode · Resize · Duration · Clips · Spread · Workers · Re-export
Control deck — Crop & Track tab
Portrait ratio · 1 random portrait · 1 random square · Track subject
Control deck — Scan tab
Scan model ▾ · ⏲ history · Scan · Auto · Speech · Review · Fuse · Threshold
Left pane (Queue) — unchanged
+ Open · filter · Hide exported · Show hidden · list tabs (tabbed / side-by-side)
Right pane (Scan results) — unchanged structurally
Decisions
- Train → Scan menu only (no deck button).
- Subcategory markers ("Sub") → View menu submenu (off the deck).
- Items appearing in both a menu and a visible control (Hide exported, Review, Scan, Auto) share one handler and stay synced.
Status bar
Restores QStatusBar (removes setStatusBar(None)):
- Left: transient feedback —
Exporting 2/3…,Scan complete · 14 regions,Ready.— with an optional inlineQProgressBarfor export/scan runs. Replaces_lbl_statusand the_status_timerclear logic. - Right (permanent widget):
current file · profile: <name> · <n> workers.
Visual polish
Extends the existing dark Fusion theme — no theme change.
- Aligned tab layouts — each deck tab uses
QFormLayout/grid solabel : controlpairs align in columns (biggest legibility win vs. today's ragged horizontal runs). - Primary/secondary button weight — Export gets an accent style (blue, reusing
#3a6ea8); Cancel/Delete read as secondary/destructive. The existing red Export = "armed to overwrite" state (main.py:5403) is preserved as a distinct state layered on top. - Consistent toggle states — x2 / x4 / 🔒 Lock / Review are checkable; one global
:checkedstyle replaces Lock's ad-hoc inline#4a3000swap (main.py:5705). - Spacing rhythm — uniform margins/spacing; fixed deck height (= tallest tab) so the video never resizes on tab switch.
- Label cleanup — de-abbreviate where cheap (
Thr→Threshold,Dur→Duration); replace cryptic⏲with a clearer history affordance. - One stylesheet block — fold scattered inline
setStyleSheetcalls into the central sheet (tabs, separators, status bar, toggles, primary button); keep per-widget overrides only for genuine state changes (overwrite-armed Export).
Implementation notes & risks
- Preserve all signal wiring. Controls are re-parented into new layouts, but every existing
connect()and the controls' object identities are kept — this is a layout move, not a rewrite of handlers. - Preserve all shortcuts. The
QShortcutblock (main.py:4450–4483) and_KeyFilterfocus suppression are untouched. Menu items reuse the same handler methods and may display the matching shortcut text. - Fixed deck height prevents video-area jump when switching tabs.
- Synced menu/button state — checkable menu items (Review, Hide exported) and their visible toggles must reflect each other; route both through the existing handler and update both widgets.
- Profile combo moves to a menu-bar corner widget but keeps its existing
activated→ new/delete/switch logic intact. - Risk: re-parenting a large
__init__is error-prone. Mitigate by moving controls in small, independently-runnable stages (menu bar → status bar → deck tabs → transport bar → polish), launching the app after each.
What this does NOT do
- No change to export, scan, tracking, or DB logic —
core/untouched. - No change to keyboard shortcuts or timeline mouse interactions.
- No theme change — stays dark Fusion.
- No new features — every control already exists; this is rehousing + polish.
- No change to the Queue or Scan-results panes' internal structure.