/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kie.workbench.common.stunner.core.graph.processing.index.bounds;
import java.util.List;
import java.util.Optional;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import org.kie.workbench.common.stunner.core.client.canvas.Point2D;
import org.kie.workbench.common.stunner.core.graph.Edge;
import org.kie.workbench.common.stunner.core.graph.Graph;
import org.kie.workbench.common.stunner.core.graph.Node;
import org.kie.workbench.common.stunner.core.graph.content.Bounds;
import org.kie.workbench.common.stunner.core.graph.content.relationship.Child;
import org.kie.workbench.common.stunner.core.graph.content.view.View;
import org.kie.workbench.common.stunner.core.graph.processing.traverse.content.AbstractChildrenTraverseCallback;
import org.kie.workbench.common.stunner.core.graph.processing.traverse.content.ChildrenTraverseProcessor;
import org.kie.workbench.common.stunner.core.graph.util.GraphUtils;
@Dependent
public class GraphBoundsIndexerImpl implements GraphBoundsIndexer {
private Graph<View, Node<View, Edge>> graph;
private String rootUUID = null;
ChildrenTraverseProcessor childrenTraverseProcessor;
@Inject
public GraphBoundsIndexerImpl(final ChildrenTraverseProcessor childrenTraverseProcessor) {
this.childrenTraverseProcessor = childrenTraverseProcessor;
}
@Override
public GraphBoundsIndexerImpl build(final Graph<View, Node<View, Edge>> graph) {
this.graph = graph;
return this;
}
@Override
public Node<View<?>, Edge> getAt(final double x,
final double y) {
return findElementAt(x,
y);
}
@Override
public double[] getTrimmedBounds() {
final double[] result = new double[]{Double.MAX_VALUE, Double.MAX_VALUE, 0, 0};
childrenTraverseProcessor
.setRootUUID(this.rootUUID)
.traverse(graph,
new GraphBoundIndexerTraverseCallback(new NodeBoundsTraverseCallback() {
@Override
public void onNodeTraverse(final Node<View, Edge> node,
final double parentX,
final double parentY) {
final String uuid = node.getUUID();
final boolean isRoot = null != GraphBoundsIndexerImpl.this.rootUUID
&& GraphBoundsIndexerImpl.this.rootUUID.equals(uuid);
if (!isRoot) {
final double[] absCoords = getNodeAbsoluteCoordinates(node,
parentX,
parentY);
final double x = absCoords[0];
final double y = absCoords[1];
final double w = absCoords[2];
final double h = absCoords[3];
if (x < result[0]) {
result[0] = x;
}
if (y < result[1]) {
result[1] = y;
}
if (w > result[2]) {
result[2] = w;
}
if (h > result[3]) {
result[3] = h;
}
}
}
}));
return result;
}
@SuppressWarnings("unchecked")
public Node<View<?>, Edge> findElementAt(final double x,
final double y) {
final Node[] result = new Node[1];
childrenTraverseProcessor.traverse(graph,
new GraphBoundIndexerTraverseCallback(new NodeBoundsTraverseCallback() {
@Override
public void onNodeTraverse(final Node<View, Edge> node,
final double parentX,
final double parentY) {
if (isNodeAt(node,
parentX,
parentY,
x,
y)) {
result[0] = node;
}
}
}));
return result[0];
}
private abstract class NodeBoundsTraverseCallback {
public abstract void onNodeTraverse(final Node<View, Edge> node,
final double parentX,
final double parentY);
}
private class GraphBoundIndexerTraverseCallback extends AbstractChildrenTraverseCallback<Node<View, Edge>, Edge<Child, Node>> {
private final NodeBoundsTraverseCallback callback;
private GraphBoundIndexerTraverseCallback(final NodeBoundsTraverseCallback callback) {
this.callback = callback;
}
@Override
public void startNodeTraversal(final Node<View, Edge> node) {
super.startNodeTraversal(node);
onStartNodeTraversal(Optional.empty(),
node);
}
@Override
public boolean startNodeTraversal(final List<Node<View, Edge>> parents,
final Node<View, Edge> node) {
super.startNodeTraversal(parents,
node);
onStartNodeTraversal(Optional.ofNullable(parents),
node);
return true;
}
private void onStartNodeTraversal(final Optional<List<Node<View, Edge>>> parents,
final Node<View, Edge> node) {
final double[] parentLocation = {0, 0};
if (parents.isPresent()) {
parents.get().forEach(parent -> {
final Point2D nodeCoordinates = getNodeCoordinates(parent);
if (null != nodeCoordinates) {
parentLocation[0] += nodeCoordinates.getX();
parentLocation[1] += nodeCoordinates.getY();
}
});
}
callback.onNodeTraverse(node,
parentLocation[0],
parentLocation[1]);
}
}
private Point2D getNodeCoordinates(final Node node) {
if (null != node) {
final Object content = node.getContent();
if (content instanceof View) {
final View viewContent = (View) content;
return GraphUtils.getPosition(viewContent);
}
}
return null;
}
private double[] getNodeAbsoluteCoordinates(final Node node,
final double parentX,
final double parentY) {
final View content = (View) node.getContent();
final Bounds bounds = content.getBounds();
final Bounds.Bound ulBound = bounds.getUpperLeft();
final Bounds.Bound lrBound = bounds.getLowerRight();
final double ulX = ulBound.getX() + parentX;
final double ulY = ulBound.getY() + parentY;
final double lrX = lrBound.getX() + parentX;
final double lrY = lrBound.getY() + parentY;
return new double[]{ulX, ulY, lrX, lrY};
}
private boolean isNodeAt(final Node node,
final double parentX,
final double parentY,
final double mouseX,
final double mouseY) {
if (null != rootUUID && node.getUUID().equals(rootUUID)) {
return true;
}
final double[] absoluteCoords = getNodeAbsoluteCoordinates(node,
parentX,
parentY);
final double ulX = absoluteCoords[0];
final double ulY = absoluteCoords[1];
final double lrX = absoluteCoords[2];
final double lrY = absoluteCoords[3];
if (mouseX >= ulX && mouseX <= lrX &&
mouseY >= ulY && mouseY <= lrY) {
return true;
}
return false;
}
@Override
public GraphBoundsIndexer setRootUUID(final String uuid) {
this.rootUUID = uuid;
return this;
}
@Override
public void destroy() {
this.graph = null;
this.rootUUID = null;
this.childrenTraverseProcessor = null;
}
}