Files
gui-video-clipper/docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md

202 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v0.1.3 Polish & Tweaks — Design Spec
**Date:** 2026-09-22
**Version:** 0.1.2 → 0.1.3
## Overview
Three UI polish features for v0.1.3: adjustable clip-list layout, improved timeline timestamp labels with a mouse-proximity magnification effect, and a custom right-click context menu on the timeline.
---
## Feature 1: Adjustable Layout (Clip List Position)
### Current State
The main content area is a vertical stack:
```
┌──────────────────────────────┐
│ Toolbar │
├──────────────────────────────┤
│ Video Preview │
├──────────────────────────────┤
│ Transport Controls │
├──────────────────────────────┤
│ Timeline / Thumbs / Waveform │
├──────────────────────────────┤
│ Clip List │
├──────────────────────────────┤
│ Status Bar │
└──────────────────────────────┘
```
### Proposed: Two Layout Modes
A new `clipListPosition` preference (`'bottom' | 'left'`) controls where the clip list appears. Default: `'bottom'` (current behavior, unchanged).
When set to `'left'`, the layout becomes:
```
┌──────────────────────────────────────────┐
│ Toolbar │
├──────────┬───────────────────────────────┤
│ Clip │ Video Preview │
│ List │ │
│ │ │
│ ├───────────────────────────────┤
│ │ Transport Controls │
├──────────┴───────────────────────────────┤
│ Timeline / Thumbs / Waveform (full) │
├──────────────────────────────────────────┤
│ Status Bar │
└──────────────────────────────────────────┘
```
Key details:
- The clip list shares horizontal space only with the video preview and transport controls. The timeline remains full-width underneath — no horizontal space is sacrificed for timeline real estate.
- A vertical drag-to-resize handle sits between the clip list and the video area, allowing the user to adjust the sidebar width. Default width: ~220px. Min: 150px. Max: 40% of window width.
- The existing vertical resize handle between timeline and clip list only appears in `'bottom'` mode. In `'left'` mode, the timeline height is controlled by the existing `timelineHeight` state (still resizable via a horizontal drag handle between the upper section and the timeline).
### Toggle Button
A button in the toolbar (alongside ⚙ and ℹ) toggles between `'bottom'` and `'left'` modes. The icon visually hints at the current layout — e.g. a stacked-rows icon for bottom mode, a sidebar icon for left mode. Clicking it switches immediately and persists the preference.
### Persistence
- Add `clipListPosition: 'bottom' | 'left'` to the `Preferences` interface in `preferences.svelte.ts`.
- Add `clipListWidth: number` (default 220) for the sidebar width in left mode.
- Both are saved/loaded via the existing Tauri store mechanism.
### Implementation Notes
- `App.svelte` is the only component that needs structural changes. `ClipList.svelte`, `VideoPlayer.svelte`, and `Timeline.svelte` remain unchanged — they just get placed in different containers.
- In `'left'` mode, the `.content` area becomes:
- A flex column with two children:
1. **Upper section** (flex row): `[ClipList sidebar | flex column of [VideoPlayer, TransportControls]]`
2. **Lower section** (same as before): timeline pane with resize handle
- The vertical resize handle between the upper and lower sections works the same as the existing one.
- In `'bottom'` mode: the current layout is preserved exactly.
- The `ClipList` component needs no changes; it already fills its container. The only adjustment is removing the `border-top` when in left mode (the sidebar should have a `border-right` instead), which can be handled via a CSS class or prop.
---
## Feature 2: Timeline Timestamp Labels
### Current State
Timestamp labels are rendered at `10px` font size in `drawTimeTicks()` in `renderer.ts`. They are small and hard to read.
### Proposed Changes
1. **Increase base font size** from `10px` to `12px`.
2. **Mouse-proximity magnification effect:** As the cursor moves along the timeline, labels near the cursor smoothly scale up. This creates an invisible "magnifying lens" effect for timestamps.
### Magnification Behavior
- **Radius:** ~80px from cursor (in canvas pixel space).
- **Peak size:** ~18px directly under the cursor.
- **Falloff:** Smooth cosine interpolation from peak to base size. A label 80px away renders at base size; a label directly under the cursor renders at peak size.
- **Vertical alignment:** Labels stay bottom-aligned as they grow — the text baseline remains constant, so larger labels grow upward. This prevents layout jitter.
- **Cursor leaves timeline:** When the mouse leaves the canvas (`onmouseleave`), `mouseX` is set to `null` and all labels render at the base size. No magnification in this state.
### Implementation Notes
- `Timeline.svelte` already handles `onmousemove`. Add tracking of the mouse X coordinate (a simple `let mouseX = $state<number | null>(null)`), updated on `mousemove` and cleared on `mouseleave`.
- Pass `mouseX` into `drawTimeline()` → `drawTimeTicks()`.
- `drawTimeTicks()` computes the per-label font size based on distance from `mouseX`:
```
distance = abs(labelX - mouseX)
if distance > MAGNIFY_RADIUS: scale = 0
else: scale = (1 + cos(PI * distance / MAGNIFY_RADIUS)) / 2
fontSize = BASE_FONT_SIZE + BONUS_FONT_SIZE * scale
```
- Trigger a redraw whenever `mouseX` changes. Since `requestDraw()` already coalesces via `requestAnimationFrame`, this adds no extra frame overhead — it simply piggybacks on the next scheduled frame.
---
## Feature 3: Timeline Right-Click Context Menu
### Current State
Right-clicking the timeline shows the default web/Tauri context menu (Reload, Inspect Element), which is not useful.
### Proposed: Custom Context Menu
A styled context menu appears on right-click anywhere on the timeline canvas. Right-clicking also moves the playhead to the clicked position.
### Menu Items
| # | Label | Behavior | Availability |
|---|-------|----------|-------------|
| 1 | **Mark In** | Sets in-point at the right-clicked time (calls `markInPoint`) | Always |
| 2 | **Mark Out** | Sets out-point at the right-clicked time (calls `markOutPoint`) | Always |
| — | *(separator)* | | |
| 3 | **Go to Clip Start** | Seeks playhead to `selectedClip.startTime` | Disabled if no clip selected |
| 4 | **Go to Clip End** | Seeks playhead to `selectedClip.endTime` | Disabled if no clip selected |
| — | *(separator)* | | |
| 5 | **Delete Clip** | Removes the selected clip | Hidden if no clip selected |
### Behavior
- **Right-click on canvas:** `contextmenu` event is intercepted, default is prevented. The playhead moves to the clicked time position. The context menu appears at the cursor's screen coordinates.
- **Selecting an item:** Executes the action and closes the menu.
- **Closing without action:** Click anywhere outside the menu, press Escape, or scroll — the menu closes.
- **Positioning:** The menu is rendered as a positioned `<div>` appended at the `App.svelte` level (or uses a portal pattern) so it isn't clipped by any `overflow: hidden` containers. If the menu would extend beyond the window edge, it flips direction (e.g. opens leftward or upward).
### Component: `TimelineContextMenu.svelte`
Props:
- `x: number` — screen X position
- `y: number` — screen Y position
- `time: number` — the timeline time at the right-click position
- `selectedClipId: string | null` — to determine which items are enabled/hidden
- `onAction: (action: ContextMenuAction) => void` — callback for menu item selection
- `onClose: () => void` — callback to close the menu
Actions (discriminated union):
```typescript
type ContextMenuAction =
| { type: 'mark-in' }
| { type: 'mark-out' }
| { type: 'go-to-clip-start' }
| { type: 'go-to-clip-end' }
| { type: 'delete-clip' };
```
### Styling
- Dark theme matching the app's existing style (dark background, light text, hover highlights).
- Subtle border/shadow to float above the timeline.
- Disabled items: dimmed text, no hover effect, not clickable.
- Separators: thin horizontal lines using `var(--border)`.
### Implementation Notes
- `Timeline.svelte` adds an `oncontextmenu` handler on the canvas element.
- The handler computes the time from the click X coordinate (using existing `computeClickTime`), seeks the playhead, and emits an event / sets state to show the context menu.
- The context menu component is rendered conditionally in `Timeline.svelte` (or `App.svelte` if clipping is an issue).
- Keyboard shortcut hints can optionally be shown in the menu items (e.g. "Mark In I") for discoverability.
---
## Version Bump
Execute `scripts/bump-version.sh` after incrementing `VERSION`.
Confirm all locations updated from `0.1.2` → `0.1.3`:
- `package.json` — `version` field
- `src-tauri/tauri.conf.json` — `version` field
- `src-tauri/Cargo.toml` — `version` field
- `grep`/`find` for remaining `0.1.2` references, fix any, and add to `scripts/bump-version.sh`
---
## Scope Boundaries
- No changes to the backend (Rust). All three features are purely frontend.
- No changes to clip store logic — existing `markInPoint`, `markOutPoint`, `removeClip`, `selectClip` functions are reused as-is.
- No new dependencies.
- The context menu does NOT appear on the minimap — only on the main timeline canvas.