smolworld / src /js /input.js
p3nGu1nZz's picture
✨ Refactor physics module; add math utilities and event handling classes; update state structure for 3D entities
7ffaa9e
import { getConfig } from './config.js';
import { EventEmitter, EventTypes } from './event.js';
import { InputHandler, LayerHandler } from './handler.js';
export function createInputHandler(canvas, scene) {
const eventEmitter = new EventEmitter();
const inputHandler = new InputHandler(canvas);
let cameraControlsEnabled = true;
// Attach DOM events to trigger eventEmitter
function attachDomEvents() {
window.addEventListener('keydown', (e) => {
eventEmitter.emit(EventTypes.KEY_DOWN, e);
});
window.addEventListener('keyup', (e) => {
eventEmitter.emit(EventTypes.KEY_UP, e);
});
window.addEventListener('keypress', (e) => {
eventEmitter.emit(EventTypes.KEY_PRESS, e);
// Emit TEXT_INPUT for single characters
if (e.key.length === 1) {
eventEmitter.emit(EventTypes.TEXT_INPUT, e.key);
}
});
canvas.addEventListener('mousemove', (e) => {
eventEmitter.emit(EventTypes.MOUSE_MOVE, e);
});
canvas.addEventListener('mousedown', (e) => {
eventEmitter.emit(EventTypes.MOUSE_DOWN, e);
});
canvas.addEventListener('mouseup', (e) => {
eventEmitter.emit(EventTypes.MOUSE_UP, e);
});
canvas.addEventListener('wheel', (e) => {
eventEmitter.emit(EventTypes.MOUSE_WHEEL, e);
}, { passive: false });
canvas.addEventListener('click', (e) => {
eventEmitter.emit(EventTypes.CLICK, e);
});
}
function setupCameraControls() {
const config = getConfig();
const moveSpeed = config?.camera?.moveSpeed || 10;
const zoomSpeed = config?.camera?.zoomSpeed || 0.1;
const inverted = config?.controls?.inverted || false;
const moveDir = inverted ? 1 : -1;
eventEmitter.on(EventTypes.KEY_DOWN, (e) => {
if (!cameraControlsEnabled) return;
switch (e.key.toLowerCase()) {
case 'w': scene.camera.moveBy(0, moveSpeed * moveDir); break;
case 's': scene.camera.moveBy(0, -moveSpeed * moveDir); break;
case 'a': scene.camera.moveBy(-moveSpeed * moveDir, 0); break;
case 'd': scene.camera.moveBy(moveSpeed * moveDir, 0); break;
case '+':
case '=': scene.camera.zoomBy(1 + zoomSpeed); break;
case '-':
case '_': scene.camera.zoomBy(1 - zoomSpeed); break;
}
});
eventEmitter.on(EventTypes.MOUSE_WHEEL, (e) => {
if (!cameraControlsEnabled) return;
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
scene.camera.zoomBy(zoomFactor);
e.preventDefault();
});
}
function setupLayerHandlers() {
scene.getLayers().forEach(layer => {
const handlers = [
new LayerHandler(EventTypes.KEY_DOWN, (e, layer) => layer.handleKeyDown?.(e)),
new LayerHandler(EventTypes.MOUSE_MOVE, (e, layer) => {
const pos = inputHandler.getMousePosition(e);
layer.handleMouseMove?.(pos.x, pos.y);
}),
new LayerHandler(EventTypes.CLICK, (e, layer) => {
const pos = inputHandler.getMousePosition(e);
layer.handleClick?.(pos.x, pos.y, scene.camera);
}),
new LayerHandler(EventTypes.MOUSE_WHEEL, (e, layer) => layer.handleWheel?.(e))
];
handlers.forEach(handler => {
handler.bind(layer);
inputHandler.addHandler(handler.eventType, handler);
});
});
}
attachDomEvents();
setupCameraControls();
setupLayerHandlers();
return {
listenForKey: (key, callback) => eventEmitter.on(EventTypes.KEY_DOWN, (e) => {
if (e.key === key) callback(e);
}),
removeKeyListener: (key, callback) => eventEmitter.off(EventTypes.KEY_DOWN, (e) => {
if (e.key === key) callback(e);
}),
addTextInputListener: (callback) => eventEmitter.on(EventTypes.TEXT_INPUT, callback),
removeTextInputListener: (callback) => eventEmitter.off(EventTypes.TEXT_INPUT, callback),
removeAllListeners: () => eventEmitter.clear(),
enableCameraControls: () => { cameraControlsEnabled = true; },
disableCameraControls: () => { cameraControlsEnabled = false; },
on: (event, callback) => eventEmitter.on(event, callback),
off: (event, callback) => eventEmitter.off(event, callback),
cleanup: () => {
eventEmitter.clear();
inputHandler.disable();
}
};
}