Skip to content

Commit 70c8c4c

Browse files
committed
dragging logic
1 parent 1ddf710 commit 70c8c4c

File tree

3 files changed

+130
-12
lines changed

3 files changed

+130
-12
lines changed

src/Project.ts

Lines changed: 118 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ export default class Project {
2424
public answer: string | null;
2525
private timerStart!: Date;
2626

27+
public draggingSprite: Sprite | null;
28+
public dragThreshold: number;
29+
private _consideringDraggingSprite: Sprite | null;
30+
private _dragOffsetX: number;
31+
private _dragOffsetY: number;
32+
private _idleDragTimeout: number | null;
33+
2734
/**
2835
* Used to keep track of what edge-activated trigger predicates evaluted to
2936
* on the previous step.
@@ -56,6 +63,14 @@ export default class Project {
5663
this.restartTimer();
5764

5865
this.answer = null;
66+
this.draggingSprite = null;
67+
this._consideringDraggingSprite = null;
68+
this._dragOffsetX = 0;
69+
this._dragOffsetY = 0;
70+
this._idleDragTimeout = null;
71+
72+
// TODO: Enable customizing, like frameRate
73+
this.dragThreshold = 3;
5974

6075
// Run project code at specified framerate
6176
setInterval(() => {
@@ -68,6 +83,7 @@ export default class Project {
6883

6984
public attach(renderTarget: string | HTMLElement): void {
7085
this.renderer.setRenderTarget(renderTarget);
86+
7187
this.renderer.stage.addEventListener("click", () => {
7288
// Chrome requires a user gesture on the page before we can start the
7389
// audio context.
@@ -76,24 +92,56 @@ export default class Project {
7692
if (Sound.audioContext.state === "suspended") {
7793
void Sound.audioContext.resume();
7894
}
95+
});
7996

80-
let clickedSprite = this.renderer.pick(this.spritesAndClones, {
81-
x: this.input.mouse.x,
82-
y: this.input.mouse.y,
83-
});
84-
if (!clickedSprite) {
85-
clickedSprite = this.stage;
97+
this.renderer.stage.addEventListener("mousedown", () => {
98+
const spriteUnderMouse = this._spriteUnderMouse;
99+
const targetUnderMouse = this._targetUnderMouse;
100+
if (spriteUnderMouse && spriteUnderMouse.draggable) {
101+
this._consideringDraggingSprite = spriteUnderMouse;
102+
this._startIdleDragTimeout();
103+
} else {
104+
this._startClickTriggersFor(targetUnderMouse);
86105
}
106+
});
107+
108+
this.renderer.stage.addEventListener("mousemove", () => {
109+
// TODO: Effects - goto() and moveAhead() - are applied immediately.
110+
// Do we want to buffer them to apply at the start of the next tick?
111+
if (this.input.mouse.down) {
112+
if (this._consideringDraggingSprite && this.input.mouse.downAt) {
113+
const distanceX = this.input.mouse.x - this.input.mouse.downAt.x;
114+
const distanceY = this.input.mouse.y - this.input.mouse.downAt.y;
115+
const distanceFromMouseDown = Math.sqrt(
116+
distanceX ** 2 + distanceY ** 2
117+
);
118+
if (distanceFromMouseDown > this.dragThreshold) {
119+
this._startDragging();
120+
}
121+
}
87122

88-
const matchingTriggers: TriggerWithTarget[] = [];
89-
for (const trigger of clickedSprite.triggers) {
90-
if (trigger.matches(Trigger.CLICKED, {}, clickedSprite)) {
91-
matchingTriggers.push({ trigger, target: clickedSprite });
123+
if (this.draggingSprite) {
124+
const gotoX = this.input.mouse.x + this._dragOffsetX;
125+
const gotoY = this.input.mouse.y + this._dragOffsetY;
126+
this.draggingSprite.goto(gotoX, gotoY, true);
92127
}
93128
}
129+
});
94130

95-
void this._startTriggers(matchingTriggers);
131+
this.renderer.stage.addEventListener("mouseup", () => {
132+
if (!this._clearDragging()) {
133+
const spriteUnderMouse = this._spriteUnderMouse;
134+
if (spriteUnderMouse && spriteUnderMouse.draggable) {
135+
this._startClickTriggersFor(spriteUnderMouse);
136+
}
137+
}
96138
});
139+
140+
if (this.renderer.stage.ownerDocument) {
141+
this.renderer.stage.ownerDocument.addEventListener("mouseup", () => {
142+
void this._clearDragging();
143+
});
144+
}
97145
}
98146

99147
public greenFlag(): void {
@@ -155,6 +203,65 @@ export default class Project {
155203
void this._startTriggers(triggersToStart);
156204
}
157205

206+
private _startClickTriggersFor(target: Sprite | Stage): void {
207+
const matchingTriggers: TriggerWithTarget[] = [];
208+
for (const trigger of target.triggers) {
209+
if (trigger.matches(Trigger.CLICKED, {}, target)) {
210+
matchingTriggers.push({ trigger, target });
211+
}
212+
}
213+
214+
void this._startTriggers(matchingTriggers);
215+
}
216+
217+
private get _spriteUnderMouse(): Sprite | null {
218+
return this.renderer.pick(this.spritesAndClones, {
219+
x: this.input.mouse.x,
220+
y: this.input.mouse.y,
221+
});
222+
}
223+
224+
private get _targetUnderMouse(): Sprite | Stage {
225+
return this._spriteUnderMouse || this.stage;
226+
}
227+
228+
private _startDragging(): void {
229+
if (this._consideringDraggingSprite) {
230+
this.draggingSprite = this._consideringDraggingSprite;
231+
this._consideringDraggingSprite = null;
232+
this._clearIdleDragTimeout();
233+
234+
this._dragOffsetX = this.draggingSprite.x - this.input.mouse.x;
235+
this._dragOffsetY = this.draggingSprite.y - this.input.mouse.y;
236+
237+
this.draggingSprite.moveAhead();
238+
}
239+
}
240+
241+
private _clearDragging(): boolean {
242+
const wasDragging = !!this.draggingSprite;
243+
this.draggingSprite = null;
244+
this._consideringDraggingSprite = null;
245+
this._dragOffsetX = 0;
246+
this._dragOffsetY = 0;
247+
this._clearIdleDragTimeout();
248+
return wasDragging;
249+
}
250+
251+
private _startIdleDragTimeout(): void {
252+
this._idleDragTimeout = window.setTimeout(
253+
this._startDragging.bind(this),
254+
400
255+
);
256+
}
257+
258+
private _clearIdleDragTimeout(): void {
259+
if (typeof this._idleDragTimeout === "number") {
260+
clearTimeout(this._idleDragTimeout);
261+
this._idleDragTimeout = null;
262+
}
263+
}
264+
158265
private step(): void {
159266
this._cachedLoudness = null;
160267
this._stepEdgeActivatedTriggers();

src/Renderer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,12 @@ export default class Renderer {
611611
}
612612
}
613613

614+
// Filter out the sprite that is being dragged, if any.
615+
// A sprite that is being dragged can detect other sprites, but other sprites can't detect it.
616+
if (this.project.draggingSprite) {
617+
targets.delete(this.project.draggingSprite);
618+
}
619+
614620
const sprBox = Rectangle.copy(
615621
this.getBoundingBox(spr),
616622
__collisionBox

src/Sprite.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,10 @@ export class Sprite extends SpriteBase {
634634
this._project.runningTriggers = this._project.runningTriggers.filter(
635635
({ target }) => target !== this
636636
);
637+
638+
if (this._project.draggingSprite === this) {
639+
this._project.draggingSprite = null;
640+
}
637641
}
638642

639643
public andClones(): this[] {
@@ -648,7 +652,8 @@ export class Sprite extends SpriteBase {
648652
this._direction = this.normalizeDeg(dir);
649653
}
650654

651-
public goto(x: number, y: number): void {
655+
public goto(x: number, y: number, fromDrag?: boolean): void {
656+
if (this._project.draggingSprite === this && !fromDrag) return;
652657
if (x === this.x && y === this.y) return;
653658

654659
if (this.penDown) {

0 commit comments

Comments
 (0)