/******************************************************************************* * Copyright (c) 2016 itemis AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Matthias Wienand (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef.mvc.fx.handlers; import org.eclipse.gef.fx.nodes.InfiniteCanvas; import org.eclipse.gef.geometry.planar.Point; import org.eclipse.gef.mvc.fx.models.GridModel; import org.eclipse.gef.mvc.fx.viewer.IViewer; import org.eclipse.gef.mvc.fx.viewer.InfiniteCanvasViewer; import javafx.geometry.Point2D; import javafx.scene.Node; /** * The {@link SnapToGridSupport} can be used within an {@link IHandler} implementation * for snapping scene coordinates to grid points. */ public class SnapToGridSupport { private IHandler handler; /** * Constructs a new {@link SnapToGridSupport} for the given host {@link IHandler}, * which gives access to the application's {@link IViewer} and scenegraph. * * @param handler * The host {@link IHandler} for this {@link SnapToGridSupport}. */ public SnapToGridSupport(IHandler handler) { this.handler = handler; } /** * Determines a visual within the given {@link IViewer} in which grid * positions are at * <code>(n * grid-cell-width, m * grid-cell-height)</code>. Per default, * the content group of the {@link InfiniteCanvas} is returned for an * {@link InfiniteCanvasViewer}. For other {@link IViewer} implementations, * the visual of the root part is used. * * @param viewer * The {@link IViewer} for which to determine a grid-local * visual. * @return A grid-local visual for the given {@link IViewer}. */ protected Node getGridLocalVisual(IViewer viewer) { return viewer instanceof InfiniteCanvasViewer ? ((InfiniteCanvasViewer) viewer).getCanvas().getContentGroup() : viewer.getRootPart().getVisual(); } /** * Returns the horizontal granularity for "snap-to-grid" where * <code>1</code> means it will snap to integer grid positions. * * @return The horizontal granularity for "snap-to-grid". */ protected double getSnapToGridGranularityX() { return 1; } /** * Returns the vertical granularity for "snap-to-grid" where <code>1</code> * means it will snap to integer grid positions. * * @return The vertical granularity for "snap-to-grid". */ protected double getSnapToGridGranularityY() { return 1; } /** * Snaps the given position (in scene coordinates) to a grid position (in * scene coordinates). * * @param sceneX * Original position's x-coordinate. * @param sceneY * Original position's y-coordinate. * @return The snapped position in scene coordinates. */ public Point snapToGrid(double sceneX, double sceneY) { IViewer viewer = handler.getHost().getRoot().getViewer(); return snapToGrid(sceneX, sceneY, viewer.<GridModel> getAdapter(GridModel.class), getSnapToGridGranularityX(), getSnapToGridGranularityY(), getGridLocalVisual(viewer)); } /** * Snaps the given position (in scene coordinates) to a grid position (in * scene coordinates). The grid positions are specified by the given * {@link GridModel} and the given cell size fractions. * * @param sceneX * The x-coordinate of the current position (in scene * coordinates). * @param sceneY * The y-coordinate of the current position (in scene * coordinates). * @param gridModel * The {@link GridModel} that specifies the grid positions. * @param gridCellWidthFraction * The cell width fraction that determines if the x-coordinate is * snapped to full (1.0), halve (0.5), etc. grid positions. * @param gridCellHeightFraction * The cell height fraction that determines if the y-coordinate * is snapped to full (1.0), halve (0.5), etc. grid positions. * @param gridLocalVisual * A visual within the coordinate system where grid positions are * at <code>(n * grid-cell-width, m * grid-cell-height)</code>. * @return The resulting snapped position in scene coordinates. */ public Point snapToGrid(final double sceneX, final double sceneY, GridModel gridModel, final double gridCellWidthFraction, final double gridCellHeightFraction, Node gridLocalVisual) { // do nothing if snap to grid is disabled if (!gridModel.isSnapToGrid()) { return new Point(sceneX, sceneY); } // transform to grid local coordinates Point2D gridLocalPosition = gridLocalVisual.sceneToLocal(sceneX, sceneY); // snap to (nearest) grid point (add 0.5 so that the nearest grid // position is computed) double gcw = gridCellWidthFraction * gridModel.getGridCellWidth(); double nearest = gridLocalPosition.getX() > 0 ? 0.5 : -0.5; int xs = (int) (gridLocalPosition.getX() / gcw + nearest); double gch = gridCellHeightFraction * gridModel.getGridCellHeight(); nearest = gridLocalPosition.getY() > 0 ? 0.5 : -0.5; int ys = (int) (gridLocalPosition.getY() / gch + nearest); double nx = xs * gcw; double ny = ys * gch; // transform to scene coordinates Point2D newPositionInScene = gridLocalVisual.localToScene(nx, ny); return new Point(newPositionInScene.getX(), newPositionInScene.getY()); } }