Fix layered collision selection
This commit is contained in:
+44
-13
@@ -731,21 +731,22 @@ function getSelectedObject() {
|
||||
function onCanvasMouseDown(event) {
|
||||
const world = screenToWorld(event.offsetX, event.offsetY);
|
||||
const selected = getSelectedObject();
|
||||
const object = hitTest(world.x, world.y);
|
||||
const stack = hitStackAt(world.x, world.y);
|
||||
const object = stack[0] || null;
|
||||
if (selected && selected.resize && isInsideResizeHandle(selected, world.x, world.y)) {
|
||||
state.dragging = { resize: true, last: snapPoint(world) };
|
||||
setCanvasCursor("nwse-resize");
|
||||
return;
|
||||
}
|
||||
if (selected && selected.move && isInsideBounds(selected, world.x, world.y) && canDragSelectedInPlace(selected, object)) {
|
||||
state.dragging = { last: snapPoint(world) };
|
||||
if (selected && selected.move && isInsideBounds(selected, world.x, world.y) && canDragSelectedInPlace(selected, object, stack)) {
|
||||
state.dragging = dragStateFor(selected, stack, world, true);
|
||||
setCanvasCursor("move");
|
||||
return;
|
||||
}
|
||||
|
||||
if (object) {
|
||||
state.selected = object.key;
|
||||
state.dragging = { last: snapPoint(world) };
|
||||
state.dragging = dragStateFor(object, stack, world, selected && sameSelection(selected.key, object.key));
|
||||
setCanvasCursor("move");
|
||||
refresh();
|
||||
} else {
|
||||
@@ -775,6 +776,7 @@ function onCanvasMouseMove(event) {
|
||||
pushUndo(state.dragging.resize ? "调整对象尺寸" : "移动对象");
|
||||
state.dragging.undoPushed = true;
|
||||
}
|
||||
state.dragging.moved = true;
|
||||
if (state.dragging.resize && object.resize) {
|
||||
object.resize(dx, dy);
|
||||
} else if (object.move) {
|
||||
@@ -786,7 +788,11 @@ function onCanvasMouseMove(event) {
|
||||
}
|
||||
|
||||
function onCanvasMouseUp() {
|
||||
const dragging = state.dragging;
|
||||
state.dragging = null;
|
||||
if (dragging && dragging.cycleOnClick && !dragging.moved && dragging.cycleKeys && dragging.cycleKeys.length > 1) {
|
||||
cycleSelection(dragging.cycleKeys, dragging.cycleFrom);
|
||||
}
|
||||
setCanvasCursor("crosshair");
|
||||
}
|
||||
|
||||
@@ -802,19 +808,23 @@ function onCanvasWheel(event) {
|
||||
}
|
||||
|
||||
function hitTest(x, y) {
|
||||
const objects = hitCandidates();
|
||||
for (const object of objects) {
|
||||
return hitStackAt(x, y)[0] || null;
|
||||
}
|
||||
|
||||
function hitStackAt(x, y) {
|
||||
const stack = [];
|
||||
for (const object of hitCandidates()) {
|
||||
const bounds = object.bounds && object.bounds();
|
||||
if (!bounds) continue;
|
||||
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) return object;
|
||||
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) stack.push(object);
|
||||
}
|
||||
return null;
|
||||
return stack;
|
||||
}
|
||||
|
||||
function hitCandidates() {
|
||||
const objects = collectObjects().filter(isHitFilterEnabled);
|
||||
if (state.hitFilter !== "all") return objects.reverse();
|
||||
const priority = { tile: 0, rect: 1, spawn: 2, collision: 3, zone: 4, player: 5, camera: 6 };
|
||||
const priority = { collision: 0, rect: 1, tile: 2, spawn: 3, zone: 4, player: 5, camera: 6 };
|
||||
return objects
|
||||
.map((object, index) => ({ object, index }))
|
||||
.sort((a, b) => {
|
||||
@@ -832,9 +842,29 @@ function isInsideBounds(object, x, y) {
|
||||
return x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height;
|
||||
}
|
||||
|
||||
function canDragSelectedInPlace(selected, topHit) {
|
||||
function canDragSelectedInPlace(selected, topHit, stack) {
|
||||
if (state.hitFilter !== "all") return true;
|
||||
return topHit && sameSelection(selected.key, topHit.key);
|
||||
return Boolean(topHit) && stack.some((object) => sameSelection(object.key, selected.key));
|
||||
}
|
||||
|
||||
function dragStateFor(object, stack, world, cycleOnClick) {
|
||||
return {
|
||||
last: snapPoint(world),
|
||||
cycleKeys: stack.map((item) => item.key),
|
||||
cycleFrom: object.key,
|
||||
cycleOnClick,
|
||||
moved: false,
|
||||
};
|
||||
}
|
||||
|
||||
function cycleSelection(cycleKeys, currentKey) {
|
||||
const index = cycleKeys.findIndex((key) => sameSelection(key, currentKey));
|
||||
if (index < 0) return;
|
||||
const nextKey = cycleKeys[(index + 1) % cycleKeys.length];
|
||||
if (!sameSelection(nextKey, state.selected)) {
|
||||
state.selected = nextKey;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function isHitFilterEnabled(object) {
|
||||
@@ -846,12 +876,13 @@ function isHitFilterEnabled(object) {
|
||||
function updateCanvasCursor(event) {
|
||||
const world = screenToWorld(event.offsetX, event.offsetY);
|
||||
const selected = getSelectedObject();
|
||||
const object = hitTest(world.x, world.y);
|
||||
const stack = hitStackAt(world.x, world.y);
|
||||
const object = stack[0] || null;
|
||||
if (selected && selected.resize && isInsideResizeHandle(selected, world.x, world.y)) {
|
||||
setCanvasCursor("nwse-resize");
|
||||
return;
|
||||
}
|
||||
if (selected && selected.move && isInsideBounds(selected, world.x, world.y) && canDragSelectedInPlace(selected, object)) {
|
||||
if (selected && selected.move && isInsideBounds(selected, world.x, world.y) && canDragSelectedInPlace(selected, object, stack)) {
|
||||
setCanvasCursor("move");
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user