Interactor中的事件系统的使用
vtkRenderWindowInteractor routes events through VTK's command/observer
design pattern. That is, when vtkRenderWindowInteractor (actually, one of
its subclasses) sees an event, it translates it into
a VTK event using the InvokeEvent() method. Afterward, any vtkInteractorObservers
registered for that event are expected to respond appropriately.
1.Interactor首先在初始化中对所有事件进行设计模式相关方法的绑定
const handledEvents = [
'StartAnimation',
'Animation',
'EndAnimation',
'StartMouseMove',
'MouseMove',
'EndMouseMove',
'LeftButtonPress',
...
export function extend(publicAPI, model, initialValues = {}) {
...
macro.event(publicAPI, model, 'RenderEvent');
handledEvents.forEach((eventName) =>
macro.event(publicAPI, model, eventName)
);
...
}
macro.event注册后会生成invokeEvent(),onEvent(callback,priority),delete()三个方法来分发事件,注册事件钩子,删除所有事件钩子,参见尾部macro.event()方法
2.然后对所有事件生成对应方法来作为捕捉事件后的直接钩子,同时调用invokeEVENT()
RenderWindowInteractor.js
// create the generic Event methods
handledEvents.forEach((eventName) => {
const lowerFirst = eventName.charAt(0).toLowerCase() + eventName.slice(1);
publicAPI[`${lowerFirst}Event`] = (arg) => { //所有事件都生成“事件名+Event” 的方法作为事件分发的入口方法 如mouseMoveEvent
// Check that interactor enabled
if (!model.enabled) {
return;
}
// Check that a poked renderer exists
const renderer = publicAPI.getCurrentRenderer();
if (!renderer) {
vtkOnceErrorMacro(`
Can not forward events without a current renderer on the interactor.
`);
return;
}
// Pass the eventName and the poked renderer
const callData = {
type: eventName,
pokedRenderer: model.currentRenderer,
};
// Add the arguments to the call data
Object.assign(callData, arg);
// Call invoke
publicAPI[`invoke${eventName}`](callData); //事件分发
};
});
3.原始事件的绑定
//dom鼠标事件的绑定
function interactionRegistration(addListeners, force = false) {
const rootElm = document;
const method = addListeners ? 'addEventListener' : 'removeEventListener';
const invMethod = addListeners ? 'removeEventListener' : 'addEventListener';
if (!force && !addListeners && activeListenerCount > 0) {
--activeListenerCount;
}
// only add/remove listeners when there are no registered listeners
if (!activeListenerCount || force) {
activeListenerCount = 0;
if (model.container) {
model.container[invMethod]('mousemove', publicAPI.handleMouseMove);
}
rootElm[method]('mouseup', publicAPI.handleMouseUp);
// rootElm[method]('mouseleave', publicAPI.handleMouseUp);
rootElm[method]('mousemove', publicAPI.handleMouseMove);
rootElm[method]('touchend', publicAPI.handleTouchEnd, false);
rootElm[method]('touchcancel', publicAPI.handleTouchEnd, false);
rootElm[method]('touchmove', publicAPI.handleTouchMove, false);
}
//调用2步生成的各事件对应的钩子
publicAPI.handleMouseMove = (event) => {
// Do not consume event for move
// event.stopPropagation();
// event.preventDefault();
const callData = {
position: getScreenEventPositionFor(event),
};
const keys = getModifierKeysFor(event);
Object.assign(callData, keys);
if (model.moveTimeoutID === 0) {
publicAPI.startMouseMoveEvent(callData);
} else {
publicAPI.mouseMoveEvent(callData);
clearTimeout(model.moveTimeoutID);
}
// start a timer to keep us animating while we get mouse move events
model.moveTimeoutID = setTimeout(() => {
publicAPI.endMouseMoveEvent();
model.moveTimeoutID = 0;
}, 200);
};
macro.event对事件系统对应方法的绑定
vtkjs通过macro.event方法将所有事件注册
export function event(publicAPI, model, eventName) {
const callbacks = [];
const previousDelete = publicAPI.delete;
let curCallbackID = 1;
function off(callbackID) {
for (let i = 0; i < callbacks.length; ++i) {
const [cbID] = callbacks[i];
if (cbID === callbackID) {
callbacks.splice(i, 1);
return;
}
}
}
function on(callbackID) {
function unsubscribe() {
off(callbackID);
}
return Object.freeze({
unsubscribe,
});
}
function invoke() {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return;
}
/* eslint-disable prefer-rest-params */
// Go through a copy of the callbacks array in case new callbacks
// get prepended within previous callbacks
const currentCallbacks = callbacks.slice();
for (let index = 0; index < currentCallbacks.length; ++index) {
const [, cb, priority] = currentCallbacks[index];
if (!cb) {
continue; // eslint-disable-line
}
if (priority < 0) {
setTimeout(() => cb.apply(publicAPI, arguments), 1 - priority);
} else {
// Abort only if the callback explicitly returns false
const continueNext = cb.apply(publicAPI, arguments);
if (continueNext === EVENT_ABORT) {
break;
}
}
}
/* eslint-enable prefer-rest-params */
}
publicAPI[`invoke${capitalize(eventName)}`] = invoke;
publicAPI[`on${capitalize(eventName)}`] = (callback, priority = 0.0) => {
if (!callback.apply) {
console.error(`Invalid callback for event ${eventName}`);
return null;
}
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return null;
}
const callbackID = curCallbackID++;
callbacks.push([callbackID, callback, priority]);
callbacks.sort((cb1, cb2) => cb2[2] - cb1[2]);
return on(callbackID);
};
publicAPI.delete = () => {
previousDelete();
callbacks.forEach(([cbID]) => off(cbID));
};
}