0f6c3faf6a
- Update the main window close behavior to use `windowManager.destroy()` for proper app termination. - Enhance cursor management in `SkinCursorsGate` to prevent stale cursor states during rapid enter/exit events. - Improve sub-window control channel registration to ensure readiness before main window updates. - Simplify the drag logic for sub-windows, ensuring only the dragged window moves independently. - Add diagnostics for mouse events in macOS to aid in debugging drag behavior. - Refactor docking logic to ensure proper vertical-only docking behavior and improve test coverage for window interactions. - Update tests to reflect changes in docking behavior and ensure accurate movement tracking for main and sub-windows. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
285 lines
13 KiB
Dart
285 lines
13 KiB
Dart
import "dart:ui";
|
|
|
|
import "package:dartamp/src/docking/dock_controller.dart";
|
|
import "package:dartamp/src/docking/dock_logic.dart";
|
|
import "package:flutter_test/flutter_test.dart";
|
|
|
|
const screen = Rect.fromLTWH(0, 0, 1440, 900);
|
|
|
|
void main() {
|
|
late List<(DockWindow, Rect)> moved;
|
|
late Map<DockWindow, Rect>? persisted;
|
|
late DockController dock;
|
|
|
|
setUp(() {
|
|
moved = [];
|
|
persisted = null;
|
|
dock = DockController(
|
|
moveWindow: (window, rect) => moved.add((window, rect)),
|
|
persistRects: (rects) => persisted = rects,
|
|
)..screenRect = screen;
|
|
});
|
|
|
|
test("setRect and removeWindow maintain the rect map", () {
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(275, 0, 275, 116));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(275, 0));
|
|
expect(dock.rects.length, 1);
|
|
dock.removeWindow(DockWindow.eq);
|
|
expect(dock.rectOf(DockWindow.eq), isNull);
|
|
});
|
|
|
|
test("a main-window drag moves the group docked below by the same delta", () {
|
|
// Vertical column: main, eq below it, pl below eq.
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 116, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(0, 232, 280, 290));
|
|
dock.beginMainDrag(const Offset(100, 50));
|
|
dock.mainDragTo(const Offset(300, 180));
|
|
dock.endMainDrag();
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(200, 130));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(200, 246));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(200, 362));
|
|
expect(persisted, isNotNull);
|
|
});
|
|
|
|
test(
|
|
"a main-window drag moves only the column below, not windows beside it",
|
|
() {
|
|
// eq docked to main's right; pl docked below main.
|
|
dock.setRect(
|
|
DockWindow.main,
|
|
const Rect.fromLTWH(100, 100, 275, 116),
|
|
);
|
|
dock.setRect(
|
|
DockWindow.eq,
|
|
const Rect.fromLTWH(375, 100, 275, 116),
|
|
);
|
|
dock.setRect(
|
|
DockWindow.pl,
|
|
const Rect.fromLTWH(100, 216, 280, 290),
|
|
);
|
|
dock.beginMainDrag(const Offset(200, 140));
|
|
dock.mainDragTo(const Offset(400, 260));
|
|
dock.endMainDrag();
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(300, 220));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(300, 336));
|
|
// eq is beside main, not docked below it: it does not follow.
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(375, 100));
|
|
},
|
|
);
|
|
|
|
test("a two-hop vertical chain (main -> eq -> pl) all follow the drag", () {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 116, 275, 116));
|
|
// pl docks below eq (still overlapping main's x-band) but offset 30px.
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(30, 232, 280, 290));
|
|
dock.beginMainDrag(const Offset(100, 50));
|
|
dock.mainDragTo(const Offset(300, 180));
|
|
// mainDelta = (200, 130); the transitive column all shifts.
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(200, 130));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(200, 246));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(230, 362));
|
|
});
|
|
|
|
test("dragging a sub-window moves only that window (it undocks)", () {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 116, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(0, 232, 280, 290));
|
|
// The sub reports it was dragged away from its dock.
|
|
dock.subBounds(DockWindow.eq, const Rect.fromLTWH(0, 146, 275, 116));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(0, 146));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(0, 232));
|
|
});
|
|
|
|
test("subBounds leaves unattached windows untouched", () {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 116, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(800, 800, 280, 290));
|
|
dock.subBounds(DockWindow.eq, const Rect.fromLTWH(0, 146, 275, 116));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(0, 146));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(800, 800));
|
|
});
|
|
|
|
test("movement tracks the cumulative pointer delta", () {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(500, 300, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(775, 300, 275, 116));
|
|
dock.beginMainDrag(const Offset(600, 340));
|
|
dock.mainDragTo(const Offset(700, 420));
|
|
dock.mainDragTo(const Offset(800, 500));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(700, 460));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(775, 300));
|
|
});
|
|
|
|
test("dragging to a screen corner snaps the main window", () {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(10, 10, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(285, 10, 275, 116));
|
|
dock.beginMainDrag(const Offset(100, 100));
|
|
dock.mainDragTo(const Offset(95, 95));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, Offset.zero);
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(285, 10));
|
|
});
|
|
|
|
test("an unattached window stays put while the group moves", () {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(500, 300, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(800, 500, 275, 116));
|
|
dock.beginMainDrag(const Offset(600, 340));
|
|
dock.mainDragTo(const Offset(650, 400));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(550, 360));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(800, 500));
|
|
});
|
|
|
|
test(
|
|
"a main-window drag does not pull in a window it overlaps",
|
|
() {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
// eq overlaps main's bottom edge; that is not a dock.
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 100, 275, 116));
|
|
dock.beginMainDrag(const Offset(100, 50));
|
|
dock.mainDragTo(const Offset(200, 150));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(100, 100));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(0, 100));
|
|
},
|
|
);
|
|
|
|
test(
|
|
"a sub-window resizing re-pins windows docked below, not beside",
|
|
() {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(275, 0, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(0, 116, 280, 290));
|
|
dock.repinResized(DockWindow.main, const Rect.fromLTWH(0, 0, 330, 139.2));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(275, 0));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(0, 139.2));
|
|
},
|
|
);
|
|
|
|
test("subBounds re-pins the chain docked below the resized window", () {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(0, 116, 280, 290));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 406, 275, 116));
|
|
dock.subBounds(DockWindow.pl, const Rect.fromLTWH(0, 116, 280, 14));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(0, 130));
|
|
});
|
|
|
|
test("repinResized keeps everything pinned when the edge does not move", () {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(275, 0, 275, 116));
|
|
dock.repinResized(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 14));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(275, 0));
|
|
});
|
|
|
|
test(
|
|
"dragging a sub-window far from the cluster moves only it",
|
|
() {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 116, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(0, 232, 280, 290));
|
|
// eq is dragged a long way away from main; only eq moves.
|
|
dock.subBounds(DockWindow.eq, const Rect.fromLTWH(600, 400, 275, 116));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(600, 400));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(0, 232));
|
|
},
|
|
);
|
|
|
|
test(
|
|
"dragging a non-docked window over the cluster does not stick to it",
|
|
() {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(275, 0, 275, 116));
|
|
// pl starts far away (not docked to anything).
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(900, 900, 280, 290));
|
|
// Drag pl so it overlaps main's x-band (not an edge dock).
|
|
dock.subBounds(DockWindow.pl, const Rect.fromLTWH(100, 50, 280, 290));
|
|
// main + eq must stay put; only pl moves to where it was dragged.
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(275, 0));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(100, 50));
|
|
},
|
|
);
|
|
|
|
test(
|
|
"a non-docked window reports a position but leaves others untouched",
|
|
() {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(275, 0, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(900, 900, 280, 290));
|
|
// pl is just reporting a moved position, not on a dock.
|
|
dock.subBounds(DockWindow.pl, const Rect.fromLTWH(800, 800, 280, 290));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(275, 0));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(800, 800));
|
|
},
|
|
);
|
|
|
|
test(
|
|
"dragging the middle of a 3-window column undocks it and moves only it",
|
|
() {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(0, 116, 280, 290));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 406, 275, 116));
|
|
// pl moves +50, +40; main + eq stay put (pl is dragged alone).
|
|
dock.subBounds(DockWindow.pl, const Rect.fromLTWH(50, 156, 280, 290));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(0, 406));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(50, 156));
|
|
// Frame 2: further +30, +20; still only pl moves.
|
|
dock.subBounds(DockWindow.pl, const Rect.fromLTWH(80, 176, 280, 290));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(0, 406));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(80, 176));
|
|
},
|
|
);
|
|
|
|
test(
|
|
"dragging the bottom of a 3-window column undocks it and moves only it",
|
|
() {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 116, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(0, 232, 280, 290));
|
|
dock.subBounds(DockWindow.pl, const Rect.fromLTWH(30, 262, 280, 290));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(0, 116));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(30, 262));
|
|
},
|
|
);
|
|
|
|
test(
|
|
"a main-window drag still moves the group after a sub drag ends",
|
|
() {
|
|
dock.setRect(DockWindow.main, const Rect.fromLTWH(0, 0, 275, 116));
|
|
dock.setRect(DockWindow.eq, const Rect.fromLTWH(0, 116, 275, 116));
|
|
dock.setRect(DockWindow.pl, const Rect.fromLTWH(0, 232, 280, 290));
|
|
// The sub drag moves pl away; main + eq stay put (undock).
|
|
dock.subBounds(DockWindow.pl, const Rect.fromLTWH(0, 400, 280, 290));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(0, 0));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(0, 116));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(0, 400));
|
|
// Main drag afterward: only the main + eq column follows.
|
|
dock.beginMainDrag(const Offset(100, 50));
|
|
dock.mainDragTo(const Offset(300, 180));
|
|
expect(dock.rectOf(DockWindow.main)!.topLeft, const Offset(200, 130));
|
|
expect(dock.rectOf(DockWindow.eq)!.topLeft, const Offset(200, 246));
|
|
expect(dock.rectOf(DockWindow.pl)!.topLeft, const Offset(0, 400));
|
|
dock.endMainDrag();
|
|
},
|
|
);
|
|
|
|
test("scaledSize accounts for shade state and UI scale", () {
|
|
expect(
|
|
DockController.scaledSize(DockWindow.main, false, 1.0),
|
|
const Size(275, 116),
|
|
);
|
|
expect(
|
|
DockController.scaledSize(DockWindow.eq, true, 1.0),
|
|
const Size(275, 14),
|
|
);
|
|
expect(
|
|
DockController.scaledSize(DockWindow.pl, false, 1.2),
|
|
const Size(336, 348),
|
|
);
|
|
});
|
|
}
|