move mouse, hover, click, drag into interact module
This commit is contained in:
parent
79f7a33823
commit
5dbe906629
2 changed files with 220 additions and 101 deletions
|
|
@ -7,6 +7,7 @@ import { local as sdb } from '../../data/local.js';
|
||||||
import { slider } from './slider.js';
|
import { slider } from './slider.js';
|
||||||
import { keyboard } from './keyboard.js';
|
import { keyboard } from './keyboard.js';
|
||||||
import { modal } from './modal.js';
|
import { modal } from './modal.js';
|
||||||
|
import { interact } from './interact.js';
|
||||||
import { space } from '../../moto/space.js';
|
import { space } from '../../moto/space.js';
|
||||||
import { LOCAL, SETUP } from './main.js';
|
import { LOCAL, SETUP } from './main.js';
|
||||||
import { VIEWS, MODES, SEED } from './consts.js';
|
import { VIEWS, MODES, SEED } from './consts.js';
|
||||||
|
|
@ -1114,22 +1115,35 @@ function init_one() {
|
||||||
|
|
||||||
api.platform.update_size();
|
api.platform.update_size();
|
||||||
|
|
||||||
function mouseOnHover(int, event, ints) {
|
// initialize interaction controller
|
||||||
if (!api.feature.hover) return;
|
interact.init({
|
||||||
if (!int) return api.feature.hovers || api.widgets.meshes();
|
api,
|
||||||
api.event.emit('mouse.hover', {int, ints, event, point: int.point, type: 'widget'});
|
space,
|
||||||
}
|
platform,
|
||||||
|
selection,
|
||||||
function platformOnHover(int, event) {
|
ui,
|
||||||
if (!api.feature.hover) return;
|
settings,
|
||||||
if (int) api.event.emit('mouse.hover', {point: int, event, type: 'platform'});
|
VIEWS,
|
||||||
}
|
onHover: (data) => {
|
||||||
|
api.event.emit('mouse.hover', data);
|
||||||
api.event.on("feature.hover", enable => {
|
},
|
||||||
space.mouse.onHover(enable ? mouseOnHover : undefined);
|
onHoverDown: (data) => {
|
||||||
space.platform.onHover(enable ? platformOnHover : undefined);
|
api.event.emit('mouse.hover.down', data);
|
||||||
|
},
|
||||||
|
onHoverUp: (data) => {
|
||||||
|
api.event.emit('mouse.hover.up', data);
|
||||||
|
},
|
||||||
|
onDragDone: (offset) => {
|
||||||
|
api.event.emit('mouse.drag.done', offset);
|
||||||
|
},
|
||||||
|
onSelectionDrag: (delta) => {
|
||||||
|
api.event.emit('selection.drag', delta);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// expose interact to API
|
||||||
|
api.interact = interact;
|
||||||
|
|
||||||
// prevent safari from exiting full screen mode
|
// prevent safari from exiting full screen mode
|
||||||
DOC.onkeydown = function (evt) {
|
DOC.onkeydown = function (evt) {
|
||||||
if (evt.code == 27) evt.preventDefault()
|
if (evt.code == 27) evt.preventDefault()
|
||||||
|
|
@ -1145,93 +1159,6 @@ function init_one() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
space.mouse.downSelect((int, event) => {
|
|
||||||
if (api.feature.on_mouse_down) {
|
|
||||||
if (int) {
|
|
||||||
api.feature.on_mouse_down(int, event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (api.feature.hover) {
|
|
||||||
if (int) {
|
|
||||||
return api.event.emit('mouse.hover.down', {int, point: int.point});
|
|
||||||
} else {
|
|
||||||
return selection.meshes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// lay flat with meta or ctrl clicking a selected face
|
|
||||||
if (int && (event.ctrlKey || event.metaKey || api.feature.on_face_select)) {
|
|
||||||
let q = new THREE.Quaternion();
|
|
||||||
// find intersecting point, look "up" on Z and rotate to face that
|
|
||||||
q.setFromUnitVectors(int.face.normal, new THREE.Vector3(0,0,-1));
|
|
||||||
selection.rotate(q);
|
|
||||||
}
|
|
||||||
if (api.view.get() !== VIEWS.ARRANGE) {
|
|
||||||
// return no selection in modes other than arrange
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
// return selected meshes for further mouse processing
|
|
||||||
return api.feature.hovers || selection.meshes();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
space.mouse.upSelect((object, event) => {
|
|
||||||
if (api.feature.on_mouse_up) {
|
|
||||||
if (event && object) {
|
|
||||||
return api.feature.on_mouse_up(object, event);
|
|
||||||
} else {
|
|
||||||
return api.widgets.meshes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event && api.feature.hover) {
|
|
||||||
api.event.emit('mouse.hover.up', { object, event });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event && event.target.nodeName === "CANVAS") {
|
|
||||||
if (object && object.object) {
|
|
||||||
if (object.object.widget) {
|
|
||||||
platform.select(object.object.widget, event.shiftKey, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
platform.deselect();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return api.feature.hovers || api.widgets.meshes();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
space.mouse.onDrag(function(delta, offset, up = false) {
|
|
||||||
if (api.feature.hover) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (up) {
|
|
||||||
api.event.emit('mouse.drag.done', offset);
|
|
||||||
}
|
|
||||||
if (delta && ui.freeLayout.checked) {
|
|
||||||
let set = settings();
|
|
||||||
let dev = set.device;
|
|
||||||
let bound = set.bounds_sel;
|
|
||||||
let width = dev.bedWidth/2;
|
|
||||||
let depth = dev.bedDepth/2;
|
|
||||||
let isout = (
|
|
||||||
bound.min.x <= -width ||
|
|
||||||
bound.min.y <= -depth ||
|
|
||||||
bound.max.x >= width ||
|
|
||||||
bound.max.y >= depth
|
|
||||||
);
|
|
||||||
if (!isout) {
|
|
||||||
if (bound.min.x + delta.x <= -width) return;
|
|
||||||
if (bound.min.y + delta.y <= -depth) return;
|
|
||||||
if (bound.max.x + delta.x >= width) return;
|
|
||||||
if (bound.max.y + delta.y >= depth) return;
|
|
||||||
}
|
|
||||||
selection.move(delta.x, delta.y, 0);
|
|
||||||
api.event.emit('selection.drag', delta);
|
|
||||||
} else {
|
|
||||||
return selection.meshes().length > 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
api.space.restore(init_two) || checkSeed(init_two) || init_two();
|
api.space.restore(init_two) || checkSeed(init_two) || init_two();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
192
src/kiri/core/interact.js
Normal file
192
src/kiri/core/interact.js
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interaction Controller (Singleton)
|
||||||
|
* Manages mouse hover, click, and drag interactions with 3D objects
|
||||||
|
*/
|
||||||
|
|
||||||
|
class InteractionControl {
|
||||||
|
#api;
|
||||||
|
#space;
|
||||||
|
#platform;
|
||||||
|
#selection;
|
||||||
|
#ui;
|
||||||
|
#settings;
|
||||||
|
#VIEWS;
|
||||||
|
#initialized = false;
|
||||||
|
|
||||||
|
// Callbacks to replace event emissions
|
||||||
|
#onHover;
|
||||||
|
#onHoverDown;
|
||||||
|
#onHoverUp;
|
||||||
|
#onDragDone;
|
||||||
|
#onSelectionDrag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the interaction controller with dependencies
|
||||||
|
* @param {Object} config
|
||||||
|
* @param {Object} config.api - API object
|
||||||
|
* @param {Object} config.space - Space object (3D scene)
|
||||||
|
* @param {Object} config.platform - Platform object
|
||||||
|
* @param {Object} config.selection - Selection object
|
||||||
|
* @param {Object} config.ui - UI element references
|
||||||
|
* @param {Function} config.settings - Settings getter function
|
||||||
|
* @param {Object} config.VIEWS - View constants
|
||||||
|
* @param {Function} config.onHover - Called when hovering over objects
|
||||||
|
* @param {Function} config.onHoverDown - Called on mouse down over object
|
||||||
|
* @param {Function} config.onHoverUp - Called on mouse up over object
|
||||||
|
* @param {Function} config.onDragDone - Called when drag is complete
|
||||||
|
* @param {Function} config.onSelectionDrag - Called during selection drag
|
||||||
|
*/
|
||||||
|
init({ api, space, platform, selection, ui, settings, VIEWS, onHover, onHoverDown, onHoverUp, onDragDone, onSelectionDrag }) {
|
||||||
|
if (this.#initialized) {
|
||||||
|
throw new Error('Interactions already initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#api = api;
|
||||||
|
this.#space = space;
|
||||||
|
this.#platform = platform;
|
||||||
|
this.#selection = selection;
|
||||||
|
this.#ui = ui;
|
||||||
|
this.#settings = settings;
|
||||||
|
this.#VIEWS = VIEWS;
|
||||||
|
this.#onHover = onHover;
|
||||||
|
this.#onHoverDown = onHoverDown;
|
||||||
|
this.#onHoverUp = onHoverUp;
|
||||||
|
this.#onDragDone = onDragDone;
|
||||||
|
this.#onSelectionDrag = onSelectionDrag;
|
||||||
|
|
||||||
|
this.#bindHoverHandlers();
|
||||||
|
this.#bindMouseHandlers();
|
||||||
|
this.#initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bindHoverHandlers() {
|
||||||
|
// Set up hover handlers
|
||||||
|
this.#api.event.on("feature.hover", enable => {
|
||||||
|
this.#space.mouse.onHover(enable ? this.#mouseOnHover.bind(this) : undefined);
|
||||||
|
this.#space.platform.onHover(enable ? this.#platformOnHover.bind(this) : undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#mouseOnHover(int, event, ints) {
|
||||||
|
if (!this.#api.feature.hover) return;
|
||||||
|
if (!int) return this.#api.feature.hovers || this.#api.widgets.meshes();
|
||||||
|
this.#onHover?.({int, ints, event, point: int.point, type: 'widget'});
|
||||||
|
}
|
||||||
|
|
||||||
|
#platformOnHover(int, event) {
|
||||||
|
if (!this.#api.feature.hover) return;
|
||||||
|
if (int) this.#onHover?.({point: int, event, type: 'platform'});
|
||||||
|
}
|
||||||
|
|
||||||
|
#bindMouseHandlers() {
|
||||||
|
// Mouse down handler
|
||||||
|
this.#space.mouse.downSelect((int, event) => {
|
||||||
|
// Feature hook for custom mouse down handling
|
||||||
|
if (this.#api.feature.on_mouse_down) {
|
||||||
|
if (int) {
|
||||||
|
this.#api.feature.on_mouse_down(int, event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover mode handling
|
||||||
|
if (this.#api.feature.hover) {
|
||||||
|
if (int) {
|
||||||
|
this.#onHoverDown?.({int, point: int.point});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return this.#selection.meshes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lay flat with meta or ctrl clicking a selected face
|
||||||
|
if (int && (event.ctrlKey || event.metaKey || this.#api.feature.on_face_select)) {
|
||||||
|
let q = new THREE.Quaternion();
|
||||||
|
// find intersecting point, look "up" on Z and rotate to face that
|
||||||
|
q.setFromUnitVectors(int.face.normal, new THREE.Vector3(0,0,-1));
|
||||||
|
this.#selection.rotate(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#api.view.get() !== this.#VIEWS.ARRANGE) {
|
||||||
|
// return no selection in modes other than arrange
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// return selected meshes for further mouse processing
|
||||||
|
return this.#api.feature.hovers || this.#selection.meshes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mouse up handler
|
||||||
|
this.#space.mouse.upSelect((object, event) => {
|
||||||
|
// Feature hook for custom mouse up handling
|
||||||
|
if (this.#api.feature.on_mouse_up) {
|
||||||
|
if (event && object) {
|
||||||
|
return this.#api.feature.on_mouse_up(object, event);
|
||||||
|
} else {
|
||||||
|
return this.#api.widgets.meshes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover mode handling
|
||||||
|
if (event && this.#api.feature.hover) {
|
||||||
|
this.#onHoverUp?.({ object, event });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular selection handling
|
||||||
|
if (event && event.target.nodeName === "CANVAS") {
|
||||||
|
if (object && object.object) {
|
||||||
|
if (object.object.widget) {
|
||||||
|
this.#platform.select(object.object.widget, event.shiftKey, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.#platform.deselect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.#api.feature.hovers || this.#api.widgets.meshes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag handler
|
||||||
|
this.#space.mouse.onDrag((delta, offset, up = false) => {
|
||||||
|
if (this.#api.feature.hover) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (up) {
|
||||||
|
this.#onDragDone?.(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta && this.#ui.freeLayout.checked) {
|
||||||
|
let set = this.#settings();
|
||||||
|
let dev = set.device;
|
||||||
|
let bound = set.bounds_sel;
|
||||||
|
let width = dev.bedWidth/2;
|
||||||
|
let depth = dev.bedDepth/2;
|
||||||
|
let isout = (
|
||||||
|
bound.min.x <= -width ||
|
||||||
|
bound.min.y <= -depth ||
|
||||||
|
bound.max.x >= width ||
|
||||||
|
bound.max.y >= depth
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isout) {
|
||||||
|
if (bound.min.x + delta.x <= -width) return;
|
||||||
|
if (bound.min.y + delta.y <= -depth) return;
|
||||||
|
if (bound.max.x + delta.x >= width) return;
|
||||||
|
if (bound.max.y + delta.y >= depth) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#selection.move(delta.x, delta.y, 0);
|
||||||
|
this.#onSelectionDrag?.(delta);
|
||||||
|
} else {
|
||||||
|
return this.#selection.meshes().length > 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
export const interact = new InteractionControl();
|
||||||
Loading…
Reference in a new issue