WidgetManager

Methods

addWidget

Register a widget on the widget manager instance.
Please note that one should link the widget manager to a view before calling this method.

Argument Type Required Description
widget vtkAbstractWidgetFactory Yes The abstract widget factory.
viewType ViewTypes No
initialValues Object No

disablePicking

Disable the picking.

enablePicking

Enable the picking.

extend

Method used to decorate a given object (publicAPI+model) with vtkWidgetManager characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues Yes (default: {})

extractRenderingComponents

Extract the rendering components from the given renderer.

Argument Type Required Description
renderer vtkRenderer Yes The vtkRenderer instance.

getCaptureOn

Get the captureOn value.

getPickingEnabled

Returns true if picking is enabled, false otherwise.

getSelectedData

The all currently selected data.

getSelectedDataForXY

Given x and y parameter, get selected data.

Argument Type Required Description
x Number Yes
y Number Yes

getSelections

Get the current selection.

getViewId

Get the view id.

getViewType

Get the view type.

getWidgets

Get all the underlying widgets.

grabFocus

Given the focus to the given widget instance.

Argument Type Required Description
widget vtkAbstractWidget or vtkAbstractWidgetFactory Yes The widget instance which should get the focus.

newInstance

Method used to create a new instance of vtkCellArray

Argument Type Required Description
initialValues Yes for pre-setting some of its content

releaseFocus

Release the focus.

removeWidget

Remove a widget from the widget manager.

Argument Type Required Description
widget vtkAbstractWidget or vtkAbstractWidgetFactory Yes The widget to remove

removeWidgets

Unregister all widgets from the widget manager.

renderWidgets

Renders all the widgets.

setCaptureOn

The the captureOn value.
CaptureOn.MOUSE_MOVE: captures small region when moving mouse
CaptureOn.MOUSE_RELEASE: captures entire region when mouse button is released

Argument Type Required Description
captureOn CaptureOn Yes

setRenderer

Set the renderer.

Argument Type Required Description
renderer vtkRenderer Yes

setViewType

The the view type.

Argument Type Required Description
type ViewTypes Yes

Source

Constants.d.ts
export declare enum ViewTypes {
DEFAULT = 0,
GEOMETRY = 1,
SLICE = 2,
VOLUME = 3,
YZ_PLANE = 4,
XZ_PLANE = 5,
XY_PLANE = 6
}

export declare enum RenderingTypes {
PICKING_BUFFER = 0,
FRONT_BUFFER = 1
}

export declare enum CaptureOn {
MOUSE_MOVE = 0,
MOUSE_RELEASE = 1
}

declare const _default: {
ViewTypes: typeof ViewTypes;
RenderingTypes: typeof RenderingTypes;
CaptureOn: typeof CaptureOn;
}

export default _default;
Constants.js
export const ViewTypes = {
DEFAULT: 0,
GEOMETRY: 1,
SLICE: 2,
VOLUME: 3,
YZ_PLANE: 4, // Sagittal
XZ_PLANE: 5, // Coronal
XY_PLANE: 6, // Axial
};

export const RenderingTypes = {
PICKING_BUFFER: 0,
FRONT_BUFFER: 1,
};

export const CaptureOn = {
MOUSE_MOVE: 0,
MOUSE_RELEASE: 1,
};

export default {
ViewTypes,
RenderingTypes,
CaptureOn,
};
index.d.ts
import vtkAbstractWidget from '../AbstractWidget';
import vtkAbstractWidgetFactory from '../AbstractWidgetFactory';
import vtkCamera from '../../../Rendering/Core/Camera';
import vtkProp from '../../../Rendering/Core/Prop';
import vtkRenderer from '../../../Rendering/Core/Renderer';
import vtkRenderWindow from '../../../Rendering/Core/RenderWindow';
import vtkRenderWindowInteractor from '../../../Rendering/Core/RenderWindowInteractor';
import vtkSelectionNode from '../../../Common/DataModel/SelectionNode';
import vtkWidgetRepresentation from '../../Representations/WidgetRepresentation';
import vtkWidgetState from '../WidgetState';
import { vtkObject } from '../../../interfaces';
import { CaptureOn, ViewTypes } from './Constants';
import { Nullable } from '../../../types';

