| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 | import {  init,  classModule,  propsModule,  styleModule,  eventListenersModule,  h,  attributesModule,} from 'snabbdom';const patch = init([  attributesModule,  classModule,  propsModule,  styleModule,  eventListenersModule,]);function mountDummySVGContainer(canvas) {  const container = canvas.parentElement;  const dummy = document.createElement('div');  container.insertBefore(dummy, canvas.nextSibling);  const containerStyles = window.getComputedStyle(container);  if (containerStyles.position === 'static') {    container.style.position = 'relative';  }  return dummy;}export const VerticalTextAlignment = {  TOP: 1,  MIDDLE: 2,  BOTTOM: 3,};/** * Computes the relative dy values around 0 for multiline text. * * @param nLines * @param fontSize * @returns a list of vertical offsets (from a zero origin) for placing multiline text. */export function multiLineTextCalculator(  nLines,  fontSize,  alignment = VerticalTextAlignment.BOTTOM) {  const dys = [];  for (let i = 0; i < nLines; i++) {    switch (alignment) {      case VerticalTextAlignment.TOP:        dys.push(fontSize * (i + 1));        break;      case VerticalTextAlignment.MIDDLE:        dys.push(-fontSize * (0.5 * nLines - i - 1));        break;      case VerticalTextAlignment.BOTTOM:      default:        dys.push(-fontSize * (nLines - i - 1));    }  }  return dys;}/** * Automatically updates an SVG rendering whenever a widget's state is updated. * * This update is done in two phases: * 1. mapState(widgetState) takes the widget state and transforms it into an intermediate data representation. * 2. render(data, h) takes the intermediate data representation and a createElement `h` function, and returns *    an SVG rendering of the state encoded in `data`. * * See snabbdom's documentation for how to use the `h` function passed to `render()`. * * @param renderer the widget manager's renderer * @param widgetState the widget state * @param mapState (object parameter) transforms the given widget's state into an intermediate data representation to be passed to render(). * @param render (object parameter) returns the SVG representation given the data from mapState() and snabbdom's h render function. */export function bindSVGRepresentation(  renderer,  widgetState,  { mapState, render }) {  const view = renderer.getRenderWindow().getViews()[0];  const canvas = view.getCanvas();  const getSize = () => {    const [width, height] = view.getSize();    const ratio = window.devicePixelRatio || 1;    return {      width: width / ratio,      height: height / ratio,      viewBox: `0 0 ${width} ${height}`,    };  };  const renderState = (state) => {    const repData = mapState(state, {      size: view.getSize(),    });    const rendered = render(repData, h);    return h(      'svg',      {        attrs: getSize(),        style: {          position: 'absolute',          top: '0',          left: '0',          width: '100%',          height: '100%',          // deny pointer events by default          'pointer-events': 'none',        },      },      Array.isArray(rendered) ? rendered : [rendered]    );  };  const dummy = mountDummySVGContainer(canvas);  let vnode = patch(dummy, renderState(widgetState));  const updateVNode = () => {    vnode = patch(vnode, renderState(widgetState));  };  const stateSub = widgetState.onModified(() => updateVNode());  const cameraSub = renderer.getActiveCamera().onModified(() => updateVNode());  const observer = new ResizeObserver(() => updateVNode());  observer.observe(canvas);  return () => {    stateSub.unsubscribe();    cameraSub.unsubscribe();    observer.disconnect();    patch(vnode, h('!')); // unmount hack    vnode = null;  };}/** * Applies a set of default interaction handling behavior. * * Typically, firing pointerenter means that the pointer is * "hovering", meaning the associated widget state should * be selected. (This is on the user to do this step.) * Accordingly, pointerleave means no more hovering. * * However, vtk.js captures the pointer on pointerdown, * which means that clicking on an SVG element will result * in a pointerleave being triggered, deselecting the * widget state. * * The abridged sequence of events is as follows: * 1. pointerenter on the SVG element (mouse moves over SVG handle) * 2. pointerdown on the SVG element (left button press) * 2. pointerdown on the vtk.js canvas (left button press) * 3. pointer captured on the vtk.js canvas (left button press) * 4. pointerleave on the SVG element as soon as the mouse is moved, *    since the capture target is now the canvas. * 5. pointerenter on the SVG element when the mouse/pointer is released. * * To workaround this issue, we conditionally fire the user's * pointerleave listener only when we are "locked", which means * we saw a pointerdown and so the vtk.js canvas is capturing * the current pointer. */function applyDefaultInteractions(userListeners) {  let locked = false;  return {    ...userListeners,    pointerdown(ev) {      locked = true;      return userListeners?.pointerdown?.(ev);    },    pointerenter(ev) {      if (locked) {        locked = false;      }      return userListeners?.pointerenter?.(ev);    },    pointerleave(ev) {      if (!locked) {        return userListeners?.pointerleave?.(ev);      }      return undefined;    },  };}/** * Requires the snabbdom eventlisteners and style modules. * @param vnode * @returns */export function makeListenableSVGNode(vnode) {  // allow pointer events on this vnode  vnode.data.style = {    ...vnode.data.style,    'pointer-events': 'all',  };  vnode.data.on = applyDefaultInteractions(vnode.data.on);  return vnode;}export default { bindSVGRepresentation, multiLineTextCalculator };
 |