Clip the children of an object to a clip area (#260)
An inventory grid could not scroll, because nothing could cut a child off at an edge. A game also had no way to hear the mouse wheel. Both are needed for a scrolling list, a dialog text or a window with more content than room. - A new object type Clip area has width and height and turns, scales and mirrors with its parent. The size gizmo drags width and height. - Everything below a clip area, on every level, is drawn only inside its rectangle, in the Scene view as in the game. Nested areas cut to their intersection. The rule works on a convex polygon, so a turned area cuts right as well. - On click, Is hovered and On mouse wheel do not hit a cut-off part. The Scene view does not pick it either. - In the Scene view the area is a dashed frame that is grabbed only at its edge, so a click inside lands on the content. The eye under Shape hides the frame. In the game the area is invisible. - While an object below an area or one of its parents is selected, the Scene view shows the cut-off part at 30 % opacity and it can be grabbed there. The rule lives in core/properties/clip.ts. - The Properties node knows the type Clip area. - The new event node On mouse wheel fires by the rule of On click. Its delta counts in notches, positive is down, and a frame's turns arrive as one value. It also gives x and y. Limits: - Only sprites and texts show the faded rest. Tilemaps and particles are cut and can be grabbed when open, but are not shown faded. - Masks by texture shape are out, as noted in the issue. Tests: - ./check.sh: 2773 tests, CHECK EXIT 0. - Full e2e run (local): ALL PASSED, 1549 checks, measured. - tests/core/clip-areas.test.ts: 12 tests for the cut rule, scene and game, save and load, hits, the wheel with and without owner, the notch conversion and the open rule. - e2e 58, 5 checks with real mouse and wheel: the scene cuts (74 of 74 red inside, 0 outside), a click on the cut part picks nothing, the selected rest is faded and grabbable, the game cuts, one notch logs 1. - e2e 04 and 46 count the object types and the eye rows. They now include Clip area. - Counter-checks: 6 in unit tests and 3 in the browser, all fail as expected. The originals were restored and checked with cmp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
89aa92d2e3
commit
0c41cb97b1
32 files changed
+759
-27
No files matched your search
+17
-1
@@ -85,6 +85,7 @@ export class World {
|
||||
down: false,
|
||||
pressed: false,
|
||||
released: false,
|
||||
wheel: 0,
|
||||
};
|
||||
/** Objects the editor is dragging (ids): the physics puts them where the drag does, at rest. */
|
||||
held: ReadonlySet<string> = new Set();
|
||||
@@ -373,6 +374,7 @@ export class World {
|
||||
dropPointerEdges(): void {
|
||||
this.pointer.pressed = false;
|
||||
this.pointer.released = false;
|
||||
this.pointer.wheel = 0;
|
||||
}
|
||||
|
||||
/** Game view: where the mouse is (world units) and whether it is over the view at all. */
|
||||
@@ -382,6 +384,11 @@ export class World {
|
||||
this.pointer.inside = inside;
|
||||
}
|
||||
|
||||
/** Game view: the wheel turned by `notches`; what turns within one tick adds up. */
|
||||
turnWheel(notches: number): void {
|
||||
this.pointer.wheel += notches;
|
||||
}
|
||||
|
||||
/** Game view: the mouse button changed. Sets the edge flag the current tick reports. */
|
||||
setPointerDown(down: boolean): void {
|
||||
if (down === this.pointer.down) return;
|
||||
@@ -404,7 +411,9 @@ export class World {
|
||||
*/
|
||||
hitTest(entity: Entity, x: number, y: number): boolean {
|
||||
const point = { x, y };
|
||||
const parts = [entity, ...this.descendantsOf(entity)].filter((e) => isHandleEnabled(e));
|
||||
const parts = [entity, ...this.descendantsOf(entity)].filter(
|
||||
(e) => isHandleEnabled(e) && this.insideClipAreas(e, point),
|
||||
);
|
||||
const colliders = parts.filter((e) => e.type === 'collider');
|
||||
if (colliders.length > 0)
|
||||
return colliders.some((c) =>
|
||||
@@ -417,6 +426,13 @@ export class World {
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether the point is inside every clip area above the entity (#260): what is cut off is not hit. */
|
||||
private insideClipAreas(entity: Entity, point: Vec2): boolean {
|
||||
for (let at = entity.parent; at; at = at.parent)
|
||||
if (at.type === 'clip' && !footprintContains('clip', at.props, worldMatrixOf(at), point)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
childrenOf(entity: Entity): Entity[] {
|
||||
return this.entities.filter((e) => e.parent === entity);
|
||||
}
|
||||
|
||||
@@ -473,6 +473,9 @@ export class BehaviorSystem implements System {
|
||||
if (pointer.pressed && (owner ? world.hitTest(owner, pointer.x, pointer.y) : pointer.inside)) {
|
||||
i.fire('OnClick', ctx, { x: pointer.x, y: pointer.y });
|
||||
}
|
||||
if (pointer.wheel !== 0 && (owner ? world.hitTest(owner, pointer.x, pointer.y) : pointer.inside)) {
|
||||
i.fire('OnMouseWheel', ctx, { delta: pointer.wheel, x: pointer.x, y: pointer.y });
|
||||
}
|
||||
i.fire('OnUpdate', ctx, { dt });
|
||||
},
|
||||
dt,
|
||||
|
||||
Reference in new issue
Block a user