/******************************************************************************* * Copyright (c) 2014, 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: * Alexander Nyßen (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef.mvc.examples.logo.parts; import java.util.Collections; import java.util.List; import org.eclipse.gef.fx.nodes.GeometryNode; import org.eclipse.gef.geometry.convert.fx.FX2Geometry; import org.eclipse.gef.geometry.convert.fx.Geometry2FX; import org.eclipse.gef.geometry.planar.AffineTransform; import org.eclipse.gef.geometry.planar.Dimension; import org.eclipse.gef.geometry.planar.IGeometry; import org.eclipse.gef.geometry.planar.IScalable; import org.eclipse.gef.geometry.planar.IShape; import org.eclipse.gef.geometry.planar.Rectangle; import org.eclipse.gef.mvc.examples.logo.model.AbstractGeometricElement; import org.eclipse.gef.mvc.examples.logo.model.GeometricShape; import org.eclipse.gef.mvc.fx.parts.IResizableContentPart; import org.eclipse.gef.mvc.fx.parts.ISnappablePart; import org.eclipse.gef.mvc.fx.parts.ITransformableContentPart; import org.eclipse.gef.mvc.fx.parts.IVisualPart; import org.eclipse.gef.mvc.fx.viewer.InfiniteCanvasViewer; import com.google.common.collect.HashMultimap; import com.google.common.collect.SetMultimap; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.geometry.Bounds; import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.StrokeType; import javafx.scene.transform.Affine; import javafx.scene.transform.Transform; public class GeometricShapePart extends AbstractGeometricElementPart<GeometryNode<IShape>> implements ITransformableContentPart<GeometryNode<IShape>>, IResizableContentPart<GeometryNode<IShape>>, ISnappablePart<GeometryNode<IShape>> { private final ChangeListener<? super Paint> fillObserver = new ChangeListener<Paint>() { @Override public void changed(ObservableValue<? extends Paint> observable, Paint oldValue, Paint newValue) { refreshVisual(); } }; private final boolean debugging = false; private javafx.scene.shape.Rectangle layoutBoundsRect; @Override protected void doActivate() { super.doActivate(); getContent().fillProperty().addListener(fillObserver); } @Override protected void doAddContentChild(Object contentChild, int index) { // nothing to do } @Override protected void doAttachToAnchorageVisual(org.eclipse.gef.mvc.fx.parts.IVisualPart<? extends Node> anchorage, String role) { // nothing to do } @Override protected void doAttachToContentAnchorage(Object contentAnchorage, String role) { if (!(contentAnchorage instanceof AbstractGeometricElement)) { throw new IllegalArgumentException("Cannot attach to content anchorage: wrong type!"); } getContent().getAnchorages().add((AbstractGeometricElement<?>) contentAnchorage); } @Override protected GeometryNode<IShape> doCreateVisual() { GeometryNode<IShape> geometryNode = new GeometryNode<>(); if (debugging) { layoutBoundsRect = new javafx.scene.shape.Rectangle(); layoutBoundsRect.setStrokeType(StrokeType.CENTERED); layoutBoundsRect.setFill(null); layoutBoundsRect.setStroke(Color.RED); layoutBoundsRect.setStrokeWidth(0.5); ((InfiniteCanvasViewer) getRoot().getViewer()).getCanvas().getScrolledOverlayGroup().getChildren() .add(layoutBoundsRect); geometryNode.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() { @Override public void changed(javafx.beans.value.ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) { updateLayoutBoundsRect(geometryNode); } }); geometryNode.localToSceneTransformProperty().addListener(new ChangeListener<Transform>() { @Override public void changed(javafx.beans.value.ObservableValue<? extends Transform> observable, Transform oldValue, Transform newValue) { updateLayoutBoundsRect(geometryNode); } }); } return geometryNode; } @Override protected void doDeactivate() { getContent().fillProperty().removeListener(fillObserver); super.doDeactivate(); } @Override protected void doDetachFromAnchorageVisual(IVisualPart<? extends Node> anchorage, String role) { // nothing to do } @Override protected void doDetachFromContentAnchorage(Object contentAnchorage, String role) { getContent().getAnchorages().remove(contentAnchorage); } @Override protected SetMultimap<? extends Object, String> doGetContentAnchorages() { SetMultimap<Object, String> anchorages = HashMultimap.create(); for (AbstractGeometricElement<? extends IGeometry> anchorage : getContent().getAnchorages()) { anchorages.put(anchorage, "link"); } return anchorages; } @Override protected List<? extends Object> doGetContentChildren() { return Collections.emptyList(); } @Override protected void doRefreshVisual(GeometryNode<IShape> visual) { GeometricShape content = getContent(); if (visual.getGeometry() != content.getGeometry()) { visual.setGeometry(content.getGeometry()); } AffineTransform transform = content.getTransform(); if (transform != null) { setVisualTransform(Geometry2FX.toFXAffine(transform)); } // apply stroke paint if (visual.getStroke() != content.getStroke()) { visual.setStroke(content.getStroke()); } // stroke width if (visual.getStrokeWidth() != content.getStrokeWidth()) { visual.setStrokeWidth(content.getStrokeWidth()); } if (visual.getFill() != content.getFill()) { visual.setFill(content.getFill()); } // apply effect super.doRefreshVisual(visual); } @Override protected void doRemoveContentChild(Object contentChild) { // nothing to do } @Override protected void doReorderContentChild(Object contentChild, int newIndex) { } @Override public GeometricShape getContent() { return (GeometricShape) super.getContent(); } @Override public Dimension getContentSize() { return getContent().getGeometry().getBounds().getSize(); } @Override public Affine getContentTransform() { return Geometry2FX.toFXAffine(getContent().getTransform()); } @Override public void setContent(Object model) { if (model != null && !(model instanceof GeometricShape)) { throw new IllegalArgumentException("Only IShape models are supported."); } super.setContent(model); } @Override public void setContentSize(Dimension size) { IShape geometry = getContent().getGeometry(); Rectangle geometricBounds = geometry.getBounds(); // XXX: The given <i>size</i> contains the stroke of the underlying // geometry, therefore, we need to subtract the stroke width from both // width and height (actually this depends on the stroke type (which is // centered, per default). double sx = (size.width - getContent().getStrokeWidth()) / geometricBounds.getWidth(); double sy = (size.height - getContent().getStrokeWidth()) / geometricBounds.getHeight(); ((IScalable<?>) geometry).scale(sx, sy, geometricBounds.getX(), geometricBounds.getY()); } @Override public void setContentTransform(Affine totalTransform) { getContent().setTransform(FX2Geometry.toAffineTransform(totalTransform)); } private void updateLayoutBoundsRect(GeometryNode<IShape> geometryNode) { Bounds boundsInScene = geometryNode.localToScene(geometryNode.getLayoutBounds()); Bounds boundsInParent = ((InfiniteCanvasViewer) getRoot().getViewer()).getCanvas().getScrolledOverlayGroup() .sceneToLocal(boundsInScene); layoutBoundsRect.setX(boundsInParent.getMinX()); layoutBoundsRect.setY(boundsInParent.getMinY()); layoutBoundsRect.setWidth(boundsInParent.getWidth()); layoutBoundsRect.setHeight(boundsInParent.getHeight()); } }