diff --git a/documentation/AdvancedEditingShortcuts.md b/documentation/AdvancedEditingShortcuts.md index d5d37362..204d4129 100644 --- a/documentation/AdvancedEditingShortcuts.md +++ b/documentation/AdvancedEditingShortcuts.md @@ -10,18 +10,23 @@ easier to manage and edit complex network structures. ### Short-cuts -| Keyboard | description | -|:----------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| -| | | -| `delete` | Delete nodes, comments, and selected trainruns. | -| `ctrl`+`d` / `insert` | Duplicate nodes, comments, and selected trainruns (if multiple nodes are selected, trains including nodes will be duplicated). | -| `insert` | Add new nodes under the mouse on the drawing area. | -| `escape` | Select trainruns / In multi-node move mode, all selected nodes will be unselected. | +| Keyboard | description | +|:----------------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| +| | | +| `delete` | Delete nodes, comments, and selected trainruns. | +| `ctrl`+`d` / `insert` | Duplicate nodes, comments, and selected trainruns (if multiple nodes are selected, trains including nodes will be duplicated). | +| `insert` | Add new nodes under the mouse on the drawing area. | +| `escape` | Select trainruns / In multi-node move mode, all selected nodes will be unselected. | | `shift` + `left mouse button` pressed and move (or alternatively with the `right mouse button` pressed and move) | Multi-select nodes and notes for subsequent moving or delete all selected elements by using `Delete`. | -| `ctrl`+`z` | Undo - Reverse the previous action. | -| `ctrl`+`c` | Copy the currently visible trainruns. The copied content remains in the browser's memory/cache and can be pasted into different variants (even after closing the browser, the data remains saved). | -| `ctrl`+`v` | Paste the copied trainruns. | -| `ctrl`+`a` | Select all. | +| `ctrl`+`z` | Undo - Reverse the previous action. | +| `ctrl`+`c` | Copy the currently visible trainruns. The copied content remains in the browser's memory/cache and can be pasted into different variants (even after closing the browser, the data remains saved). | +| `ctrl`+`v` | Paste the copied trainruns. | +| `ctrl`+`a` | Select all. | +| | | +| `Arrow left` | Align all selected nodes - left. | +| `Arrow up` | Align all selected nodes - top. | +| `Arrow right` | Align all selected nodes - right. | +| `Arrow down` | Align all selected nodes - bottom. | --- ### Scale Netzgrafik diff --git a/src/app/services/util/position.transformation.service.spec.ts b/src/app/services/util/position.transformation.service.spec.ts new file mode 100644 index 00000000..458941bc --- /dev/null +++ b/src/app/services/util/position.transformation.service.spec.ts @@ -0,0 +1,197 @@ +import {DataService} from "../../services/data/data.service"; +import {NodeService} from "../../services/data/node.service"; +import {ResourceService} from "../../services/data/resource.service"; +import {TrainrunService} from "../../services/data/trainrun.service"; +import {TrainrunSectionService} from "../../services/data/trainrunsection.service"; +import {StammdatenService} from "../../services/data/stammdaten.service"; +import {NoteService} from "../../services/data/note.service"; +import {Node} from "../../models/node.model"; +import {TrainrunSection} from "../../models/trainrunsection.model"; +import {LabelGroupService} from "../../services/data/labelgroup.service"; +import {LabelService} from "../../services/data/label.serivce"; +import {NetzgrafikColoringService} from "../../services/data/netzgrafikColoring.service"; +import {LogService} from "../../logger/log.service"; +import {LogPublishersService} from "../../logger/log.publishers.service"; +import {FilterService} from "../../services/ui/filter.service"; +import {UiInteractionService} from "../../services/ui/ui.interaction.service"; +import {LoadPerlenketteService} from "../../perlenkette/service/load-perlenkette.service"; +import {NetzgrafikUnitTesting} from "../../../integration-testing/netzgrafik.unit.testing"; +import {ViewportCullService} from "../../services/ui/viewport.cull.service"; +import {PositionTransformationService} from "./position.transformation.service"; +import {Vec2D} from "../../utils/vec2D"; + +describe("PositionTransformationService", () => { + let dataService: DataService; + let nodeService: NodeService; + let resourceService: ResourceService; + let trainrunService: TrainrunService; + let trainrunSectionService: TrainrunSectionService; + let stammdatenService: StammdatenService; + let noteService: NoteService; + let nodes: Node[] = null; + let trainrunSections: TrainrunSection[] = null; + let logService: LogService = null; + let logPublishersService: LogPublishersService = null; + let labelGroupService: LabelGroupService = null; + let labelService: LabelService = null; + let filterService: FilterService = null; + let netzgrafikColoringService: NetzgrafikColoringService = null; + let uiInteractionService: UiInteractionService = null; + let loadPerlenketteService: LoadPerlenketteService = null; + let positionTransformationService: PositionTransformationService = null; + + beforeEach(() => { + stammdatenService = new StammdatenService(); + resourceService = new ResourceService(); + logPublishersService = new LogPublishersService(); + logService = new LogService(logPublishersService); + labelGroupService = new LabelGroupService(logService); + labelService = new LabelService(logService, labelGroupService); + filterService = new FilterService(labelService, labelGroupService); + trainrunService = new TrainrunService( + logService, + labelService, + filterService, + ); + trainrunSectionService = new TrainrunSectionService( + logService, + trainrunService, + filterService, + ); + nodeService = new NodeService( + logService, + resourceService, + trainrunService, + trainrunSectionService, + labelService, + filterService, + ); + noteService = new NoteService(logService, labelService, filterService); + netzgrafikColoringService = new NetzgrafikColoringService(logService); + dataService = new DataService( + resourceService, + nodeService, + trainrunSectionService, + trainrunService, + stammdatenService, + noteService, + labelService, + labelGroupService, + filterService, + netzgrafikColoringService, + ); + nodeService.nodes.subscribe((updatesNodes) => (nodes = updatesNodes)); + trainrunSectionService.trainrunSections.subscribe( + (updatesTrainrunSections) => (trainrunSections = updatesTrainrunSections), + ); + + loadPerlenketteService = new LoadPerlenketteService( + trainrunService, + trainrunSectionService, + nodeService, + filterService + ); + + uiInteractionService = new UiInteractionService( + filterService, + nodeService, + noteService, + stammdatenService, + trainrunSectionService, + trainrunService, + netzgrafikColoringService, + loadPerlenketteService, + ); + + const viewportCullSerivce = new ViewportCullService( + uiInteractionService, + nodeService, + noteService, + trainrunSectionService + ); + + positionTransformationService = new PositionTransformationService( + trainrunSectionService, + nodeService, + noteService, + uiInteractionService, + viewportCullSerivce + ); + }); + + it("PositionTransformationService test-001", () => { + dataService.loadNetzgrafikDto( + NetzgrafikUnitTesting.getUnitTestNetzgrafik(), + ); + const pos: Vec2D[] = []; + nodeService.getNodes().forEach((n) => { + pos.push(new Vec2D(n.getPositionX(), n.getPositionY())); + }); + // no node is selected (default) - nothing changes + positionTransformationService.alignSelectedElementsToRightBorder(); + positionTransformationService.alignSelectedElementsToLeftBorder(); + positionTransformationService.alignSelectedElementsToTopBorder(); + positionTransformationService.alignSelectedElementsToBottomBorder(); + nodeService.getNodes().forEach((n: Node, index: number) => { + expect(n.getPositionX()).toBe(pos[index].getX()); + expect(n.getPositionY()).toBe(pos[index].getY()); + }); + }); + + it("PositionTransformationService test-001", () => { + dataService.loadNetzgrafikDto( + NetzgrafikUnitTesting.getUnitTestNetzgrafik(), + ); + const pos: Vec2D[] = []; + let minX = 100000; + nodeService.getNodes().forEach((n) => { + pos.push(new Vec2D(n.getPositionX(), n.getPositionY())); + nodeService.selectNode(n.getId()); + minX = Math.min(n.getPositionX(), minX); + }); + // all node are selected (default) - nothing changes + positionTransformationService.alignSelectedElementsToLeftBorder(); + nodeService.getNodes().forEach((n: Node, index: number) => { + expect(n.getPositionX()).toBe(minX); + expect(n.getPositionY()).toBe(pos[index].getY()); + }); + }); + + it("PositionTransformationService test-002", () => { + dataService.loadNetzgrafikDto( + NetzgrafikUnitTesting.getUnitTestNetzgrafik(), + ); + const pos: Vec2D[] = []; + let minY = 100000; + nodeService.getNodes().forEach((n) => { + pos.push(new Vec2D(n.getPositionX(), n.getPositionY())); + nodeService.selectNode(n.getId()); + minY = Math.min(n.getPositionY(), minY); + }); + // all node are selected (default) - nothing changes + positionTransformationService.alignSelectedElementsToTopBorder(); + nodeService.getNodes().forEach((n: Node, index: number) => { + expect(n.getPositionX()).toBe(pos[index].getX()); + expect(n.getPositionY()).toBe(minY); + }); + }); + + it("PositionTransformationService test-003", () => { + dataService.loadNetzgrafikDto( + NetzgrafikUnitTesting.getUnitTestNetzgrafik(), + ); + const pos: Vec2D[] = []; + nodeService.getNodes().forEach((n) => { + pos.push(new Vec2D(n.getPositionX(), n.getPositionY())); + }); + // all node are selected (default) - nothing changes + positionTransformationService.scaleNetzgrafikArea(2.0, + new Vec2D(0.0, 0.0), + "graphContainer"); + nodeService.getNodes().forEach((n: Node, index: number) => { + expect(n.getPositionX()).toBe(pos[index].getX() * 2.0); + expect(n.getPositionY()).toBe(pos[index].getY() * 2.0); + }); + }); + +}); diff --git a/src/app/services/util/position.transformation.service.ts b/src/app/services/util/position.transformation.service.ts new file mode 100644 index 00000000..df41bbb2 --- /dev/null +++ b/src/app/services/util/position.transformation.service.ts @@ -0,0 +1,210 @@ +import {Injectable} from "@angular/core"; +import {TrainrunSectionService} from "../data/trainrunsection.service"; +import {UiInteractionService} from "../ui/ui.interaction.service"; +import {NodeService} from "../data/node.service"; +import {NoteService} from "../data/note.service"; +import {Vec2D} from "../../utils/vec2D"; +import {Node} from "../../models/node.model"; +import {ViewportCullService} from "../ui/viewport.cull.service"; + +@Injectable({ + providedIn: "root", +}) +export class PositionTransformationService { + constructor( + private readonly trainrunSectionService: TrainrunSectionService, + private readonly nodeSerivce: NodeService, + private readonly noteSerivce: NoteService, + private readonly uiInteractionService: UiInteractionService, + private readonly viewportCullService: ViewportCullService, + ) { + } + + private scaleFullNetzgrafikArea(factor: number, zoomCenter: Vec2D, windowViewboxPropertiesMapKey: string) { + const scaleCenterCoordinates: Vec2D = this.computeScaleCenterCoordinates(zoomCenter, windowViewboxPropertiesMapKey); + const focalNode: Node = this.getFocalNode(scaleCenterCoordinates); + + this.nodeSerivce.getNodes().forEach((n, index) => { + let newPos = new Vec2D( + (n.getPositionX() - scaleCenterCoordinates.getX()) * factor + scaleCenterCoordinates.getX(), + (n.getPositionY() - scaleCenterCoordinates.getY()) * factor + scaleCenterCoordinates.getY() + ); + + if (focalNode?.getId() === n.getId()) { + const delta = Vec2D.sub(newPos, new Vec2D(focalNode.getPositionX(), focalNode.getPositionY(),)); + newPos = Vec2D.sub(newPos, delta); + } + n.setPosition(newPos.getX(), newPos.getY()); + }); + } + + private computeScaleCenterCoordinates(zoomCenter: Vec2D, + windowViewboxPropertiesMapKey: string): Vec2D { + const vp = + this.uiInteractionService.getViewboxProperties(windowViewboxPropertiesMapKey); + const scaleCenterCoordinates = new Vec2D( + vp.panZoomLeft + zoomCenter.getX() * vp.panZoomWidth, + vp.panZoomTop + zoomCenter.getY() * vp.panZoomHeight, + ); + + return scaleCenterCoordinates; + } + + private getFocalNode(scaleCenterCoordinates: Vec2D): Node { + // get the node under the mouse cursos and update the scaleCenter + const focalNode = this.nodeSerivce.getNodes().find((n) => + scaleCenterCoordinates.getX() > n.getPositionX() && scaleCenterCoordinates.getX() < (n.getPositionX() + n.getNodeWidth()) && + scaleCenterCoordinates.getY() > n.getPositionY() && scaleCenterCoordinates.getY() < (n.getPositionY() + n.getNodeHeight()) + ); + return focalNode; + } + + private scaleNetzgrafikSelectedNodesArea(factor: number, zoomCenter: Vec2D, nodes: Node[], windowViewboxPropertiesMapKey: string) { + const scaleCenterCoordinates: Vec2D = this.computeScaleCenterCoordinates(zoomCenter, windowViewboxPropertiesMapKey); + const focalNode: Node = this.getFocalNode(scaleCenterCoordinates); + + if (!focalNode) { + /* + * if more than one node is selected (multi-selected nodes) transform the nodes with center of + * mass + */ + let centerOfMass = new Vec2D(0, 0); + nodes.forEach(n => { + centerOfMass = Vec2D.add( + centerOfMass, + new Vec2D(n.getPositionX() + n.getNodeWidth() / 2.0, n.getPositionY() + n.getNodeHeight() / 2.0) + ); + }); + centerOfMass = Vec2D.scale(centerOfMass, 1.0 / Math.max(1, nodes.length)); + + scaleCenterCoordinates.setData(centerOfMass.getX(), centerOfMass.getY()); + } + + nodes.forEach((n, index) => { + let newPos = new Vec2D( + (n.getPositionX() + n.getNodeWidth() / 2.0 - scaleCenterCoordinates.getX()) * factor + scaleCenterCoordinates.getX() - n.getNodeWidth() / 2.0, + (n.getPositionY() + n.getNodeHeight() / 2.0 - scaleCenterCoordinates.getY()) * factor + scaleCenterCoordinates.getY() - n.getNodeHeight() / 2.0 + ); + + if (focalNode?.getId() === n.getId()) { + const delta = Vec2D.sub(newPos, new Vec2D(focalNode.getPositionX(), focalNode.getPositionY(),)); + newPos = Vec2D.sub(newPos, delta); + } + + n.setPosition(newPos.getX(), newPos.getY()); + + }); + } + + private upateRendering() { + this.nodeSerivce.initPortOrdering(); + + this.trainrunSectionService.getTrainrunSections().forEach(ts => { + ts.routeEdgeAndPlaceText(); + ts.getSourceNode().updateTransitionsAndConnections(); + }); + + this.viewportCullService.onViewportChangeUpdateRendering(true); + } + + scaleNetzgrafikArea(factor: number, zoomCenter: Vec2D, windowViewboxPropertiesMapKey: string) { + const nodes: Node[] = this.nodeSerivce.getSelectedNodes(); + + if (nodes.length < 2) { + this.scaleFullNetzgrafikArea(factor, zoomCenter, windowViewboxPropertiesMapKey); + } else { + this.scaleNetzgrafikSelectedNodesArea(factor, zoomCenter, nodes, windowViewboxPropertiesMapKey); + } + + this.upateRendering(); + } + + alignSelectedElementsToLeftBorder() { + const nodes: Node[] = this.nodeSerivce.getSelectedNodes(); + + let leftX = undefined; + nodes.forEach((n) => { + const pos = n.getPositionX(); + if (leftX === undefined) { + leftX = pos; + } else { + leftX = Math.min(leftX, pos); + } + }); + + if (leftX !== undefined) { + nodes.forEach((n) => { + n.setPosition(leftX, n.getPositionY()); + }); + } + + this.upateRendering(); + } + + alignSelectedElementsToRightBorder() { + const nodes: Node[] = this.nodeSerivce.getSelectedNodes(); + + let rightX = undefined; + nodes.forEach((n) => { + const pos = n.getPositionX() + n.getNodeWidth(); + if (rightX === undefined) { + rightX = pos; + } else { + rightX = Math.max(rightX, pos); + } + }); + + if (rightX !== undefined) { + nodes.forEach((n) => { + n.setPosition(rightX - n.getNodeWidth(), n.getPositionY()); + }); + } + + this.upateRendering(); + } + + alignSelectedElementsToTopBorder() { + const nodes: Node[] = this.nodeSerivce.getSelectedNodes(); + + let topY = undefined; + nodes.forEach((n) => { + const pos = n.getPositionY(); + if (topY === undefined) { + topY = pos; + } else { + topY = Math.min(topY, pos); + } + }); + + if (topY !== undefined) { + nodes.forEach((n) => { + n.setPosition(n.getPositionX(), topY); + }); + } + + this.upateRendering(); + } + + alignSelectedElementsToBottomBorder() { + const nodes: Node[] = this.nodeSerivce.getSelectedNodes(); + + let bottomY = undefined; + nodes.forEach((n) => { + const pos = n.getPositionY() + n.getNodeHeight(); + if (bottomY === undefined) { + bottomY = pos; + } else { + bottomY = Math.max(bottomY, pos); + } + }); + + if (bottomY !== undefined) { + nodes.forEach((n) => { + n.setPosition(n.getPositionX(), bottomY - n.getNodeHeight()); + }); + } + + this.upateRendering(); + } + +} diff --git a/src/app/view/editor-main-view/data-views/d3.utils.spec.ts b/src/app/view/editor-main-view/data-views/d3.utils.spec.ts index 35bf64b1..f1ef1dc3 100644 --- a/src/app/view/editor-main-view/data-views/d3.utils.spec.ts +++ b/src/app/view/editor-main-view/data-views/d3.utils.spec.ts @@ -24,6 +24,9 @@ import {NetzgrafikUnitTesting} from "../../../../integration-testing/netzgrafik. import {Vec2D} from "../../../utils/vec2D"; import {LevelOfDetailService} from "../../../services/ui/level.of.detail.service"; import {ViewportCullService} from "../../../services/ui/viewport.cull.service"; +import { + PositionTransformationService +} from "../../../services/util/position.transformation.service"; describe("3d.Utils.tests", () => { let dataService: DataService; @@ -139,6 +142,14 @@ describe("3d.Utils.tests", () => { trainrunSectionService ); + const positionTransformationService = new PositionTransformationService( + trainrunSectionService, + nodeService, + noteService, + uiInteractionService, + viewportCullSerivce + ); + const controller = new EditorMainViewComponent( nodeService, trainrunSectionService, @@ -152,7 +163,8 @@ describe("3d.Utils.tests", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); new EditorView( @@ -168,7 +180,8 @@ describe("3d.Utils.tests", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); controller.bindViewToServices(); editorView = controller.editorView; diff --git a/src/app/view/editor-main-view/data-views/data.view.spec.ts b/src/app/view/editor-main-view/data-views/data.view.spec.ts index 62b35666..dc9bd70d 100644 --- a/src/app/view/editor-main-view/data-views/data.view.spec.ts +++ b/src/app/view/editor-main-view/data-views/data.view.spec.ts @@ -28,6 +28,9 @@ import {TransitionViewObject} from "./transitionViewObject"; import {StaticDomTags} from "./static.dom.tags"; import {LevelOfDetailService} from "../../../services/ui/level.of.detail.service"; import {ViewportCullService} from "../../../services/ui/viewport.cull.service"; +import { + PositionTransformationService +} from "../../../services/util/position.transformation.service"; describe("Editor-DataView", () => { let dataService: DataService; @@ -140,10 +143,20 @@ describe("Editor-DataView", () => { noteService, trainrunSectionService ); + const levelOfDetailService = new LevelOfDetailService( uiInteractionService ); + const positionTransformationService = new PositionTransformationService( + trainrunSectionService, + nodeService, + noteService, + uiInteractionService, + viewportCullSerivce + ); + + const controller = new EditorMainViewComponent( nodeService, trainrunSectionService, @@ -157,7 +170,8 @@ describe("Editor-DataView", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); new EditorView( @@ -173,7 +187,8 @@ describe("Editor-DataView", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); controller.bindViewToServices(); diff --git a/src/app/view/editor-main-view/data-views/editor.keyEvents.ts b/src/app/view/editor-main-view/data-views/editor.keyEvents.ts index de57b37d..63be097d 100644 --- a/src/app/view/editor-main-view/data-views/editor.keyEvents.ts +++ b/src/app/view/editor-main-view/data-views/editor.keyEvents.ts @@ -19,6 +19,9 @@ import {Connection} from "../../../models/connection.model"; import {PreviewLineMode, TrainrunSectionPreviewLineView,} from "./trainrunsection.previewline.view"; import {TrainrunSection} from "../../../models/trainrunsection.model"; import {Trainrun} from "../../../models/trainrun.model"; +import { + PositionTransformationService +} from "../../../services/util/position.transformation.service"; export class EditorKeyEvents { private editorMode: EditorMode; @@ -35,6 +38,7 @@ export class EditorKeyEvents { private copyService: CopyService, private svgMouseController: SVGMouseController, private trainrunSectionPreviewLineView: TrainrunSectionPreviewLineView, + private positionTransformationService: PositionTransformationService ) { this.activateMousekeyDownHandler(EditorMode.NetzgrafikEditing); } @@ -128,6 +132,23 @@ export class EditorKeyEvents { d3.event.preventDefault(); } break; + case "ArrowLeft": + this.onArrowLeft(); + d3.event.preventDefault(); + break; + case "ArrowUp": + this.onArrowUp(); + d3.event.preventDefault(); + break; + case "ArrowRight": + this.onArrowRight(); + d3.event.preventDefault(); + break; + case "ArrowDown": + this.onArrowDown(); + d3.event.preventDefault(); + break; + default: break; } @@ -200,6 +221,26 @@ export class EditorKeyEvents { return false; } + private onArrowLeft(): boolean { + this.positionTransformationService.alignSelectedElementsToLeftBorder(); + return true; + } + + private onArrowUp(): boolean { + this.positionTransformationService.alignSelectedElementsToTopBorder(); + return true; + } + + private onArrowDown(): boolean { + this.positionTransformationService.alignSelectedElementsToBottomBorder(); + return true; + } + + private onArrowRight(): boolean { + this.positionTransformationService.alignSelectedElementsToRightBorder(); + return true; + } + private onRevertLastChange(): boolean { if (!this.trainrunSectionPreviewLineView.getVariantIsWritable()) { return true; diff --git a/src/app/view/editor-main-view/data-views/editor.view.ts b/src/app/view/editor-main-view/data-views/editor.view.ts index 324432a6..e1512cc1 100644 --- a/src/app/view/editor-main-view/data-views/editor.view.ts +++ b/src/app/view/editor-main-view/data-views/editor.view.ts @@ -27,6 +27,10 @@ import {EditorKeyEvents} from "./editor.keyEvents"; import {MultiSelectRenderer} from "./multiSelectRenderer"; import {UndoService} from "../../../services/data/undo.service"; import {CopyService} from "../../../services/data/copy.service"; +import { + PositionTransformationService +} from "../../../services/util/position.transformation.service"; + import { StreckengrafikDrawingContext } from "../../../streckengrafik/model/util/streckengrafik.drawing.context"; @@ -129,7 +133,8 @@ export class EditorView implements SVGMouseControllerObserver { private logService: LogService, private viewportCullService: ViewportCullService, private levelOfDetailService: LevelOfDetailService, - private versionControlService: VersionControlService + private versionControlService: VersionControlService, + private positionTransformationService: PositionTransformationService ) { this.controller = controller; this.svgMouseController = new SVGMouseController(EditorView.svgName, this); @@ -156,6 +161,7 @@ export class EditorView implements SVGMouseControllerObserver { copyService, this.svgMouseController, this.trainrunSectionPreviewLineView, + this.positionTransformationService ); } @@ -578,7 +584,7 @@ export class EditorView implements SVGMouseControllerObserver { onScaleNetzgrafik(factor: number, scaleCenter: Vec2D) { - this.scaleNetzgrafikArea(factor, scaleCenter); + this.positionTransformationService.scaleNetzgrafikArea(factor, scaleCenter, EditorView.svgName); } zoomFactorChanged(newZoomFactor: number) { @@ -693,98 +699,4 @@ export class EditorView implements SVGMouseControllerObserver { } - private scaleFullNetzgrafikArea(factor: number, zoomCenter: Vec2D) { - const vp = this.uiInteractionService.getViewboxProperties(EditorView.svgName); - const scaleCenterCoordinates = new Vec2D( - vp.panZoomLeft + zoomCenter.getX() * vp.panZoomWidth, - vp.panZoomTop + zoomCenter.getY() * vp.panZoomHeight, - ); - - // get the node under the mouse cursos and update the scaleCenter - const focalNode = this.nodeService.getNodes().find((n) => - scaleCenterCoordinates.getX() > n.getPositionX() && scaleCenterCoordinates.getX() < (n.getPositionX() + n.getNodeWidth()) && - scaleCenterCoordinates.getY() > n.getPositionY() && scaleCenterCoordinates.getY() < (n.getPositionY() + n.getNodeHeight()) - ); - - this.nodeService.getNodes().forEach((n, index) => { - let newPos = new Vec2D( - (n.getPositionX() - scaleCenterCoordinates.getX()) * factor + scaleCenterCoordinates.getX(), - (n.getPositionY() - scaleCenterCoordinates.getY()) * factor + scaleCenterCoordinates.getY() - ); - - if (focalNode?.getId() === n.getId()) { - const delta = Vec2D.sub(newPos, new Vec2D(focalNode.getPositionX(), focalNode.getPositionY(),)); - newPos = Vec2D.sub(newPos, delta); - } - n.setPosition(newPos.getX(), newPos.getY()); - }); - } - - private scaleNetzgrafikSelectedNodesArea(factor: number, zoomCenter: Vec2D, nodes: Node[]) { - - const vp = this.uiInteractionService.getViewboxProperties(EditorView.svgName); - const scaleCenterCoordinates = new Vec2D( - vp.panZoomLeft + zoomCenter.getX() * vp.panZoomWidth, - vp.panZoomTop + zoomCenter.getY() * vp.panZoomHeight, - ); - - // get the node under the mouse cursos and update the scaleCenter - const focalNode = this.nodeService.getNodes().find((n) => - scaleCenterCoordinates.getX() > n.getPositionX() && scaleCenterCoordinates.getX() < (n.getPositionX() + n.getNodeWidth()) && - scaleCenterCoordinates.getY() > n.getPositionY() && scaleCenterCoordinates.getY() < (n.getPositionY() + n.getNodeHeight()) - ); - - if (!focalNode) { - /* - * if more than one node is selected (multi-selected nodes) transform the nodes with center of - * mass - */ - let centerOfMass = new Vec2D(0, 0); - nodes.forEach(n => { - centerOfMass = Vec2D.add( - centerOfMass, - new Vec2D(n.getPositionX() + n.getNodeWidth() / 2.0, n.getPositionY() + n.getNodeHeight() / 2.0) - ); - }); - centerOfMass = Vec2D.scale(centerOfMass, 1.0 / Math.max(1, nodes.length)); - - scaleCenterCoordinates.setData(centerOfMass.getX(), centerOfMass.getY()); - } - - nodes.forEach((n, index) => { - let newPos = new Vec2D( - (n.getPositionX() + n.getNodeWidth() / 2.0 - scaleCenterCoordinates.getX()) * factor + scaleCenterCoordinates.getX() - n.getNodeWidth() / 2.0, - (n.getPositionY() + n.getNodeHeight() / 2.0 - scaleCenterCoordinates.getY()) * factor + scaleCenterCoordinates.getY() - n.getNodeHeight() / 2.0 - ); - - if (focalNode?.getId() === n.getId()) { - const delta = Vec2D.sub(newPos, new Vec2D(focalNode.getPositionX(), focalNode.getPositionY(),)); - newPos = Vec2D.sub(newPos, delta); - } - - n.setPosition(newPos.getX(), newPos.getY()); - - }); - } - - private scaleNetzgrafikArea(factor: number, zoomCenter: Vec2D) { - const nodes: Node[] = this.nodeService.getSelectedNodes(); - - if (nodes.length < 2) { - this.scaleFullNetzgrafikArea(factor, zoomCenter); - } else { - this.scaleNetzgrafikSelectedNodesArea(factor, zoomCenter, nodes); - } - - this.trainrunSectionService.getTrainrunSections().forEach(ts => { - ts.routeEdgeAndPlaceText(); - ts.getSourceNode().updateTransitionsAndConnections(); - }); - - this.nodeService.initPortOrdering(); - - this.viewportCullService.onViewportChangeUpdateRendering(true); - } - - } diff --git a/src/app/view/editor-main-view/data-views/nodes.view.spec.ts b/src/app/view/editor-main-view/data-views/nodes.view.spec.ts index 664456bb..0aa8a129 100644 --- a/src/app/view/editor-main-view/data-views/nodes.view.spec.ts +++ b/src/app/view/editor-main-view/data-views/nodes.view.spec.ts @@ -23,6 +23,9 @@ import {EditorView} from "./editor.view"; import {NodesView} from "./nodes.view"; import {LevelOfDetailService} from "../../../services/ui/level.of.detail.service"; import {ViewportCullService} from "../../../services/ui/viewport.cull.service"; +import { + PositionTransformationService +} from "../../../services/util/position.transformation.service"; describe("Nodes-View", () => { let dataService: DataService; @@ -131,6 +134,7 @@ describe("Nodes-View", () => { const levelOfDetailService = new LevelOfDetailService( uiInteractionService ); + const viewportCullSerivce = new ViewportCullService( uiInteractionService, nodeService, @@ -138,6 +142,14 @@ describe("Nodes-View", () => { trainrunSectionService ); + const positionTransformationService = new PositionTransformationService( + trainrunSectionService, + nodeService, + noteService, + uiInteractionService, + viewportCullSerivce + ); + const controller = new EditorMainViewComponent( nodeService, trainrunSectionService, @@ -151,7 +163,8 @@ describe("Nodes-View", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); new EditorView( @@ -167,8 +180,10 @@ describe("Nodes-View", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); + controller.bindViewToServices(); editorView = controller.editorView; }); diff --git a/src/app/view/editor-main-view/data-views/notes.view.spec.ts b/src/app/view/editor-main-view/data-views/notes.view.spec.ts index 882e579e..cba6f1c7 100644 --- a/src/app/view/editor-main-view/data-views/notes.view.spec.ts +++ b/src/app/view/editor-main-view/data-views/notes.view.spec.ts @@ -23,6 +23,9 @@ import {EditorView} from "./editor.view"; import {NetzgrafikUnitTesting} from "../../../../integration-testing/netzgrafik.unit.testing"; import {LevelOfDetailService} from "../../../services/ui/level.of.detail.service"; import {ViewportCullService} from "../../../services/ui/viewport.cull.service"; +import { + PositionTransformationService +} from "../../../services/util/position.transformation.service"; describe("Notes-View", () => { let dataService: DataService; @@ -131,6 +134,7 @@ describe("Notes-View", () => { const levelOfDetailService = new LevelOfDetailService( uiInteractionService ); + const viewportCullSerivce = new ViewportCullService( uiInteractionService, nodeService, @@ -138,6 +142,14 @@ describe("Notes-View", () => { trainrunSectionService ); + const positionTransformationService = new PositionTransformationService( + trainrunSectionService, + nodeService, + noteService, + uiInteractionService, + viewportCullSerivce + ); + const controller = new EditorMainViewComponent( nodeService, trainrunSectionService, @@ -151,7 +163,8 @@ describe("Notes-View", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); new EditorView( @@ -167,7 +180,8 @@ describe("Notes-View", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); controller.bindViewToServices(); editorView = controller.editorView; diff --git a/src/app/view/editor-main-view/data-views/trainrunsections.view.spec.ts b/src/app/view/editor-main-view/data-views/trainrunsections.view.spec.ts index 5c4befa2..dbf33113 100644 --- a/src/app/view/editor-main-view/data-views/trainrunsections.view.spec.ts +++ b/src/app/view/editor-main-view/data-views/trainrunsections.view.spec.ts @@ -28,6 +28,9 @@ import {TrainrunSectionsView} from "./trainrunsections.view"; import {Vec2D} from "../../../utils/vec2D"; import {LevelOfDetailService} from "../../../services/ui/level.of.detail.service"; import {ViewportCullService} from "../../../services/ui/viewport.cull.service"; +import { + PositionTransformationService +} from "../../../services/util/position.transformation.service"; describe("TrainrunSection-View", () => { let dataService: DataService; @@ -136,6 +139,7 @@ describe("TrainrunSection-View", () => { const levelOfDetailService = new LevelOfDetailService( uiInteractionService ); + const viewportCullSerivce = new ViewportCullService( uiInteractionService, nodeService, @@ -143,6 +147,14 @@ describe("TrainrunSection-View", () => { trainrunSectionService ); + const positionTransformationService = new PositionTransformationService( + trainrunSectionService, + nodeService, + noteService, + uiInteractionService, + viewportCullSerivce + ); + const controller = new EditorMainViewComponent( nodeService, trainrunSectionService, @@ -156,7 +168,8 @@ describe("TrainrunSection-View", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); new EditorView( @@ -172,7 +185,8 @@ describe("TrainrunSection-View", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); controller.bindViewToServices(); diff --git a/src/app/view/editor-main-view/data-views/transitions.view.spec.ts b/src/app/view/editor-main-view/data-views/transitions.view.spec.ts index 9a168d30..d595830d 100644 --- a/src/app/view/editor-main-view/data-views/transitions.view.spec.ts +++ b/src/app/view/editor-main-view/data-views/transitions.view.spec.ts @@ -23,6 +23,9 @@ import {NetzgrafikUnitTesting} from "../../../../integration-testing/netzgrafik. import {TransitionsView} from "./transitions.view"; import {LevelOfDetailService} from "../../../services/ui/level.of.detail.service"; import {ViewportCullService} from "../../../services/ui/viewport.cull.service"; +import { + PositionTransformationService +} from "../../../services/util/position.transformation.service"; describe("Transitions-View", () => { let dataService: DataService; @@ -131,6 +134,7 @@ describe("Transitions-View", () => { const levelOfDetailService = new LevelOfDetailService( uiInteractionService ); + const viewportCullSerivce = new ViewportCullService( uiInteractionService, nodeService, @@ -138,6 +142,14 @@ describe("Transitions-View", () => { trainrunSectionService ); + const positionTransformationService = new PositionTransformationService( + trainrunSectionService, + nodeService, + noteService, + uiInteractionService, + viewportCullSerivce + ); + const controller = new EditorMainViewComponent( nodeService, trainrunSectionService, @@ -151,10 +163,10 @@ describe("Transitions-View", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); - new EditorView( controller, nodeService, @@ -168,7 +180,8 @@ describe("Transitions-View", () => { logService, viewportCullSerivce, levelOfDetailService, - undefined + undefined, + positionTransformationService ); controller.bindViewToServices(); diff --git a/src/app/view/editor-main-view/editor-main-view.component.ts b/src/app/view/editor-main-view/editor-main-view.component.ts index 1e3caa73..2f224909 100644 --- a/src/app/view/editor-main-view/editor-main-view.component.ts +++ b/src/app/view/editor-main-view/editor-main-view.component.ts @@ -44,6 +44,7 @@ import {Port} from "../../models/port.model"; import {LevelOfDetailService} from "../../services/ui/level.of.detail.service"; import {ViewportCullService} from "../../services/ui/viewport.cull.service"; import {VersionControlService} from "../../services/data/version-control.service"; +import {PositionTransformationService} from "../../services/util/position.transformation.service"; @Component({ selector: "sbb-editor-main-view", @@ -78,6 +79,7 @@ export class EditorMainViewComponent implements AfterViewInit, OnDestroy { private viewportCullSerivce: ViewportCullService, private levelOfDetailService: LevelOfDetailService, private versionControlService: VersionControlService, + private positionTransformationService: PositionTransformationService ) { this.editorView = new EditorView( this, @@ -92,7 +94,8 @@ export class EditorMainViewComponent implements AfterViewInit, OnDestroy { logService, viewportCullSerivce, levelOfDetailService, - versionControlService + versionControlService, + positionTransformationService ); this.uiInteractionService.zoomInObservable .pipe(takeUntil(this.destroyed))