Files
hans 212f8e16d2 feat: Implement window dragging and docking functionality
- Added WindowDragController to manage custom dragging of sub-windows.
- Integrated dragging logic into EqWindow and PlWindow, allowing for smooth window movement.
- Introduced docking logic to snap windows together and maintain their positions relative to each other.
- Implemented persistence of window positions using SharedPreferences.
- Enhanced AppDelegate to support screen bounds and mouse location retrieval for accurate window positioning.
- Updated pubspec.yaml to include screen_retriever dependency.
- Added comprehensive tests for dock controller and dock logic to ensure correct behavior during window interactions.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
2026-08-10 15:53:29 +02:00

6.4 KiB

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)

  • New package dartamp_skin at packages/dartamp_skin/.
  • 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.
  • Moved skin-debugging tooling (tool/) and text_font_test.dart into the package; added base_skin_test.dart (loadBaseSkin smoke test).
  • App keeps all window/UI logic; imports package:dartamp_skin/...; archive dep moved to the package; app gained path_provider (for A2).
  • 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)

  • 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/<name>/ (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.
  • 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().
  • 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.
  • Import UI: "Load Skin…" item added to the app menu (MainMenu.xib + AppDelegate importSkin:), wired to Dart FilePicker -> import + apply.
  • Verify: flutter analyze (app + package clean), package tests green (11 incl. skin_registry_test.dart), flutter build macos --debug ok.

A3. Cursors (task 4)

  • CurDecoder in the package (cur_decoder.dart): decodes Windows .cur resources (1/4/8/32 bpp, AND-mask transparency, hotspot) into SkinCursor { ui.Image, hotspotX, hotspotY }.
  • ClassicSkin exposes Map<String, SkinCursor> cursors keyed by the .cur file stem; the base skin bundles the original normal.cur/move.cur/resize.cur (copied from original/Src/Winamp/ resource/); loadBaseSkin + _buildSkin decode them, so imported skins can override cursors and fall back to base otherwise.
  • App: registerSkinCursors/setSkinCursor over the winamp channel; AppDelegate builds NSCursor from the PNG+hotspot and setCursor switches it. SkinCursorsGate (MouseRegion) applies the skin's normal cursor on enter and restores the arrow on exit, wrapping the main, EQ, and PL windows.
  • Note: sub-windows run their own Flutter engines and their winamp channel calls can't reach the AppDelegate handler, so custom cursors are applied on the main window; EQ/PL keep the system arrow for now.
  • Verify: package tests (14 incl. cur_decoder_test.dart), app analyze clean, flutter build macos --debug ok, app launches with no errors.

Phase B — Docking & window management (new lib/src/docking/)

B1. Plumbing

  • Persist per-window positions (winamp.window_<name>_x/y in PlayerState); sub-windows spawn at saved positions instead of center().
  • Sub-windows report bounds over winamp.subwindows (subReady/subBounds); main holds the authoritative rect map; main -> sub pushes on demand (setDockContext/setBounds on each sub-window's control channel).
  • Add getScreenBounds to AppDelegate's winamp channel.
  • Replace DragSurface.startDragging() with a custom drag loop (WindowDragSurface/WindowDragController): 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.
  • Unit tests for dock_logic.dart + dock_controller.dart (31 tests).

B2. Dock logic (task 1) — port from DOCK.cpp / Set.cpp / ui.cpp

  • IsWindowAttached (dynamic geometry), SnapWindowToWindow, SnapToScreen, AdjustSnap, FixOverlaps (lib/src/docking/dock_logic.dart).
  • 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.