export interface ISelectedData {
requestCount: number;
propID: number;
compositeID: number;
prop: vtkProp;
widget: vtkAbstractWidget;
representation: vtkWidgetRepresentation;
selectedState: vtkWidgetState;
}

export interface IRenderingComponents {
renderer: vtkRenderer;
renderWindow: vtkRenderWindow;
interactor: vtkRenderWindowInteractor;
apiSpecificRenderWindow: vtkRenderWindow;
camera: vtkCamera;
}

/**
* Extract the rendering components from the given renderer.
*
* @param {vtkRenderer} renderer The vtkRenderer instance.
*/
export function extractRenderingComponents(renderer: vtkRenderer): IRenderingComponents;

export interface vtkWidgetManager extends vtkObject {
/**
* The the captureOn value.
* `CaptureOn.MOUSE_MOVE`: captures small region when moving mouse
* `CaptureOn.MOUSE_RELEASE`: captures entire region when mouse button is released
*
* @param {CaptureOn} captureOn
*/
setCaptureOn(captureOn: CaptureOn): boolean;

/**
* Get the captureOn value.
*/
getCaptureOn(): CaptureOn;

/**
* The the view type.
*
* @param {ViewTypes} type
*/
setViewType(type: ViewTypes): boolean;

/**
* Get the view type.
*/
getViewType(): ViewTypes;

/**
* Get the current selection.
*/
getSelections(): vtkSelectionNode[];

/**
* Get all the underlying widgets.
*/
getWidgets(): vtkAbstractWidget[];

/**
* Get the view id.
*/
getViewId(): string;

/**
* Returns true if picking is enabled, false otherwise.
*/
getPickingEnabled(): boolean;

/**
* @deprecated
*/
getUseSvgLayer(): boolean;

/**
* @deprecated
*/
setUseSvgLayer(use: boolean): boolean;

/**
* Enable the picking.
*/
enablePicking(): void;

/**
* Renders all the widgets.
*/
renderWidgets(): void;

/**
* Disable the picking.
*/
disablePicking(): void;

/**
* Set the renderer.
*
* @param {vtkRenderer} renderer
*/
setRenderer(renderer: vtkRenderer): void;

/**
* Register a widget on the widget manager instance.
* Please note that one should link the widget manager to a view before calling this method.
*
* @param {vtkAbstractWidgetFactory} widget The abstract widget factory.
* @param {ViewTypes} [viewType]
* @param {Object} [initialValues]
*/
addWidget(
widget: vtkAbstractWidgetFactory,
viewType?: ViewTypes,
initialValues?: object
): Nullable<vtkAbstractWidget>;

/**
* Unregister all widgets from the widget manager.
*/
removeWidgets(): void;

/**
* Remove a widget from the widget manager.
*
* @param {vtkAbstractWidget | vtkAbstractWidgetFactory} widget The widget to remove
*/
removeWidget(widget: vtkAbstractWidget | vtkAbstractWidgetFactory): void;

/**
* Given x and y parameter, get selected data.
*
* @param {Number} x
* @param {Number} y
*/
getSelectedDataForXY(x: number, y: number): Promise<ISelectedData>;

/**
* @deprecated
*/
updateSelectionFromXY(x: number, y: number): void;

/**
* @deprecated
*/
updateSelectionFromMouseEvent(event: MouseEvent): void;

/**
* The all currently selected data.
*/
getSelectedData(): ISelectedData | {};

/**
* Given the focus to the given widget instance.
*
* @param {vtkAbstractWidget | vtkAbstractWidgetFactory} widget The widget instance which should get the focus.
*/
grabFocus(widget: vtkAbstractWidget | vtkAbstractWidgetFactory): void;

/**
* Release the focus.
*/
releaseFocus(): void;
}

export interface IWidgetManagerInitialValues {
captureOn?: CaptureOn;
viewType?: ViewTypes;
pickingEnabled?: boolean;
/**
* @deprecated
*/
useSvgLayer?: boolean;
}

