@kitware/vtk.js
Version:
Visualization Toolkit for the Web
298 lines (268 loc) • 11.9 kB
JavaScript
import Constants from './Constants.js';
import { m as macro } from '../../../macros2.js';
import { k as add, s as subtract, l as normalize } from '../../../Common/Core/Math/index.js';
import { getNumberOfPlacedHandles, isHandlePlaced, calculateTextPosition, updateTextPosition, getPoint } from './helpers.js';
const {
ShapeType
} = Constants;
// Total number of points to place
const MAX_POINTS = 2;
const handleGetters = ['getHandle1', 'getHandle2', 'getMoveHandle'];
function widgetBehavior(publicAPI, model) {
model.classHierarchy.push('vtkLineWidgetProp');
model._isDragging = false;
/**
* Returns the handle at the handleIndex'th index.
* @param {number} handleIndex 0, 1 or 2
*/
publicAPI.getHandle = handleIndex => model.widgetState[handleGetters[handleIndex]]();
/**
* Return the index in the of tbe handle in `representations` array,
* or -1 if the handle is not present in the widget state.
*/
publicAPI.getHandleIndex = handle => {
switch (handle) {
case model.widgetState.getHandle1():
return 0;
case model.widgetState.getHandle2():
return 1;
case model.widgetState.getMoveHandle():
return 2;
default:
return -1;
}
};
publicAPI.isPlaced = () => getNumberOfPlacedHandles(model.widgetState) === MAX_POINTS;
// --------------------------------------------------------------------------
// Interactor event
// --------------------------------------------------------------------------
function ignoreKey(e) {
return e.altKey || e.controlKey || e.shiftKey;
}
function updateCursor(callData) {
model._isDragging = true;
const manipulator = model.activeState?.getManipulator?.() ?? model.manipulator;
manipulator.handleEvent(callData, model._apiSpecificRenderWindow);
model._apiSpecificRenderWindow.setCursor('grabbing');
model._interactor.requestAnimation(publicAPI);
}
// --------------------------------------------------------------------------
// Text methods
// --------------------------------------------------------------------------
publicAPI.setText = text => {
model.widgetState.getText().setText(text);
model._interactor.render();
};
// --------------------------------------------------------------------------
// Handle positioning methods
// --------------------------------------------------------------------------
// Handle utilities ---------------------------------------------------------
function getLineDirection(p1, p2) {
const dir = subtract(p1, p2, []);
normalize(dir);
return dir;
}
// Handle orientation & rotation ---------------------------------------------------------
function computeMousePosition(p1, callData) {
const displayMousePos = publicAPI.computeWorldToDisplay(model._renderer, ...p1);
const worldMousePos = publicAPI.computeDisplayToWorld(model._renderer, callData.position.x, callData.position.y, displayMousePos[2]);
return worldMousePos;
}
/**
* Returns the handle orientation to match the direction vector of the polyLine from one tip to another
* @param {number} handleIndex 0 for handle1, 1 for handle2
* @param {object} callData if specified, uses mouse position as 2nd point
*/
function getHandleOrientation(handleIndex) {
let callData = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
const point1 = getPoint(handleIndex, model.widgetState);
const point2 = callData ? computeMousePosition(point1, callData) : getPoint(1 - handleIndex, model.widgetState);
return point1 && point2 ? getLineDirection(point1, point2) : null;
}
/**
* Orient handle
* @param {number} handleIndex 0, 1 or 2
* @param {object} callData optional, see getHandleOrientation for details.
*/
function updateHandleOrientation(handleIndex) {
const orientation = getHandleOrientation(Math.min(1, handleIndex));
model.representations[handleIndex].setOrientation(orientation);
}
publicAPI.updateHandleOrientations = () => {
updateHandleOrientation(0);
updateHandleOrientation(1);
updateHandleOrientation(2);
};
publicAPI.rotateHandlesToFaceCamera = () => {
model.representations[0].setViewMatrix(Array.from(model._camera.getViewMatrix()));
model.representations[1].setViewMatrix(Array.from(model._camera.getViewMatrix()));
};
// Handles visibility ---------------------------------------------------------
/**
* Set actor visibility to true unless it is a NONE handle
* and uses state visibility variable for the displayActor visibility to
* allow pickable handles even when they are not displayed on screen
* @param handle : the handle state object
* @param handleNb : the handle number according to its label in widget state
*/
publicAPI.updateHandleVisibility = handleIndex => {
const handle = publicAPI.getHandle(handleIndex);
const visibility = handle.getVisible() && isHandlePlaced(handleIndex, model.widgetState);
model.representations[handleIndex].setVisibilityFlagArray([visibility, visibility && handle.getShape() !== ShapeType.NONE]);
model.representations[handleIndex].updateActorVisibility();
model._interactor.render();
};
/**
* Called when placing a point from the first time.
* @param {number} handleIndex
*/
publicAPI.placeHandle = handleIndex => {
const handle = publicAPI.getHandle(handleIndex);
handle.setOrigin(...model.widgetState.getMoveHandle().getOrigin());
publicAPI.updateHandleOrientations();
publicAPI.rotateHandlesToFaceCamera();
model.widgetState.getText().setOrigin(calculateTextPosition(model));
publicAPI.updateHandleVisibility(handleIndex);
if (handleIndex === 0) {
// For the line (handle1, handle2, moveHandle) to be displayed
// correctly, handle2 origin must be valid.
publicAPI.getHandle(1).setOrigin(...model.widgetState.getMoveHandle().getOrigin());
// Now that handle2 has a valid origin, hide it
publicAPI.updateHandleVisibility(1);
model.widgetState.getMoveHandle().setShape(publicAPI.getHandle(1).getShape());
}
if (handleIndex === 1) {
publicAPI.loseFocus();
}
};
// --------------------------------------------------------------------------
// Left press: Select handle to drag
// --------------------------------------------------------------------------
publicAPI.handleLeftButtonPress = e => {
if (!model.activeState || !model.activeState.getActive() || !model.pickable || ignoreKey(e)) {
return macro.VOID;
}
if (model.activeState === model.widgetState.getMoveHandle() && getNumberOfPlacedHandles(model.widgetState) === 0) {
publicAPI.placeHandle(0);
} else if (model.widgetState.getMoveHandle().getActive() && getNumberOfPlacedHandles(model.widgetState) === 1) {
publicAPI.placeHandle(1);
} else if (model.dragable && !model.widgetState.getText().getActive()) {
// Grab handle1, handle2 or whole widget
updateCursor(e);
}
publicAPI.invokeStartInteractionEvent();
return macro.EVENT_ABORT;
};
// --------------------------------------------------------------------------
// Mouse move: Drag selected handle / Handle follow the mouse
// --------------------------------------------------------------------------
publicAPI.handleMouseMove = callData => {
const manipulator = model.activeState?.getManipulator?.() ?? model.manipulator;
if (manipulator && model.pickable && model.dragable && model.activeState && model.activeState.getActive() && !ignoreKey(callData)) {
const {
worldCoords,
worldDelta
} = manipulator.handleEvent(callData, model._apiSpecificRenderWindow);
const isHandleMoving =
// is placing first or second handle
model.activeState === model.widgetState.getMoveHandle() ||
// is dragging already placed first or second handle
model._isDragging;
// the line state doesn't have setOrigin
const isDraggingLine = !model.activeState.setOrigin;
if (isHandleMoving) {
if (!isDraggingLine) {
const curOrigin = model.activeState.getOrigin();
if (curOrigin) {
model.activeState.setOrigin(add(model.activeState.getOrigin(), worldDelta, []));
} else {
model.activeState.setOrigin(worldCoords);
}
publicAPI.updateHandleVisibility(publicAPI.getHandleIndex(model.activeState));
} else {
// Dragging line; move all handles
for (let i = 0; i < 2; i++) {
const handleOrigin = publicAPI.getHandle(i).getOrigin();
publicAPI.getHandle(i).setOrigin(add(handleOrigin, worldDelta, []));
}
}
publicAPI.updateHandleOrientations();
updateTextPosition(model);
publicAPI.invokeInteractionEvent();
return macro.EVENT_ABORT;
}
}
return macro.VOID;
};
// --------------------------------------------------------------------------
// Left release: Finish drag
// --------------------------------------------------------------------------
publicAPI.handleLeftButtonRelease = () => {
if (!model.activeState || !model.activeState.getActive() || !model.pickable) {
publicAPI.rotateHandlesToFaceCamera();
return macro.VOID;
}
if (model.hasFocus && publicAPI.isPlaced()) {
publicAPI.loseFocus();
return macro.VOID;
}
if (model._isDragging && publicAPI.isPlaced()) {
const wasTextActive = model.widgetState.getText().getActive();
// Recompute offsets
model.widgetState.deactivate();
model.activeState = null;
if (!wasTextActive) {
model._interactor.cancelAnimation(publicAPI);
}
model._apiSpecificRenderWindow.setCursor('pointer');
model.hasFocus = false;
model._isDragging = false;
} else if (model.activeState !== model.widgetState.getMoveHandle()) {
model.widgetState.deactivate();
}
if (model.hasFocus && !model.activeState || model.activeState && !model.activeState.getActive()) {
model._widgetManager.enablePicking();
model._interactor.render();
}
publicAPI.invokeEndInteractionEvent();
return macro.EVENT_ABORT;
};
// --------------------------------------------------------------------------
// Focus API - moveHandle follow mouse when widget has focus
// --------------------------------------------------------------------------
publicAPI.grabFocus = () => {
if (!model.hasFocus && !publicAPI.isPlaced()) {
model.activeState = model.widgetState.getMoveHandle();
model.activeState.setShape(publicAPI.getHandle(0).getShape());
model.activeState.activate();
model._interactor.requestAnimation(publicAPI);
publicAPI.invokeStartInteractionEvent();
}
model.hasFocus = true;
};
// --------------------------------------------------------------------------
publicAPI.loseFocus = () => {
if (model.hasFocus) {
model._interactor.cancelAnimation(publicAPI);
publicAPI.invokeEndInteractionEvent();
}
model.widgetState.deactivate();
model.widgetState.getMoveHandle().deactivate();
model.widgetState.getMoveHandle().setOrigin(null);
model.activeState = null;
model.hasFocus = false;
model._widgetManager.enablePicking();
model._interactor.render();
};
publicAPI.reset = () => {
model.widgetState.deactivate();
model.widgetState.getMoveHandle().deactivate();
model.widgetState.getHandle1().setOrigin(null);
model.widgetState.getHandle2().setOrigin(null);
model.widgetState.getMoveHandle().setOrigin(null);
model.widgetState.getText().setOrigin(null);
model.widgetState.getText().setText('');
model.activeState = null;
};
}
export { widgetBehavior as default };