UNPKG

@kieler/klighd-core

Version:

Core KLighD diagram visualization with Sprotty

245 lines (221 loc) 9.05 kB
/* * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient * * http://rtsys.informatik.uni-kiel.de/kieler * * Copyright 2022-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 */ // We follow Sprotty's way of redeclaring the interface and its create function, so disable this lint check for this file. /* eslint-disable no-redeclare */ import { KNode } from '@kieler/klighd-interactive/lib/constraint-classes' import { inject, injectable, postConstruct } from 'inversify' import { ActionHandlerRegistry, IActionHandler, IActionHandlerInitializer, ICommand, MouseListener, SModelElementImpl, SetUIExtensionVisibilityAction, } from 'sprotty' import { Action, CenterAction, SetModelAction, UpdateModelAction } from 'sprotty-protocol' import { SendModelContextAction } from '../actions/actions' import { DISymbol } from '../di.symbols' import { OptionsRegistry } from '../options/options-registry' import { RenderOptionsRegistry } from '../options/render-options-registry' import { SynthesesRegistry } from '../syntheses/syntheses-registry' import { ProxyView } from './proxy-view' import { ProxyViewCapProxyToParent, ProxyViewCapScaleToOne, ProxyViewCategory, ProxyViewClusterTransparent, ProxyViewClusteringCascading, ProxyViewClusteringSweepLine, ProxyViewDebugCategory, ProxyViewDecreaseProxyClutter, ProxyViewDrawEdgesAboveNodes, ProxyViewEdgesToOffScreenPoint, ProxyViewEnableEdgeProxies, ProxyViewEnableSegmentProxies, ProxyViewEnabled, ProxyViewHighlightSelected, ProxyViewInteractiveProxies, ProxyViewOpacityBySelected, ProxyViewOriginalNodeScale, ProxyViewShowProxiesEarly, ProxyViewShowProxiesEarlyNumber, ProxyViewShowProxiesImmediately, ProxyViewSimpleAlongBorderRouting, ProxyViewSize, ProxyViewStackingOrderByDistance, ProxyViewStackingOrderByOpacity, ProxyViewStackingOrderBySelected, ProxyViewTitleScaling, ProxyViewTransparentEdges, ProxyViewUseDetailLevel, ProxyViewUseSynthesisProxyRendering, } from './proxy-view-options' import { getNodeId, isProxyRendering } from './proxy-view-util' /* global MouseEvent, SVGElement */ /** * Wrapper action around {@link SetUIExtensionVisibilityAction} which shows the proxy. * Otherwise the proxy-view would be invisible. */ export type ShowProxyViewAction = SetUIExtensionVisibilityAction export namespace ShowProxyViewAction { export function create(): ShowProxyViewAction { return SetUIExtensionVisibilityAction.create({ extensionId: ProxyView.ID, visible: true, }) } } /** An action containing the {@link ProxyView}. */ // Sent from the proxy-view to the action handler to avoid stackoverflows export interface SendProxyViewAction extends Action { kind: typeof SendProxyViewAction.KIND proxyView: ProxyView } export namespace SendProxyViewAction { export const KIND = 'sendProxyViewAction' export function create(proxyView: ProxyView): SendProxyViewAction { return { kind: KIND, proxyView, } } } /** Handles all actions and mouse events regarding the {@link ProxyView}. */ @injectable() export class ProxyViewActionHandler extends MouseListener implements IActionHandler, IActionHandlerInitializer { /** The proxy-view. */ private proxyView: ProxyView // Sidebar registries @inject(DISymbol.SynthesesRegistry) private synthesesRegistry: SynthesesRegistry @inject(DISymbol.RenderOptionsRegistry) private renderOptionsRegistry: RenderOptionsRegistry @inject(DISymbol.OptionsRegistry) private optionsRegistry: OptionsRegistry /** Whether the proxy-view was registered in the registries' onchange() method. Prevents registering multiple times. */ private onChangeRegistered: boolean mouseMoved = false /// / Mouse events //// mouseDown(_target: SModelElementImpl, event: MouseEvent): (Action | Promise<Action>)[] { this.mouseMoved = false if (this.proxyView) { this.proxyView.setMouseDown(event) } return [] } mouseMove(): (Action | Promise<Action>)[] { this.mouseMoved = true return [] } mouseUp(target: SModelElementImpl, event: MouseEvent): (Action | Promise<Action>)[] { let action: Action | undefined if ( !this.mouseMoved && target instanceof KNode && event.target instanceof SVGElement && isProxyRendering(event.target, target.id) ) { // TODO: Use the FitToScreenAction if the node is larger than the canvas. // Center on node when proxy is clicked // if (target.bounds.width > canvas.width || target.bounds.height > canvas.height) { // // Node is larger than canvas, zoom out so the node is fully on-screen // action = FitToScreenAction.create([getNodeId(target.id)], { animate: true, padding: 10 }) // } else { // // Retain the zoom, e.g. don't zoom in action = CenterAction.create([getNodeId(target.id)], { animate: true, retainZoom: true }) // } } if (this.proxyView) { this.proxyView.setMouseUp() } return action ? [action] : [] } /// / Actions //// @postConstruct() init(): void { // Register options // Proxy-view this.renderOptionsRegistry.registerAll( ProxyViewCategory, ProxyViewEnabled, ProxyViewSize, ProxyViewDecreaseProxyClutter, ProxyViewEnableEdgeProxies, ProxyViewDrawEdgesAboveNodes, ProxyViewEnableSegmentProxies, ProxyViewInteractiveProxies, ProxyViewTitleScaling ) // Proxy-view debug this.renderOptionsRegistry.registerAll( ProxyViewDebugCategory, ProxyViewHighlightSelected, ProxyViewOpacityBySelected, ProxyViewUseSynthesisProxyRendering, ProxyViewSimpleAlongBorderRouting, ProxyViewCapProxyToParent, ProxyViewShowProxiesImmediately, ProxyViewShowProxiesEarly, ProxyViewShowProxiesEarlyNumber, ProxyViewStackingOrderByDistance, ProxyViewStackingOrderByOpacity, ProxyViewStackingOrderBySelected, ProxyViewUseDetailLevel, ProxyViewEdgesToOffScreenPoint, ProxyViewTransparentEdges, ProxyViewOriginalNodeScale, ProxyViewCapScaleToOne, ProxyViewClusterTransparent, ProxyViewClusteringCascading, ProxyViewClusteringSweepLine ) } handle(action: Action): void | Action | ICommand { if (action.kind === SendProxyViewAction.KIND) { // Save the proxy-view instance const sPVAction = action as SendProxyViewAction this.proxyView = sPVAction.proxyView // Register to receive updates on registry changes // Do this here instead of in init() to ensure proxyView isn't undefined if (!this.onChangeRegistered) { // Make sure the rendering cache is cleared when the renderings change this.synthesesRegistry.onChange(() => this.proxyView.reset()) this.optionsRegistry.onChange(() => this.proxyView.clearRenderings()) // Make sure to be notified when rendering options are changed this.renderOptionsRegistry.onChange(() => this.proxyView.updateOptions(this.renderOptionsRegistry)) // Initialize the proxy view with all current options. this.proxyView.updateOptions(this.renderOptionsRegistry) this.onChangeRegistered = true } } else if (this.proxyView) { if (action.kind === SendModelContextAction.KIND) { // Redirect the content to the proxy-view const { model, context } = action as SendModelContextAction this.proxyView.update(model, context) } else if ([SetModelAction.KIND, UpdateModelAction.KIND].includes(action.kind)) { // Layout has changed, new model this.proxyView.reset() } } } initialize(registry: ActionHandlerRegistry): void { // Register as a handler to receive the actions registry.register(SendModelContextAction.KIND, this) registry.register(SendProxyViewAction.KIND, this) // Layout changes registry.register(SetModelAction.KIND, this) registry.register(UpdateModelAction.KIND, this) } }