/**
* Method used to decorate a given object (publicAPI+model) with vtkWidgetManager characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param initialValues (default: {})
*/
export function extend(
publicAPI: object,
model: object,
initialValues?: IWidgetManagerInitialValues
): vtkWidgetManager;

/**
* Method used to create a new instance of vtkCellArray
*
* @param initialValues for pre-setting some of its content
*/
export function newInstance(initialValues?: IWidgetManagerInitialValues): vtkWidgetManager;

export declare const vtkWidgetManager: {
newInstance: typeof newInstance;
extend: typeof extend;
};

export default vtkWidgetManager;
index.js
import { radiansFromDegrees } from 'vtk.js/Sources/Common/Core/Math';
import { FieldAssociations } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
import macro from 'vtk.js/Sources/macros';
import vtkSelectionNode from 'vtk.js/Sources/Common/DataModel/SelectionNode';
import Constants from 'vtk.js/Sources/Widgets/Core/WidgetManager/Constants';
import vtkSVGRepresentation from 'vtk.js/Sources/Widgets/SVG/SVGRepresentation';
import { WIDGET_PRIORITY } from 'vtk.js/Sources/Widgets/Core/AbstractWidget/Constants';
import { diff } from './vdom';

const { ViewTypes, RenderingTypes, CaptureOn } = Constants;
const { vtkErrorMacro, vtkWarningMacro } = macro;
const { createSvgElement, createSvgDomElement } = vtkSVGRepresentation;

let viewIdCount = 1;

// ----------------------------------------------------------------------------
// Helper
// ----------------------------------------------------------------------------

export function extractRenderingComponents(renderer) {
const camera = renderer.getActiveCamera();
const renderWindow = renderer.getRenderWindow();
const interactor = renderWindow.getInteractor();
const apiSpecificRenderWindow = interactor.getView();
return {
renderer,
renderWindow,
interactor,
apiSpecificRenderWindow,
camera,
};
}

// ----------------------------------------------------------------------------

function createSvgRoot(id) {
const svgRoot = createSvgDomElement('svg');
svgRoot.setAttribute('version', '1.1');
svgRoot.setAttribute('baseProfile', 'full');

return svgRoot;
}

// ----------------------------------------------------------------------------
// vtkWidgetManager methods
// ----------------------------------------------------------------------------

