27a30b0668
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
398 lines
13 KiB
Dart
398 lines
13 KiB
Dart
import "dart:ui";
|
|
|
|
import "package:dartamp/src/docking/dock_logic.dart";
|
|
import "package:flutter_test/flutter_test.dart";
|
|
|
|
void main() {
|
|
const snap = DockLogic.defaultSnapLen;
|
|
|
|
group("windowAttached", () {
|
|
test("returns 1 for a vertical seam with vertical overlap", () {
|
|
const a = Rect.fromLTWH(0, 0, 275, 116);
|
|
const b = Rect.fromLTWH(275, 10, 275, 116);
|
|
expect(DockLogic.windowAttached(a, b), 1);
|
|
});
|
|
|
|
test("returns 2 for a horizontal seam with horizontal overlap", () {
|
|
const a = Rect.fromLTWH(0, 0, 275, 116);
|
|
const b = Rect.fromLTWH(50, 116, 280, 290);
|
|
expect(DockLogic.windowAttached(a, b), 2);
|
|
});
|
|
|
|
test("returns 3 when both seams meet", () {
|
|
const a = Rect.fromLTWH(0, 0, 275, 116);
|
|
const b = Rect.fromLTWH(275, 116, 275, 116);
|
|
expect(DockLogic.windowAttached(a, b), 3);
|
|
});
|
|
|
|
test("returns 0 for disjoint windows", () {
|
|
const a = Rect.fromLTWH(0, 0, 275, 116);
|
|
const b = Rect.fromLTWH(500, 500, 275, 116);
|
|
expect(DockLogic.windowAttached(a, b), 0);
|
|
});
|
|
});
|
|
|
|
group("snapWindowBelow", () {
|
|
const dest = Rect.fromLTWH(0, 0, 275, 116);
|
|
|
|
test("docks src just below dest when its top is within snap range", () {
|
|
final src = Rect.fromLTWH(50, dest.bottom + snap - 3, 275, 116);
|
|
final out = DockLogic.snapWindowBelow(src, dest);
|
|
expect(out.top, dest.bottom);
|
|
expect(out.left, src.left);
|
|
});
|
|
|
|
test("leaves src unchanged outside snap range", () {
|
|
final src = Rect.fromLTWH(50, dest.bottom + 40, 275, 116);
|
|
expect(DockLogic.snapWindowBelow(src, dest), src);
|
|
});
|
|
|
|
test("does not dock src below dest without horizontal overlap", () {
|
|
final src = Rect.fromLTWH(400, dest.bottom + 2, 275, 116);
|
|
expect(DockLogic.snapWindowBelow(src, dest), src);
|
|
});
|
|
|
|
test("does not dock src above dest (docking is downward-only)", () {
|
|
final src = Rect.fromLTWH(50, dest.top - 3, 275, 116);
|
|
expect(DockLogic.snapWindowBelow(src, dest), src);
|
|
});
|
|
|
|
test("does not dock src beside dest (docking is vertical-only)", () {
|
|
final src = Rect.fromLTWH(dest.right - 3, 20, 275, 116);
|
|
expect(DockLogic.snapWindowBelow(src, dest), src);
|
|
});
|
|
});
|
|
|
|
group("snapToScreen", () {
|
|
const screen = Rect.fromLTWH(0, 0, 1440, 900);
|
|
|
|
test("snaps to the left and top edges", () {
|
|
const src = Rect.fromLTWH(-5, -5, 275, 116);
|
|
final out = DockLogic.snapToScreen(src, screen);
|
|
expect(out.left, 0);
|
|
expect(out.top, 0);
|
|
});
|
|
|
|
test("snaps to the right and bottom edges", () {
|
|
const src = Rect.fromLTWH(1440 - 275 + 5, 900 - 116 + 5, 275, 116);
|
|
final out = DockLogic.snapToScreen(src, screen);
|
|
expect(out.right, 1440);
|
|
expect(out.bottom, 900);
|
|
});
|
|
|
|
test("leaves rect unchanged away from the edges", () {
|
|
const src = Rect.fromLTWH(400, 300, 275, 116);
|
|
expect(DockLogic.snapToScreen(src, screen), src);
|
|
});
|
|
});
|
|
|
|
group("clampVisible", () {
|
|
const screen = Rect.fromLTWH(0, 0, 1440, 900);
|
|
const minVisible = DockLogic.defaultMinVisible;
|
|
|
|
test(
|
|
"keeps at least minVisible on-screen when dragged far off the left",
|
|
() {
|
|
const src = Rect.fromLTWH(-500, 300, 275, 116);
|
|
final out = DockLogic.clampVisible(src, screen);
|
|
expect(out.left, screen.left + minVisible - src.width);
|
|
expect(out.top, src.top);
|
|
},
|
|
);
|
|
|
|
test(
|
|
"keeps at least minVisible on-screen when dragged far off the top",
|
|
() {
|
|
const src = Rect.fromLTWH(300, -500, 275, 116);
|
|
final out = DockLogic.clampVisible(src, screen);
|
|
expect(out.left, src.left);
|
|
expect(out.top, screen.top + minVisible - src.height);
|
|
},
|
|
);
|
|
|
|
test(
|
|
"keeps at least minVisible on-screen when dragged far off the right",
|
|
() {
|
|
const src = Rect.fromLTWH(2000, 300, 275, 116);
|
|
final out = DockLogic.clampVisible(src, screen);
|
|
expect(out.left, screen.right - minVisible);
|
|
expect(out.top, src.top);
|
|
},
|
|
);
|
|
|
|
test(
|
|
"keeps at least minVisible on-screen when dragged far off the bottom",
|
|
() {
|
|
const src = Rect.fromLTWH(300, 2000, 275, 116);
|
|
final out = DockLogic.clampVisible(src, screen);
|
|
expect(out.left, src.left);
|
|
expect(out.top, screen.bottom - minVisible);
|
|
},
|
|
);
|
|
|
|
test("leaves a fully on-screen rect unchanged", () {
|
|
const src = Rect.fromLTWH(400, 300, 275, 116);
|
|
expect(DockLogic.clampVisible(src, screen), src);
|
|
});
|
|
|
|
test("returns the rect unchanged for an empty screen", () {
|
|
const src = Rect.fromLTWH(-500, 300, 275, 116);
|
|
expect(DockLogic.clampVisible(src, Rect.zero), src);
|
|
});
|
|
|
|
test(
|
|
"a slightly off-screen rect stays where the window still has minVisible "
|
|
"on-screen",
|
|
() {
|
|
const src = Rect.fromLTWH(-20, 100, 275, 116);
|
|
expect(DockLogic.clampVisible(src, screen), src);
|
|
},
|
|
);
|
|
});
|
|
|
|
group("moveBy and pointInRect", () {
|
|
test("moveBy shifts the top-left", () {
|
|
final out = DockLogic.moveBy(const Rect.fromLTWH(10, 20, 5, 6), 3, -2);
|
|
expect(out.left, 13);
|
|
expect(out.top, 18);
|
|
expect(out.width, 5);
|
|
expect(out.height, 6);
|
|
});
|
|
|
|
test("pointInRect respects the half-open bounds", () {
|
|
const r = Rect.fromLTWH(0, 0, 10, 10);
|
|
expect(DockLogic.pointInRect(const Offset(5, 5), r), isTrue);
|
|
expect(DockLogic.pointInRect(const Offset(10, 5), r), isFalse);
|
|
expect(DockLogic.pointInRect(const Offset(5, 10), r), isFalse);
|
|
});
|
|
});
|
|
|
|
group("dragCandidate", () {
|
|
test("moves the window by the pointer's window-local delta", () {
|
|
const grab = Offset(50, 30);
|
|
const applied = Rect.fromLTWH(100, 100, 275, 116);
|
|
final out = DockLogic.dragCandidate(
|
|
applied,
|
|
const Offset(150, 130),
|
|
grab,
|
|
);
|
|
expect(out.topLeft, const Offset(200, 200));
|
|
expect(out.width, applied.width);
|
|
expect(out.height, applied.height);
|
|
});
|
|
|
|
test("tracks the cursor absolutely regardless of the applied base", () {
|
|
const grab = Offset(50, 30);
|
|
// Pointer at screen (450, 330): window-local (50, 30) when the window
|
|
// really is at (400, 300). The base must cancel so a stale (lagging)
|
|
// applied position never accumulates error.
|
|
const lagging = Rect.fromLTWH(400, 300, 275, 116);
|
|
final kept = DockLogic.dragCandidate(lagging, const Offset(50, 30), grab);
|
|
expect(kept.topLeft, const Offset(400, 300));
|
|
// Cursor moves to screen (500, 400) -> window-local (100, 100).
|
|
final moved = DockLogic.dragCandidate(
|
|
lagging,
|
|
const Offset(100, 100),
|
|
grab,
|
|
);
|
|
expect(moved.topLeft, const Offset(450, 370));
|
|
});
|
|
});
|
|
|
|
group("fixOverlaps", () {
|
|
test("pushes r2 right when the horizontal gap is smaller", () {
|
|
const r1 = Rect.fromLTWH(0, 0, 50, 200);
|
|
const r2 = Rect.fromLTWH(40, 10, 50, 50);
|
|
final (_, out) = DockLogic.fixOverlaps(r1, r2);
|
|
expect(out.left, r1.right);
|
|
expect(out.top, r2.top);
|
|
});
|
|
|
|
test("pushes r2 down when the vertical gap is smaller", () {
|
|
const r1 = Rect.fromLTWH(0, 0, 200, 50);
|
|
const r2 = Rect.fromLTWH(10, 40, 50, 50);
|
|
final (_, out) = DockLogic.fixOverlaps(r1, r2);
|
|
expect(out.left, r2.left);
|
|
expect(out.top, r1.bottom);
|
|
});
|
|
|
|
test("leaves disjoint rects alone", () {
|
|
const r1 = Rect.fromLTWH(0, 0, 50, 50);
|
|
const r2 = Rect.fromLTWH(100, 100, 50, 50);
|
|
final out = DockLogic.fixOverlaps(r1, r2);
|
|
expect(out.$1, r1);
|
|
expect(out.$2, r2);
|
|
});
|
|
});
|
|
|
|
group("adjustSnap", () {
|
|
test("re-pins the left window's right edge to the right window's left", () {
|
|
const old1 = Rect.fromLTWH(0, 0, 275, 116);
|
|
const old2 = Rect.fromLTWH(275, 0, 275, 116);
|
|
const new1 = Rect.fromLTWH(0, 0, 330, 116);
|
|
const new2 = Rect.fromLTWH(275, 0, 275, 116);
|
|
final out = DockLogic.adjustSnap(old1, old2, new1, new2);
|
|
expect(out.$1, const Rect.fromLTWH(-55, 0, 330, 116));
|
|
expect(out.$2, new2);
|
|
});
|
|
});
|
|
|
|
group("dockedBelow", () {
|
|
test("includes only the dragged window when nothing is docked", () {
|
|
final rects = {
|
|
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
|
|
DockWindow.eq: const Rect.fromLTWH(500, 500, 275, 116),
|
|
};
|
|
final attached = DockLogic.dockedBelow(
|
|
rects: rects,
|
|
dragged: DockWindow.main,
|
|
);
|
|
expect(attached, {DockWindow.main});
|
|
});
|
|
|
|
test("walks the transitive closure of a vertical column", () {
|
|
final rects = {
|
|
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
|
|
DockWindow.eq: const Rect.fromLTWH(0, 116, 275, 116),
|
|
DockWindow.pl: const Rect.fromLTWH(0, 232, 280, 290),
|
|
};
|
|
final attached = DockLogic.dockedBelow(
|
|
rects: rects,
|
|
dragged: DockWindow.main,
|
|
);
|
|
expect(attached, {DockWindow.main, DockWindow.eq, DockWindow.pl});
|
|
});
|
|
|
|
test("excludes a window docked only beside the dragged window", () {
|
|
final rects = {
|
|
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
|
|
DockWindow.eq: const Rect.fromLTWH(275, 0, 275, 116),
|
|
};
|
|
final attached = DockLogic.dockedBelow(
|
|
rects: rects,
|
|
dragged: DockWindow.main,
|
|
);
|
|
expect(attached, {DockWindow.main});
|
|
});
|
|
|
|
test("excludes a window merely overlapping the dragged window", () {
|
|
final rects = {
|
|
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
|
|
DockWindow.eq: const Rect.fromLTWH(0, 100, 275, 116),
|
|
};
|
|
final attached = DockLogic.dockedBelow(
|
|
rects: rects,
|
|
dragged: DockWindow.main,
|
|
);
|
|
expect(attached, {DockWindow.main});
|
|
});
|
|
|
|
test("does not chain through a horizontal seam", () {
|
|
final rects = {
|
|
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
|
|
DockWindow.eq: const Rect.fromLTWH(275, 0, 275, 116),
|
|
DockWindow.pl: const Rect.fromLTWH(275, 116, 280, 290),
|
|
};
|
|
final attached = DockLogic.dockedBelow(
|
|
rects: rects,
|
|
dragged: DockWindow.main,
|
|
);
|
|
expect(attached, {DockWindow.main});
|
|
});
|
|
});
|
|
|
|
group("defaultSubRect", () {
|
|
const main = Rect.fromLTWH(0, 0, 275, 116);
|
|
const eqSize = Size(275, 116);
|
|
const plSize = Size(280, 290);
|
|
|
|
test("opens below main when alone (never beside it)", () {
|
|
expect(
|
|
DockLogic.defaultSubRect(
|
|
DockWindow.eq,
|
|
main,
|
|
eqSize,
|
|
const {},
|
|
),
|
|
const Rect.fromLTWH(0, 116, 275, 116),
|
|
);
|
|
expect(
|
|
DockLogic.defaultSubRect(
|
|
DockWindow.pl,
|
|
main,
|
|
plSize,
|
|
const {},
|
|
),
|
|
const Rect.fromLTWH(0, 116, 280, 290),
|
|
);
|
|
});
|
|
|
|
test("stacks pl below an eq dragged into a vertical column", () {
|
|
const eqBelow = Rect.fromLTWH(0, 116, 275, 116);
|
|
expect(
|
|
DockLogic.defaultSubRect(DockWindow.pl, main, plSize, {
|
|
DockWindow.eq: eqBelow,
|
|
}),
|
|
// below eq (bottom = 232), not on top of it
|
|
const Rect.fromLTWH(0, 232, 280, 290),
|
|
);
|
|
});
|
|
|
|
test("does not stack pl onto an eq sitting to the right of main", () {
|
|
const eqRight = Rect.fromLTWH(275, 0, 275, 116);
|
|
expect(
|
|
DockLogic.defaultSubRect(DockWindow.pl, main, plSize, {
|
|
DockWindow.eq: eqRight,
|
|
}),
|
|
const Rect.fromLTWH(0, 116, 280, 290),
|
|
);
|
|
});
|
|
});
|
|
|
|
group("repinResized", () {
|
|
test("moves a window docked below when the edge grows", () {
|
|
final oldRects = {
|
|
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
|
|
DockWindow.pl: const Rect.fromLTWH(0, 116, 280, 290),
|
|
};
|
|
const newRect = Rect.fromLTWH(0, 0, 275, 139.2);
|
|
final out = DockLogic.repinResized(
|
|
DockWindow.main,
|
|
newRect,
|
|
oldRects,
|
|
);
|
|
expect(out[DockWindow.pl]!.top, 139.2);
|
|
});
|
|
|
|
test("recursively re-pins a chain docked below", () {
|
|
final oldRects = {
|
|
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
|
|
DockWindow.pl: const Rect.fromLTWH(0, 116, 280, 290),
|
|
DockWindow.eq: const Rect.fromLTWH(0, 406, 275, 116),
|
|
};
|
|
const newRect = Rect.fromLTWH(0, 0, 275, 139.2);
|
|
final out = DockLogic.repinResized(
|
|
DockWindow.main,
|
|
newRect,
|
|
oldRects,
|
|
);
|
|
expect(out[DockWindow.pl]!.top, 139.2);
|
|
expect(out[DockWindow.eq]!.top, 429.2);
|
|
});
|
|
|
|
test("does not move a window beside the changed window", () {
|
|
final oldRects = {
|
|
DockWindow.main: const Rect.fromLTWH(0, 0, 275, 116),
|
|
DockWindow.eq: const Rect.fromLTWH(275, 0, 275, 116),
|
|
};
|
|
const newRect = Rect.fromLTWH(0, 0, 330, 116);
|
|
final out = DockLogic.repinResized(
|
|
DockWindow.main,
|
|
newRect,
|
|
oldRects,
|
|
);
|
|
expect(out[DockWindow.eq]!.left, 275);
|
|
});
|
|
});
|
|
}
|