# PLAN.md — Winamp Desktop (dartamp) work plan Order: A -> B -> C -> D (confirmed). Docking uses a custom in-Dart drag loop. Prefs window matches the full original tab set. Resumable across sessions; mark items `[x]` as done. ## Phase A — Skin subsystem (TODO items 5, 3, 4) ### A1. Extract classic-skin code into a project-level package (task 5) - [x] New package `dartamp_skin` at `packages/dartamp_skin/`. - [x] Moved `classic_skin.dart`, `skin_loader.dart`, `bmp_decoder.dart`, `skin_image.dart`, `skin_slider.dart`, `text_font.dart`, and `assets/skins/base/` into the package; added barrel + pubspec + analysis_options. - [x] Moved skin-debugging tooling (`tool/`) and `text_font_test.dart` into the package; added `base_skin_test.dart` (loadBaseSkin smoke test). - [x] App keeps all window/UI logic; imports `package:dartamp_skin/...`; `archive` dep moved to the package; app gained `path_provider` (for A2). - [x] Verify: `dart format`, `flutter analyze` (app + package clean), `flutter test` (7 tests green), `flutter build macos --debug` succeeds and bundles `packages/dartamp_skin/assets/...`. Note: package assets resolve at `packages/dartamp_skin/assets/skins/base/` in the bundle, so `loadBaseSkin` uses that path. ### A2. Self-contained storage + .wsz import (task 3) - [x] `SkinRegistry` in the package (`skin_registry.dart`): scans a directory for extracted skins, holds a `manifest.json` per skin (name + import time), `importArchive()` unzips a `.wsz` into `skins//` (flat top-level files only), `loadSkin()` overlays on the bundled base. Base is NOT extracted to disk: it stays bundled in app assets and is served directly by `loadBaseSkin()` (registry lists it first), so a first-run extraction is unnecessary. Imported skins never touch original Winamp files. - [x] App-side `SkinManager` (`lib/src/skin/skin_manager.dart`): owns the registry + active skin + loaded `ClassicSkin`; `apply()`/`importArchive()` persist `winamp.active_skin` and reload; sub-windows load their own skin by name via `loadAppSkin()`. - [x] Main window renders the manager's skin; EQ/PL sub-windows receive the skin name in `SubWindowArgs` at spawn and live `setSkin` pushes over `winamp.subwindows` control channel. - [x] Import UI: "Load Skin…" item added to the app menu (MainMenu.xib + AppDelegate `importSkin:`), wired to Dart `FilePicker` -> import + apply. - [x] Verify: `flutter analyze` (app + package clean), package tests green (11 incl. `skin_registry_test.dart`), `flutter build macos --debug` ok. ### A3. Cursors (task 4) - `ClassicSkin` exposes cursor bitmaps (`cursor.bmp`/`.cur`) + hotspot (default skin included). - Native `NSCursor` from skin bitmap via the `winamp` channel; applied with `MouseRegion` over the windows. ## Phase B — Docking & window management (new `lib/src/docking/`) ### B1. Plumbing - Persist per-window positions (`winamp.window__x/y` in PlayerState); sub-windows spawn at saved positions instead of `center()`. - Sub-windows report bounds over `winamp.subwindows`; main holds the authoritative rect map; main -> sub pushes on demand. - Add `getScreenBounds` to AppDelegate's `winamp` channel. - Replace `DragSurface.startDragging()` with a custom drag loop: pointer-down records start pos + grab; move computes delta -> snap -> `setPosition`; dragged window moves attached windows via relayed delta; drag-end persists positions + recomputes attachments. ### B2. Dock logic (task 1) — port from DOCK.cpp / Set.cpp / ui.cpp - `IsWindowAttached` (dynamic geometry), `SnapWindowToWindow`, `SnapToScreen`, `AdjustSnap`, `FixOverlaps`. - Shade-toggle re-pin (`doMyDirtyShitholeDockingShit`) when main/EQ/PL sizes change; docking severs automatically on window close. - Verify vs original: stacked main->PL->EQ moves together; moving a docked window undocks it; shade toggle keeps below-windows pinned; closing middle PL severs the chain. ### B3. Playlist resize from bottom-right (task 6) - Custom bottom-right resize handle (window_manager startResizing is Win/Linux-only). - Verify original row-snapping constraints; reuse re-pin for windows docked below PL. ## Phase C — UI fixes ### C1. EQ presets window (task 7) - Implement the presets window, wire the presets button, load/save to prefs. ### C2. PL bottom action buttons (task 8) - Research original `button.bmp` regions + popup behavior; clipped sprites + long-press full-sheet popup with release-to-activate; verify vs original. ## Phase D — Preferences + dock menu ### D1. Preferences window (task 2) - Match original classic layout (Setup, Audio, Visualization, Plug-ins, Skins, Titles, Directories, etc.); all tabs no-op except Skins (registry list + active selection). ### D2. Dock icon menu (task 9) - `applicationDockMenu(_:)` with Main/EQ/PL visibility toggles; add missing main-window item to the Window menu; flip `applicationShouldTerminateAfterLastWindowClosed` -> `false` so closing all windows keeps the app alive.