From 9f5e7ab91a2f46719a64be0ef702bb6b1e1caa67 Mon Sep 17 00:00:00 2001 From: JoshuaRiewesell <40123349+JoshuaRiewesell@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:46:43 +0200 Subject: [PATCH] add zoom to Patient Editor --- frontend/package-lock.json | 6 ++ frontend/package.json | 1 + .../src/components/widgets/ZoomControl.vue | 56 +++++++++++++ frontend/src/rete/editor.ts | 7 +- frontend/src/rete/zoom.ts | 78 +++++++++++++++++++ 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/widgets/ZoomControl.vue create mode 100644 frontend/src/rete/zoom.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 87ce2265..ce6351af 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@formkit/vue": "^1.6.5", "@rushstack/eslint-patch": "^1.7.2", "@types/three": "^0.156.0", + "animejs": "^3.2.2", "ant-design-vue": "^4.2.3", "antd": "5.3.2", "elkjs": "^0.8.2", @@ -2234,6 +2235,11 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/animejs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.2.tgz", + "integrity": "sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ==" + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 83cbe942..1c226328 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "@formkit/vue": "^1.6.5", "@rushstack/eslint-patch": "^1.7.2", "@types/three": "^0.156.0", + "animejs": "^3.2.2", "ant-design-vue": "^4.2.3", "antd": "5.3.2", "elkjs": "^0.8.2", diff --git a/frontend/src/components/widgets/ZoomControl.vue b/frontend/src/components/widgets/ZoomControl.vue new file mode 100644 index 00000000..bb01dc59 --- /dev/null +++ b/frontend/src/components/widgets/ZoomControl.vue @@ -0,0 +1,56 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/rete/editor.ts b/frontend/src/rete/editor.ts index a95260f0..6a47d12c 100644 --- a/frontend/src/rete/editor.ts +++ b/frontend/src/rete/editor.ts @@ -1,6 +1,6 @@ import { ref, watch } from 'vue' import { ClassicPreset as Classic, NodeEditor } from 'rete' -import { AreaExtensions, AreaPlugin } from 'rete-area-plugin' +import { AreaExtensions, AreaPlugin, Zoom } from 'rete-area-plugin' import { ConnectionPlugin } from 'rete-connection-plugin' import { VuePlugin, Presets as VuePresets } from 'rete-vue-plugin' import { @@ -18,6 +18,7 @@ import CustomDropdown from './customization/CustomDropdown.vue' import { DropdownControl } from './dropdown' import { StateNode, InitialStateNode, InputNode, OutputNode } from './nodes/index' import CircleNode from './customization/CircleNode.vue' +import { SmoothZoom } from './zoom' export const editorMode = ref("patient") @@ -83,7 +84,9 @@ export async function createEditor(container: HTMLElement, data: any) { area.use(connection) area.use(contextMenu) - area.area.setZoomHandler(null) + area.area.setZoomHandler( + new SmoothZoom(0.5, 200, "cubicBezier(.45,.91,.49,.98)", area) + ) area.addPipe(context => { if (context.type === 'nodepicked') { diff --git a/frontend/src/rete/zoom.ts b/frontend/src/rete/zoom.ts new file mode 100644 index 00000000..9039ad70 --- /dev/null +++ b/frontend/src/rete/zoom.ts @@ -0,0 +1,78 @@ +import { AreaPlugin, Zoom } from "rete-area-plugin" +import anime from "animejs/lib/anime.es.js" + +function screenToArea(x: number, y: number, t: any) { + const { x: tx, y: ty, k } = t + + return { x: (x - tx) / k, y: (y - ty) / k } +} + +function areaToScreen(x: number, y: number, t: any) { + const { x: tx, y: ty, k } = t + + return { x: x * k + tx, y: y * k + ty } +} + +export class SmoothZoom extends Zoom { + animation?: any + + constructor( + intensity: number, + private duration: number, + private easing: string, + private area: AreaPlugin + ) { + super(intensity) + } + + wheel = (e: WheelEvent) => { + e.preventDefault() + + const isNegative = e.deltaY < 0 + const delta = isNegative ? this.intensity : -this.intensity + const { left, top } = this.container.getBoundingClientRect() + const ox = e.clientX - left + const oy = e.clientY - top + + const coords = screenToArea(ox, oy, this.area.area.transform) + + const { k } = this.area.area.transform + const targets = { + zoom: k + } + const { duration, easing } = this + + if (this.animation) { + this.animation.reset() + } + this.animation = anime({ + targets, + x: coords.x, + y: coords.y, + zoom: k * (1 + delta), + duration, + easing, + update: () => { + const currentTransform = this.area.area.transform + + const coordinates = areaToScreen(coords.x, coords.y, currentTransform) + + const nextX = coordinates.x - coords.x * targets.zoom + const nextY = coordinates.y - coords.y * targets.zoom + + this.area.area.zoom( + targets.zoom, + nextX - currentTransform.x, + nextY - currentTransform.y + ) + } + }) + } + + destroy() { + super.destroy() + if (this.animation) { + this.animation.reset() + } + } +}