import { Utils } from '../common/Utils'
import { EventTrigger } from '../common/EventTrigger';
const __ = {
private: Symbol('private'),
}
// #region Private Functions
// Check whether it's keyboard event.
function _isKeyboardEvent(ev) {
switch (ev.type) {
case 'keyup':
case 'keydown':
case 'keypress':
return true;
default:
break;
}
return false;
}
// Check whether it's mouse event.
function _isMouseEvent(ev) {
switch (ev.type) {
case 'mousedown':
case 'mouseup':
case 'mousemove':
case 'pointerdown':
case 'pointerup':
case 'pointermove':
case 'click':
case 'dblclick':
return true;
default:
break;
}
return false;
}
// Check whether it's global event.
function _isGlobalEvent(ev) {
if (_isKeyboardEvent(ev)) {
return true;
}
return false;
}
// Process pick object callback.
function _processPickObjectCall(object, ev) {
let callback = object.onProcessPickObject || object.constructor.onProcessPickObject;
// Let the target static function to process pick object event before event trigger
if (callback) {
callback(ev, object);
}
}
// Get proxy object.
function _getProxyObject(object, ev) {
let proxyObject = object;
if (proxyObject.getProxyObject()) {
do {
_processPickObjectCall(proxyObject, ev);
proxyObject = proxyObject.getProxyObject();
}
while (proxyObject.getProxyObject());
}
return proxyObject;
}
// Build mouse hover event.
function _buildMouseHoverEvent(type, object, ev) {
let result = {
type,
object,
x: ev.x,
y: ev.y,
deltaX: ev.deltaX,
deltaY: ev.deltaY,
altKey: ev.altKey,
ctrlKey: ev.ctrlKey,
shiftKey: ev.shiftKey,
pickedId: ev.pickedId,
subNodeName: ev.subNodeName,
};
if (ev.target) {
result.target = ev.target;
}
return result;
}
// #endregion
/**
* @class EventManager
* The event manager.
* @memberof THING
*/
class EventManager extends EventTrigger {
/**
* The event manager to register, unregister or trigger event(s).
*/
constructor() {
super();
this[__.private] = {};
let _private = this[__.private];
// The current hover object(for 'mouseenter/mouseleave' event usage)
_private.hoverInfo = {
object: null,
pickedId: null
}
_private.app = Utils.getCurrentApp();
// Hook application delegate events
_private.app.delegate.addEventListener('*', (ev) => {
// Process event info
let pickedObject = this._processEventInfo(ev);
// Get the event type
let type = ev.type;
// Process mouse(enter/leave) event
if (type == 'mousemove') {
this._processMouseLeave(pickedObject, ev);
this._processMouseEnter(pickedObject, ev);
}
// Process mouse leave event
else if (type == 'mouseleave') {
this._processMouseLeave(pickedObject, ev);
}
// Process DOM element enter event
else if (type == 'domenter') {
this._processMouseLeave(pickedObject, ev);
}
// Dispatch event to object
if (pickedObject) {
this.dispatchEvent(type, pickedObject, ev, {});
}
else {
// Check whether it's global event what can dispatch to every objects
if (_isGlobalEvent(ev)) {
this.traverseListenerByType(type, (listener) => {
ev.object = listener.object;
this.invokeListener(listener, ev);
});
}
// Trigger event by root
else {
// We do not hit any objects
ev.object = null;
ev.subNodeName = '';
this.dispatchEvent(type, _private.app.root, ev, {});
}
}
});
}
// #region Private
// Process pick.
_processPick(ev) {
let _private = this[__.private];
let result = _private.app.renderCamera.pick(ev.x, ev.y);
if (!result) {
return null;
}
let pickedObject = result.object;
if (pickedObject) {
pickedObject = _getProxyObject(pickedObject, ev);
_processPickObjectCall(pickedObject, ev);
}
// Copy pick info
ev.subNodeName = result.subNodeName;
ev.external = result.external;
// Copy picked Id when it's existing (it's from merge geometries)
if (result.pickedId !== undefined) {
ev.pickedId = result.pickedId;
}
// To prevent BIG deal for these actions, we make property here to delay some operations
Object.defineProperties(ev, {
'pickedPosition': {
get() {
return result.position;
}
},
'normal': {
get() {
return result.normal;
}
}
});
return pickedObject;
}
// Check whether can process pick action.
_needProcessPick(ev) {
let _private = this[__.private];
let app = _private.app;
let picker = app.picker;
// If mouse up event then we skip the camera control disable picker event
if (ev.type == 'mouseup') {
let stateGroup = picker.stateGroup;
return stateGroup.isEnableByFilter((name) => {
if (name == 'OrbitControls-start') {
return false;
}
return true;
});
}
else {
return picker.enable;
}
}
// Process event info.
_processEventInfo(ev) {
if (ev.type == 'domenter') {
ev.target = null;
}
else {
if (_isMouseEvent(ev)) {
ev.buttonType = Utils.parseMouseButtonType(ev.button);
// Check whether enable pick
if (this._needProcessPick(ev)) {
return this._processPick(ev);
}
}
}
return null;
}
_processMouseLeave(object, ev) {
let _private = this[__.private];
// Only works when picker is enable
let app = _private.app;
if (!app.picker.enable) {
return;
}
// The hovering object can not be the leaving object
let hoverInfo = _private.hoverInfo;
if (!hoverInfo.object || hoverInfo.object == object) {
return;
}
// Notify mouse leave event
let mouseEvent = _buildMouseHoverEvent('mouseleave', hoverInfo.object, ev);
if (Utils.isValid(hoverInfo.pickedId)) {
mouseEvent.pickedId = hoverInfo.pickedId;
}
this.dispatchEvent(mouseEvent.type, mouseEvent.object, mouseEvent, {});
// Clear current hover object
hoverInfo.object = null;
hoverInfo.pickedId = null;
}
_processMouseEnter(object, ev) {
let _private = this[__.private];
// Only works when picker is enable
let app = _private.app;
if (!app.picker.enable) {
return;
}
// Make sure we get object by picker
if (!object) {
return;
}
// Skip for the current hovering object
let hoverInfo = _private.hoverInfo;
if (hoverInfo.object == object) {
return;
}
// Notify mouse enter event
let mouseEvent = _buildMouseHoverEvent('mouseenter', object, ev);
this.dispatchEvent(mouseEvent.type, mouseEvent.object, mouseEvent, {});
// Update current hover object
hoverInfo.object = object;
if (Utils.isValid(ev.pickedId)) {
hoverInfo.pickedId = ev.pickedId;
}
else {
hoverInfo.pickedId = null;
}
}
// #endregion
/**
* Get current hover object.
* @type {THING.BaseObject}
* @private
*/
get curHoverObject() {
let _private = this[__.private];
let hoverInfo = _private.hoverInfo;
if (!hoverInfo) {
return null;
}
return hoverInfo.object;
}
}
export { EventManager }