mode enter/leave callbacks

This commit is contained in:
Ladd 2025-12-27 12:45:42 -06:00
parent 4f88d404d6
commit 28909a0c82
7 changed files with 68 additions and 20 deletions

View File

@ -44,6 +44,10 @@ export const GRAVITATIONAL_CONSTANT = 1E5;
// CSS CLASS NAMES // CSS CLASS NAMES
export const DRAGGABLE_ELEMENT_CLASSNAME = 'lhg-draggable-element'; export const DRAGGABLE_ELEMENT_CLASSNAME = 'lhg-draggable-element';
// EVENT NAMES
export const EVENT_MODE_LEAVE = 'lhg-mode-leave';
export const EVENT_MODE_ENTER = 'lhg-mode-enter';
// MODES // MODES
export const MODE_MASS_GENERATION = 'mass-gen'; export const MODE_MASS_GENERATION = 'mass-gen';
export const MODE_PAN_VIEW = 'pan-view'; export const MODE_PAN_VIEW = 'pan-view';

View File

@ -194,7 +194,6 @@ export class Display {
// Draw arrow for the velocity // Draw arrow for the velocity
if (this.sim.getOption('display.velocity')) { if (this.sim.getOption('display.velocity')) {
console.log('velocity vector');
const speed = Math.sqrt(vx ** 2 + vy ** 2); const speed = Math.sqrt(vx ** 2 + vy ** 2);
const endVx = x + VELOCITY_VECTOR_SCALE * vx / speed * Math.log(speed); const endVx = x + VELOCITY_VECTOR_SCALE * vx / speed * Math.log(speed);
const endVy = y + VELOCITY_VECTOR_SCALE * vy / speed * Math.log(speed); const endVy = y + VELOCITY_VECTOR_SCALE * vy / speed * Math.log(speed);

View File

@ -98,11 +98,13 @@ export class Objects {
} }
objectAtLocation(x, y) { objectAtLocation(x, y) {
this.forEachObject(obj => { let idx = undefined;
this.forEachObject((obj, i) => {
// If distance to object is less than object's radius, we are touching the object // If distance to object is less than object's radius, we are touching the object
const dist = Math.pow((obj.position.x - x)**2 + (obj.position.y - y)**2, 1/2); const dist = Math.pow((obj.position.x - x)**2 + (obj.position.y - y)**2, 1/2);
if (dist <= obj.radius) { if (dist <= obj.radius) {
return i; idx = i;
return null;
} }
}); });
} }
@ -137,11 +139,13 @@ export class Objects {
} }
// cb: (obj, idx) => {} // cb: (obj, idx) => {}
// TODO: Reducer
forEachObject(cb, alive = true, startWith = 0) { forEachObject(cb, alive = true, startWith = 0) {
for (let i = startWith; i < this.objects.length; i++) { for (let i = startWith; i < this.objects.length; i++) {
const obj = this.objects[i]; const obj = this.objects[i];
if (alive === null || alive == obj.alive) { if (alive === null || alive == obj.alive) {
cb(obj, i); const ret = cb(obj, i);
if (ret === null) break;
} }
} }
} }

View File

@ -90,6 +90,11 @@ export class Pointer {
this.sim.scheduleZoom({x, y}, factor); this.sim.scheduleZoom({x, y}, factor);
}); });
// When leaving panning mode, clear panning
this.sim.onModeLeave(MODE_PAN_VIEW, () => {
this.panning = undefined;
});
} }
getPointerVelocity() { getPointerVelocity() {
@ -179,10 +184,6 @@ export class Pointer {
} }
// Apply update to viewOrigin based on panning // Apply update to viewOrigin based on panning
if (!this.sim.isCurrentMode(MODE_PAN_VIEW)) {
this.panning = undefined;
return;
}
if (this.panning) { if (this.panning) {
const {pointerStart, pointerCurrent, viewOriginStart, velocity} = this.sim.pointer.panning; const {pointerStart, pointerCurrent, viewOriginStart, velocity} = this.sim.pointer.panning;
// Convert pointer velocity to sim internal scale // Convert pointer velocity to sim internal scale

View File

@ -36,7 +36,6 @@ export class Sim {
this.display = new Display(this); this.display = new Display(this);
this.overlay = new Overlay(this); this.overlay = new Overlay(this);
this.objects = new Objects(this); this.objects = new Objects(this);
this.pointer = new Pointer(this);
this.toolbar = new Toolbar(this); this.toolbar = new Toolbar(this);
// Set up toolbar // Set up toolbar
@ -45,6 +44,8 @@ export class Sim {
this.toolbar.addTool(new ModeSwitch(this.toolbar)); this.toolbar.addTool(new ModeSwitch(this.toolbar));
this.toolbar.addTool(new Options(this.toolbar)); this.toolbar.addTool(new Options(this.toolbar));
this.pointer = new Pointer(this);
// Initiate main loop // Initiate main loop
this.time = document.timeline.currentTime; this.time = document.timeline.currentTime;
requestAnimationFrame(t => this.loop(t)); requestAnimationFrame(t => this.loop(t));

View File

@ -3,6 +3,8 @@ import { Tool } from '../tool.js';
import { import {
MODE_MASS_GENERATION, MODE_MASS_GENERATION,
MODE_PAN_VIEW, MODE_PAN_VIEW,
EVENT_MODE_LEAVE,
EVENT_MODE_ENTER,
} from '../config.js'; } from '../config.js';
export class ModeSwitch extends Tool { export class ModeSwitch extends Tool {
@ -16,9 +18,6 @@ export class ModeSwitch extends Tool {
constructor(toolbar) { constructor(toolbar) {
super(toolbar); super(toolbar);
const [[currentModeID, _]] = this.modes;
this.currentMode = currentModeID;
const modesDiv = document.createElement('div'); const modesDiv = document.createElement('div');
const titleDiv = document.createElement('div'); const titleDiv = document.createElement('div');
@ -41,18 +40,18 @@ export class ModeSwitch extends Tool {
button.innerHTML = `<h3>${modeTitle}</h3>`; button.innerHTML = `<h3>${modeTitle}</h3>`;
button.classList.add('wide'); button.classList.add('wide');
button.addEventListener('click', (e) => { button.addEventListener('click', (e) => this.setMode(modeID));
if (this.currentMode !== modeID) {
this.currentMode = modeID;
this.setModesOpacity();
} }
});
} // First listed mode is the default
this.setModesOpacity(); const [[currentModeID, _]] = this.modes;
this.setMode(currentModeID);
// Add global method to get current mode / check mode // Add global method to get current mode / check mode
this.sim.getCurrentMode = () => this.currentMode; this.sim.getCurrentMode = () => this.currentMode;
this.sim.isCurrentMode = (modeID) => modeID === this.currentMode; this.sim.isCurrentMode = (modeID) => modeID === this.currentMode;
this.sim.onModeLeave = (modeID, cb) => this.onModeLeave(modeID, cb);
this.sim.onModeEnter = (modeID, cb) => this.onModeEnter(modeID, cb);
} }
setModesOpacity() { setModesOpacity() {
@ -61,6 +60,32 @@ export class ModeSwitch extends Tool {
} }
} }
// TODO: on enter / on leave mode / some sort of callbacks on mode transitions setMode(modeID) {
if (modeID === this.currentMode) return;
const leave = new CustomEvent(EVENT_MODE_LEAVE, {detail: {modeID: this.currentMode}});
const enter = new CustomEvent(EVENT_MODE_LEAVE, {detail: {modeID}});
this.currentMode = modeID;
this.setModesOpacity();
this.div.dispatchEvent(leave);
this.div.dispatchEvent(enter);
}
// cb: () => {}
onModeLeave(modeID, cb) {
this.div.addEventListener(EVENT_MODE_LEAVE, (e) => {
if (e.detail?.modeID === modeID) {
cb();
}
});
}
// cb: () => {}
onModeEnter(modeID, cb) {
this.div.addEventListener(EVENT_MODE_ENTER, (e) => {
if (e.detail?.modeID === modeID) {
cb();
}
});
}
} }

View File

@ -51,6 +51,20 @@ export class Zoom extends Tool {
const factor = Math.floor(base2factor) - 1; const factor = Math.floor(base2factor) - 1;
this.sim.scheduleZoom({x, y}, factor); this.sim.scheduleZoom({x, y}, factor);
} }
// Determine average velocity and set panning velocity to match
const totalVelocity = {x: 0, y: 0};
let count = 0;
this.sim.objects.forEachObject(obj => {
count++;
totalVelocity.x += obj.velocity.x;
totalVelocity.y += obj.velocity.y;
});
const vx = totalVelocity.x / count;
const vy = totalVelocity.y / count;
console.log('zoom, pan', vx, vy);
this.sim.pointer.panning = {
velocity: {x: vx, y: vy},
};
}); });
} }
} }