function vtkWidgetManager(publicAPI, model) {
if (!model.viewId) {
model.viewId = `view-${viewIdCount++}`;
}
model.classHierarchy.push('vtkWidgetManager');
const propsWeakMap = new WeakMap();
const widgetToSvgMap = new WeakMap();
const svgVTrees = new WeakMap();
const subscriptions = [];

// --------------------------------------------------------------------------
// Internal variable
// --------------------------------------------------------------------------

model.svgRoot = createSvgRoot(model.viewId);

// --------------------------------------------------------------------------
// API internal
// --------------------------------------------------------------------------

function updateWidgetWeakMap(widget) {
const representations = widget.getRepresentations();
for (let i = 0; i < representations.length; i++) {
const representation = representations[i];
const origin = { widget, representation };
const actors = representation.getActors();
for (let j = 0; j < actors.length; j++) {
const actor = actors[j];
propsWeakMap.set(actor, origin);
}
}
}

function getViewWidget(widget) {
return (
widget &&
(widget.isA('vtkAbstractWidget')
? widget
: widget.getWidgetForView({ viewId: model.viewId }))
);
}

// --------------------------------------------------------------------------
// internal SVG API
// --------------------------------------------------------------------------

const pendingSvgRenders = new WeakMap();

function enableSvgLayer() {
const container = model._apiSpecificRenderWindow.getReferenceByName('el');
const canvas = model._apiSpecificRenderWindow.getCanvas();
container.insertBefore(model.svgRoot, canvas.nextSibling);
const containerStyles = window.getComputedStyle(container);
if (containerStyles.position === 'static') {
container.style.position = 'relative';
}
}

function disableSvgLayer() {
const container = model._apiSpecificRenderWindow.getReferenceByName('el');
container.removeChild(model.svgRoot);
}

function removeFromSvgLayer(viewWidget) {
const group = widgetToSvgMap.get(viewWidget);
if (group) {
widgetToSvgMap.delete(viewWidget);
svgVTrees.delete(viewWidget);
model.svgRoot.removeChild(group);
}
}

function setSvgSize() {
const [cwidth, cheight] = model._apiSpecificRenderWindow.getViewportSize(
model._renderer
);
const ratio = window.devicePixelRatio || 1;
const bwidth = String(cwidth / ratio);
const bheight = String(cheight / ratio);
const viewBox = `0 0 ${cwidth} ${cheight}`;

const origWidth = model.svgRoot.getAttribute('width');
const origHeight = model.svgRoot.getAttribute('height');
const origViewBox = model.svgRoot.getAttribute('viewBox');

if (origWidth !== bwidth) {
model.svgRoot.setAttribute('width', bwidth);
}
if (origHeight !== bheight) {
model.svgRoot.setAttribute('height', bheight);
}
if (origViewBox !== viewBox) {
model.svgRoot.setAttribute('viewBox', viewBox);
}
}

function setSvgRootStyle() {
const viewport = model._renderer.getViewport().map((v) => v * 100);
model.svgRoot.setAttribute(
'style',
`position: absolute; left: ${viewport[0]}%; top: ${
100 - viewport[3]
}%; width: ${viewport[2] - viewport[0]}%; height: ${
viewport[3] - viewport[1]
}%;`
);
}

function updateSvg() {
if (model.useSvgLayer) {
for (let i = 0; i < model.widgets.length; i++) {
const widget = model.widgets[i];
const svgReps = widget
.getRepresentations()
.filter((r) => r.isA('vtkSVGRepresentation'));

let pendingContent = [];
if (widget.getVisibility()) {
pendingContent = svgReps
.filter((r) => r.getVisibility())
.map((r) => r.render());
}

const promise = Promise.all(pendingContent);

const renders = pendingSvgRenders.get(widget) || [];
renders.push(promise);
pendingSvgRenders.set(widget, renders);

promise.then((vnodes) => {
let pendingRenders = pendingSvgRenders.get(widget) || [];
const idx = pendingRenders.indexOf(promise);
if (model.deleted || widget.isDeleted() || idx === -1) {
return;
}

// throw away previous renders
pendingRenders = pendingRenders.slice(idx + 1);
pendingSvgRenders.set(widget, pendingRenders);

const oldVTree = svgVTrees.get(widget);
const newVTree = createSvgElement('g');
for (let ni = 0; ni < vnodes.length; ni++) {
newVTree.appendChild(vnodes[ni]);
}

const widgetGroup = widgetToSvgMap.get(widget);
let node = widgetGroup;

const patchFns = diff(oldVTree, newVTree);
for (let j = 0; j < patchFns.length; j++) {
node = patchFns[j](node);
}

if (!widgetGroup && node) {
// add
model.svgRoot.appendChild(node);
widgetToSvgMap.set(widget, node);
} else if (widgetGroup && !node) {
// delete
widgetGroup.remove();
widgetToSvgMap.delete(widget);
}

svgVTrees.set(widget, newVTree);
});
}
}
}

// --------------------------------------------------------------------------
// Widget scaling
// --------------------------------------------------------------------------

function updateDisplayScaleParams() {
const { _apiSpecificRenderWindow, _camera, _renderer } = model;
if (_renderer && _apiSpecificRenderWindow && _camera) {
const [rwW, rwH] = _apiSpecificRenderWindow.getSize();
const [vxmin, vymin, vxmax, vymax] = _renderer.getViewport();
const rendererPixelDims = [rwW * (vxmax - vxmin), rwH * (vymax - vymin)];

const cameraPosition = _camera.getPosition();
const cameraDir = _camera.getDirectionOfProjection();
const isParallel = _camera.getParallelProjection();
const dispHeightFactor = isParallel
? 2 * _camera.getParallelScale()
: 2 * Math.tan(radiansFromDegrees(_camera.getViewAngle()) / 2);

model.widgets.forEach((w) => {
w.getNestedProps().forEach((r) => {
if (r.getScaleInPixels()) {
r.setDisplayScaleParams({
dispHeightFactor,
cameraPosition,
cameraDir,
isParallel,
rendererPixelDims,
});
}
});
});
}
}

// --------------------------------------------------------------------------
// API public
// --------------------------------------------------------------------------

async function updateSelection(callData, fromTouchEvent, callID) {
const { position } = callData;
const { requestCount, selectedState, representation, widget } =
await publicAPI.getSelectedDataForXY(position.x, position.y);

if (requestCount || callID !== model._currentUpdateSelectionCallID) {
// requestCount > 0: Call activate only once
// callID check: drop old calls
return;
}

function activateHandle(w) {
if (fromTouchEvent) {
// release any previous left button interaction
model._interactor.invokeLeftButtonRelease(callData);
}
w.activateHandle({ selectedState, representation });
if (fromTouchEvent) {
// re-trigger the left button press to pick the now-active widget
model._interactor.invokeLeftButtonPress(callData);
}
}

// Default cursor behavior
model._apiSpecificRenderWindow.setCursor(widget ? 'pointer' : 'default');

if (model.widgetInFocus === widget && widget.hasFocus()) {
activateHandle(widget);
// Ken FIXME
model._interactor.render();
model._interactor.render();
} else {
for (let i = 0; i < model.widgets.length; i++) {
const w = model.widgets[i];
if (w === widget && w.getNestedPickable()) {
activateHandle(w);
model.activeWidget = w;
} else {
w.deactivateAllHandles();
}
}
// Ken FIXME
model._interactor.render();
model._interactor.render();
}
}

const handleEvent = async (callData, fromTouchEvent = false) => {
if (!model.isAnimating && model.pickingEnabled) {
const callID = Symbol('UpdateSelection');
model._currentUpdateSelectionCallID = callID;
await updateSelection(callData, fromTouchEvent, callID);
}
};

function updateWidgetForRender(w) {
w.updateRepresentationForRender(model.renderingType);
}

function renderPickingBuffer() {
model.renderingType = RenderingTypes.PICKING_BUFFER;
model.widgets.forEach(updateWidgetForRender);
}

function renderFrontBuffer() {
model.renderingType = RenderingTypes.FRONT_BUFFER;
model.widgets.forEach(updateWidgetForRender);
}

async function captureBuffers(x1, y1, x2, y2) {
if (model._captureInProgress) {
return;
}
model._captureInProgress = true;
renderPickingBuffer();

model._capturedBuffers = null;
model._capturedBuffers = await model._selector.getSourceDataAsync(
model._renderer,
x1,
y1,
x2,
y2
);
model.previousSelectedData = null;
renderFrontBuffer();
model._captureInProgress = false;
}

publicAPI.enablePicking = () => {
model.pickingEnabled = true;
publicAPI.renderWidgets();
};

publicAPI.renderWidgets = () => {
if (model.pickingEnabled && model.captureOn === CaptureOn.MOUSE_RELEASE) {
const [w, h] = model._apiSpecificRenderWindow.getSize();
captureBuffers(0, 0, w, h);
}

renderFrontBuffer();
publicAPI.modified();
};

publicAPI.disablePicking = () => {
model.pickingEnabled = false;
};

publicAPI.setRenderer = (renderer) => {
const renderingComponents = extractRenderingComponents(renderer);
Object.assign(model, renderingComponents);
macro.moveToProtected({}, model, Object.keys(renderingComponents));
while (subscriptions.length) {
subscriptions.pop().unsubscribe();
}

model._selector = model._apiSpecificRenderWindow.createSelector();
model._selector.setFieldAssociation(
FieldAssociations.FIELD_ASSOCIATION_POINTS
);

subscriptions.push(model._interactor.onRenderEvent(updateSvg));

subscriptions.push(renderer.onModified(setSvgRootStyle));
setSvgRootStyle();

subscriptions.push(model._apiSpecificRenderWindow.onModified(setSvgSize));
setSvgSize();

subscriptions.push(
model._apiSpecificRenderWindow.onModified(updateDisplayScaleParams)
);
subscriptions.push(model._camera.onModified(updateDisplayScaleParams));
updateDisplayScaleParams();

subscriptions.push(
model._interactor.onStartAnimation(() => {
model.isAnimating = true;
})
);
subscriptions.push(
model._interactor.onEndAnimation(() => {
model.isAnimating = false;
publicAPI.renderWidgets();
})
);

subscriptions.push(
model._interactor.onMouseMove((eventData) => {
handleEvent(eventData);
return macro.VOID;
})
);

// must be handled after widgets, hence the given priority.
subscriptions.push(
model._interactor.onLeftButtonPress((eventData) => {
const { deviceType } = eventData;
const touchEvent = deviceType === 'touch' || deviceType === 'pen';
// only try selection if the left button press is from touch.
if (touchEvent) {
handleEvent(eventData, touchEvent);
}
return macro.VOID;
}, WIDGET_PRIORITY / 2)
);

publicAPI.modified();

if (model.pickingEnabled) {
publicAPI.enablePicking();
}

if (model.useSvgLayer) {
enableSvgLayer();
}
};

function addWidgetInternal(viewWidget) {
viewWidget.setWidgetManager(publicAPI);
updateWidgetWeakMap(viewWidget);
updateDisplayScaleParams();

// Register to renderer
model._renderer.addActor(viewWidget);
}

publicAPI.addWidget = (widget, viewType, initialValues) => {
if (!model._renderer) {
vtkErrorMacro(
'Widget manager MUST BE link to a view before registering widgets'
);
return null;
}
const { viewId, _renderer } = model;
const w = widget.getWidgetForView({
viewId,
renderer: _renderer,
viewType: viewType || ViewTypes.DEFAULT,
initialValues,
});

if (w != null && model.widgets.indexOf(w) === -1) {
model.widgets.push(w);
addWidgetInternal(w);
publicAPI.modified();
}

return w;
};

function removeWidgetInternal(viewWidget) {
model._renderer.removeActor(viewWidget);
removeFromSvgLayer(viewWidget);
viewWidget.delete();
}

function onWidgetRemoved() {
model._renderer.getRenderWindow().getInteractor().render();
publicAPI.renderWidgets();
}

publicAPI.removeWidgets = () => {
model.widgets.forEach(removeWidgetInternal);
model.widgets = [];
model.widgetInFocus = null;
onWidgetRemoved();
};

publicAPI.removeWidget = (widget) => {
const viewWidget = getViewWidget(widget);
const index = model.widgets.indexOf(viewWidget);
if (index !== -1) {
model.widgets.splice(index, 1);

const isWidgetInFocus = model.widgetInFocus === viewWidget;
if (isWidgetInFocus) {
publicAPI.releaseFocus();
}

removeWidgetInternal(viewWidget);
onWidgetRemoved();
}
};

publicAPI.getSelectedDataForXY = async (x, y) => {
model.selections = null;
if (model.pickingEnabled) {
// First pick SVG representation
for (let i = 0; i < model.widgets.length; ++i) {
const widget = model.widgets[i];
const hoveredSVGReps = widget
.getRepresentations()
.filter((r) => r.isA('vtkSVGRepresentation') && r.getHover() != null);
if (hoveredSVGReps.length) {
const selection = vtkSelectionNode.newInstance();
selection.getProperties().compositeID = hoveredSVGReps[0].getHover();
selection.getProperties().widget = widget;
selection.getProperties().representation = hoveredSVGReps[0];
model.selections = [selection];
return publicAPI.getSelectedData();
}
}

// do we require a new capture?
if (!model._capturedBuffers || model.captureOn === CaptureOn.MOUSE_MOVE) {
await captureBuffers(x, y, x, y);
}

// or do we need a pixel that is outside the last capture?
const capturedRegion = model._capturedBuffers.area;
if (
x < capturedRegion[0] ||
x > capturedRegion[2] ||
y < capturedRegion[1] ||
y > capturedRegion[3]
) {
await captureBuffers(x, y, x, y);
}

model.selections = model._capturedBuffers.generateSelection(x, y, x, y);
}
return publicAPI.getSelectedData();
};

publicAPI.updateSelectionFromXY = (x, y) => {
vtkWarningMacro(
'updateSelectionFromXY is deprecated, please use getSelectedDataForXY'
);
if (model.pickingEnabled) {
// First pick SVG representation
for (let i = 0; i < model.widgets.length; ++i) {
const widget = model.widgets[i];
const hoveredSVGReps = widget
.getRepresentations()
.filter((r) => r.isA('vtkSVGRepresentation') && r.getHover() != null);
if (hoveredSVGReps.length) {
const selection = vtkSelectionNode.newInstance();
selection.getProperties().compositeID = hoveredSVGReps[0].getHover();
selection.getProperties().widget = widget;
selection.getProperties().representation = hoveredSVGReps[0];
model.selections = [selection];
return;
}
}

// Then pick regular representations.
if (model.captureOn === CaptureOn.MOUSE_MOVE) {
captureBuffers(x, y, x, y);
}
}
};

publicAPI.updateSelectionFromMouseEvent = (event) => {
vtkWarningMacro(
'updateSelectionFromMouseEvent is deprecated, please use getSelectedDataForXY'
);
const { pageX, pageY } = event;
const { top, left, height } = model._apiSpecificRenderWindow
.getCanvas()
.getBoundingClientRect();
const x = pageX - left;
const y = height - (pageY - top);
publicAPI.updateSelectionFromXY(x, y);
};

publicAPI.getSelectedData = () => {
if (!model.selections || !model.selections.length) {
model.previousSelectedData = null;
return {};
}
const { propID, compositeID, prop } = model.selections[0].getProperties();
let { widget, representation } = model.selections[0].getProperties();
// prop is undefined for SVG representation, widget is undefined for handle
// representation.
if (
model.previousSelectedData &&
model.previousSelectedData.prop === prop &&
model.previousSelectedData.widget === widget &&
model.previousSelectedData.compositeID === compositeID
) {
model.previousSelectedData.requestCount++;
return model.previousSelectedData;
}

if (propsWeakMap.has(prop)) {
const props = propsWeakMap.get(prop);
widget = props.widget;
representation = props.representation;
}

if (widget && representation) {
const selectedState = representation.getSelectedState(prop, compositeID);
model.previousSelectedData = {
requestCount: 0,
propID,
compositeID,
prop,
widget,
representation,
selectedState,
};
return model.previousSelectedData;
}
model.previousSelectedData = null;
return {};
};

publicAPI.grabFocus = (widget) => {
const viewWidget = getViewWidget(widget);
if (model.widgetInFocus && model.widgetInFocus !== viewWidget) {
model.widgetInFocus.loseFocus();
}
model.widgetInFocus = viewWidget;
if (model.widgetInFocus) {
model.widgetInFocus.grabFocus();
}
};

publicAPI.releaseFocus = () => publicAPI.grabFocus(null);

publicAPI.setUseSvgLayer = (useSvgLayer) => {
if (useSvgLayer !== model.useSvgLayer) {
model.useSvgLayer = useSvgLayer;

if (model._renderer) {
if (useSvgLayer) {
enableSvgLayer();
// force a render so svg widgets can be drawn
updateSvg();
} else {
disableSvgLayer();
}
}

return true;
}
return false;
};

const superDelete = publicAPI.delete;
publicAPI.delete = () => {
while (subscriptions.length) {
subscriptions.pop().unsubscribe();
}
superDelete();
};
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
// _camera: null,
// _selector: null,
// _currentUpdateSelectionCallID: null,
viewId: null,
widgets: [],
renderer: null,
viewType: ViewTypes.DEFAULT,
isAnimating: false,
pickingEnabled: true,
selections: null,
previousSelectedData: null,
widgetInFocus: null,
useSvgLayer: true,
captureOn: CaptureOn.MOUSE_MOVE,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

macro.obj(publicAPI, model);
macro.setGet(publicAPI, model, [
'captureOn',
{ type: 'enum', name: 'viewType', enum: ViewTypes },
]);
macro.get(publicAPI, model, [
'selections',
'widgets',
'viewId',
'pickingEnabled',
'useSvgLayer',
]);

// Object specific methods
vtkWidgetManager(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkWidgetManager');

// ----------------------------------------------------------------------------

export default { newInstance, extend, Constants };
vdom.js
const SVG_XMLNS = 'http://www.w3.org/2000/svg';

function attrDelta(oldObj, newObj) {
const set = [];
const remove = [];
const oldKeysArray = Object.keys(oldObj);
const newKeysArray = Object.keys(newObj);
const oldKeys = new Set(oldKeysArray);
const newKeys = new Set(newKeysArray);
for (let i = 0; i < oldKeysArray.length; i++) {
const key = oldKeysArray[i];
if (newKeys.has(key)) {
if (oldObj[key] !== newObj[key]) {
set.push([key, newObj[key]]);
}
} else {
remove.push(key);
}
}
for (let i = 0; i < newKeysArray.length; i++) {
const key = newKeysArray[i];
if (!oldKeys.has(key)) {
set.push([key, newObj[key]]);
}
}

return [set, remove];
}

export function render(vnode) {
const node = document.createElementNS(SVG_XMLNS, vnode.name);

const keys = Object.keys(vnode.attrs);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
node.setAttribute(key, vnode.attrs[key]);
}
// TODO: support removing event listener (e.g. use snabbdom)
Object.keys(vnode.eventListeners).forEach((key) => {
node.addEventListener(key, vnode.eventListeners[key]);
});

if (vnode.textContent) {
node.textContent = vnode.textContent;
} else {
for (let i = 0; i < vnode.children.length; i++) {
node.appendChild(render(vnode.children[i]));
}
}

return node;
}

/**
* Returns a set of patch functions to be applied to a document node.
*
* Patch functions must return the effective result node.
*/
export function diff(oldVTree, newVTree) {
if (newVTree.textContent !== null && newVTree.children.length) {
throw new Error('Tree cannot have both children and textContent!');
}

if (!oldVTree) {
return [() => render(newVTree)];
}

if (!newVTree) {
return [(node) => node.remove()];
}

if (oldVTree.name !== newVTree.name) {
return [
(node) => {
const newNode = render(newVTree);
node.replaceWith(newNode);
return newNode;
},
];
}

const patchFns = [];

const [attrsSet, attrsRemove] = attrDelta(oldVTree.attrs, newVTree.attrs);
if (attrsSet.length || attrsRemove.length) {
patchFns.push((node) => {
for (let i = 0; i < attrsSet.length; i++) {
const [name, value] = attrsSet[i];
node.setAttribute(name, value);
}
for (let i = 0; i < attrsRemove.length; i++) {
const name = attrsRemove[i];
node.removeAttribute(name);
}
return node;
});
}

if (
oldVTree.textContent !== newVTree.textContent &&
newVTree.textContent !== null
) {
patchFns.push((node) => {
node.textContent = newVTree.textContent;
return node;
});
}

if (newVTree.textContent === null) {
const min = Math.min(oldVTree.children.length, newVTree.children.length);
for (let i = 0; i < min; i++) {
const childPatches = diff(oldVTree.children[i], newVTree.children[i]);
patchFns.push((node) => {
for (let p = 0; p < childPatches.length; p++) {
childPatches[p](node.children[i]);
}
return node;
});
}
if (oldVTree.children.length < newVTree.children.length) {
for (let i = min; i < newVTree.children.length; i++) {
patchFns.push((node) => {
node.appendChild(render(newVTree.children[i]));
return node;
});
}
} else {
// always delete nodes in reverse
for (let i = oldVTree.children.length - 1; i >= min; i--) {
patchFns.push((node) => {
node.children[i].remove();
return node;
});
}
}
}

return patchFns;
}