/* * Copyright 2015 JBoss, by Red Hat, Inc * * 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.uberfire.ext.wires.core.api.shapes; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.ait.lienzo.client.core.animation.AnimationProperties; import com.ait.lienzo.client.core.animation.AnimationTweener; import com.ait.lienzo.client.core.animation.IAnimation; import com.ait.lienzo.client.core.animation.IAnimationCallback; import com.ait.lienzo.client.core.animation.IAnimationHandle; import com.ait.lienzo.client.core.event.NodeDragMoveEvent; import com.ait.lienzo.client.core.event.NodeDragMoveHandler; import com.ait.lienzo.client.core.event.NodeMouseClickEvent; import com.ait.lienzo.client.core.event.NodeMouseClickHandler; import com.ait.lienzo.client.core.shape.Group; import com.ait.lienzo.client.core.shape.Layer; import com.ait.lienzo.client.core.types.Point2D; import org.uberfire.commons.data.Pair; import org.uberfire.commons.uuid.UUID; import org.uberfire.ext.wires.core.api.selection.RequiresSelectionManager; import org.uberfire.ext.wires.core.api.selection.SelectionManager; /** * A Fixed Shape that cannot be re-sized or have connectors attached */ public abstract class WiresBaseShape extends Group implements WiresShape, RequiresSelectionManager { private static final int ANIMATION_DURATION = 250; private static final int DEFAULT_CONTROL_SPACING = 30; private static final int DEFAULT_CONTROL_POSITION_X_OFFSET = 100; private static final int DEFAULT_CONTROL_POSITION_Y_OFFSET = 0; protected String id; protected SelectionManager selectionManager; protected List<Group> controls = new ArrayList<Group>(); protected boolean isControlsVisible = false; private IAnimationHandle animationHandle; public WiresBaseShape() { id = UUID.uuid(); setDraggable(true); //Clicking the Group selects the Shape addNodeMouseClickHandler(new NodeMouseClickHandler() { @Override public void onNodeMouseClick(final NodeMouseClickEvent nodeMouseClickEvent) { selectionManager.selectShape(WiresBaseShape.this); } }); //Update Control positions when Shape is dragged addNodeDragMoveHandler(new NodeDragMoveHandler() { @Override public void onNodeDragMove(final NodeDragMoveEvent nodeDragMoveEvent) { updateControlLocations(); } }); } @Override public String getId() { return this.id; } @Override public void setSelectionManager(final SelectionManager manager) { this.selectionManager = manager; } @Override public void showControls() { if (controls == null || controls.isEmpty()) { return; } if (isControlsVisible) { return; } if (animationHandle != null) { animationHandle.stop(); } isControlsVisible = true; animationHandle = animate(AnimationTweener.EASE_OUT, new AnimationProperties(), ANIMATION_DURATION, new IAnimationCallback() { private final AnimationTweener tweener = AnimationTweener.TweenerBuilder.MAKE_ELASTIC(1); private final Map<Group, Pair<Point2D, Point2D>> transformations = new HashMap<Group, Pair<Point2D, Point2D>>(); @Override public void onStart(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { //Store required transformations: Control, Current location, Target location transformations.clear(); for (int index = 0; index < controls.size(); index++) { final Group ctrl = controls.get(index); final Point2D origin = new Point2D(0, 0); final Point2D target = getControlTarget(ctrl); transformations.put(ctrl, new Pair<Point2D, Point2D>(origin, target)); WiresBaseShape.this.getLayer().add(ctrl); ctrl.setLocation(origin); ctrl.setAlpha(0.0); } } @Override public void onFrame(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { final double pct = tweener.apply(iAnimation.getPercent() > 1.0 ? 1.0 : iAnimation.getPercent()); //Move each Control along the line between its origin and the target destination for (Map.Entry<Group, Pair<Point2D, Point2D>> e : transformations.entrySet()) { final Point2D origin = e.getValue().getK1(); final Point2D target = e.getValue().getK2(); final double dx = (target.getX() - origin.getX()) * pct; final double dy = (target.getY() - origin.getY()) * pct; e.getKey().setLocation(new Point2D(origin.getX() + dx, origin.getY() + dy).add(WiresBaseShape.this.getLocation())); } for (Group ctrl : controls) { ctrl.setAlpha(iAnimation.getPercent()); } //Without this call Lienzo doesn't update the Canvas for sub-classes of WiresBaseTreeNode WiresBaseShape.this.getLayer().batch(); } @Override public void onClose(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { //Do nothing } }); } @Override public void hideControls() { if (controls == null || controls.isEmpty()) { return; } if (!isControlsVisible) { return; } if (animationHandle != null) { animationHandle.stop(); } isControlsVisible = false; animationHandle = animate(AnimationTweener.EASE_OUT, new AnimationProperties(), ANIMATION_DURATION, new IAnimationCallback() { private final AnimationTweener tweener = AnimationTweener.TweenerBuilder.MAKE_EASE_IN(3.0); private final Map<Group, Pair<Point2D, Point2D>> transformations = new HashMap<Group, Pair<Point2D, Point2D>>(); @Override public void onStart(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { //Store required transformations: Control, Current location, Target location transformations.clear(); for (int index = 0; index < controls.size(); index++) { final Group ctrl = controls.get(index); final Point2D origin = ctrl.getLocation(); origin.minus(WiresBaseShape.this.getLocation()); final Point2D target = new Point2D(0, 0); transformations.put(ctrl, new Pair<Point2D, Point2D>(origin, target)); } } @Override public void onFrame(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { final double pct = tweener.apply(iAnimation.getPercent() > 1.0 ? 1.0 : iAnimation.getPercent()); //Move each Control along the line between its origin and the target destination for (Map.Entry<Group, Pair<Point2D, Point2D>> e : transformations.entrySet()) { final Point2D origin = e.getValue().getK1(); final Point2D target = e.getValue().getK2(); final double dx = (target.getX() - origin.getX()) * pct; final double dy = (target.getY() - origin.getY()) * pct; e.getKey().setLocation(new Point2D(origin.getX() + dx, origin.getY() + dy).add(WiresBaseShape.this.getLocation())); } for (Group ctrl : controls) { ctrl.setAlpha(1.0 - iAnimation.getPercent()); } //Without this call Lienzo doesn't update the Canvas for sub-classes of WiresBaseTreeNode WiresBaseShape.this.getLayer().batch(); } @Override public void onClose(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { for (Group ctrl : controls) { WiresBaseShape.this.getLayer().remove(ctrl); } } }); } @Override public void addControl(final Group ctrlToAdd) { if (!isControlsVisible) { controls.add(ctrlToAdd); return; } final List<Group> newControls = new ArrayList<Group>(controls); newControls.add(ctrlToAdd); setControls(newControls); } @Override public void removeControl(final Group ctrlToRemove) { if (!isControlsVisible) { controls.remove(ctrlToRemove); return; } final List<Group> newControls = new ArrayList<Group>(controls); newControls.remove(ctrlToRemove); setControls(newControls); } @Override public void setControls(final List<Group> newControls) { if (!isControlsVisible) { controls.clear(); controls.addAll(newControls); return; } if (animationHandle != null) { animationHandle.stop(); } animationHandle = animate(AnimationTweener.EASE_OUT, new AnimationProperties(), ANIMATION_DURATION, new IAnimationCallback() { private final List<Group> controlsToAdd = new ArrayList<Group>(); private final List<Group> controlsToRemove = new ArrayList<Group>(); private final List<Group> controlsToRemain = new ArrayList<Group>(); private final AnimationTweener tweener = AnimationTweener.TweenerBuilder.MAKE_ELASTIC(1); private final Map<Group, Pair<Point2D, Point2D>> transformations = new HashMap<Group, Pair<Point2D, Point2D>>(); @Override public void onStart(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { //Initialise new Controls being added controlsToAdd.clear(); controlsToAdd.addAll(newControls); controlsToAdd.removeAll(controls); for (Group ctrl : controlsToAdd) { ctrl.setLocation(new Point2D(0, 0)); ctrl.setAlpha(0.0); WiresBaseShape.this.getLayer().add(ctrl); } //Initialise new Controls being removed controlsToRemove.clear(); controlsToRemove.addAll(controls); controlsToRemove.removeAll(newControls); //Initialise remaining Controls controlsToRemain.clear(); controlsToRemain.addAll(controls); controlsToRemain.removeAll(controlsToAdd); controlsToRemain.removeAll(controlsToRemove); //Store required transformations: Control, Current location, Target location controls.clear(); controls.addAll(newControls); transformations.clear(); for (Group ctrl : controlsToAdd) { final Point2D origin = new Point2D(0, 0); final Point2D target = getControlTarget(ctrl); transformations.put(ctrl, new Pair<Point2D, Point2D>(origin, target)); } for (Group ctrl : controlsToRemove) { final Point2D origin = ctrl.getLocation(); origin.minus(WiresBaseShape.this.getLocation()); final Point2D target = new Point2D(0, 0); transformations.put(ctrl, new Pair<Point2D, Point2D>(origin, target)); } for (Group ctrl : controlsToRemain) { final Point2D origin = ctrl.getLocation(); origin.minus(WiresBaseShape.this.getLocation()); final Point2D target = getControlTarget(ctrl); transformations.put(ctrl, new Pair<Point2D, Point2D>(origin, target)); } } @Override public void onFrame(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { final double pct = tweener.apply(iAnimation.getPercent() > 1.0 ? 1.0 : iAnimation.getPercent()); //Move each Control along the line between its origin and the target destination for (Map.Entry<Group, Pair<Point2D, Point2D>> e : transformations.entrySet()) { final Point2D origin = e.getValue().getK1(); final Point2D target = e.getValue().getK2(); final double dx = (target.getX() - origin.getX()) * pct; final double dy = (target.getY() - origin.getY()) * pct; e.getKey().setLocation(new Point2D(origin.getX() + dx, origin.getY() + dy).add(WiresBaseShape.this.getLocation())); } for (Group ctrl : controlsToAdd) { ctrl.setAlpha(pct); } for (Group ctrl : controlsToRemove) { ctrl.setAlpha(1.0 - pct); } //Without this call Lienzo doesn't update the Canvas for sub-classes of WiresBaseTreeNode WiresBaseShape.this.getLayer().batch(); } @Override public void onClose(final IAnimation iAnimation, final IAnimationHandle iAnimationHandle) { isControlsVisible = !controls.isEmpty(); for (Group ctrl : controlsToRemove) { WiresBaseShape.this.getLayer().remove(ctrl); } } }); } @Override public boolean isControlsVisible() { return this.isControlsVisible; } /** * Returns a Point (relative to the WiresShape) where a Control should be placed when Controls * are shown. This default implementation places all Controls at WiresShape.getX()+100 and evenly * spaces the Controls vertically with centres spaced 30px * @param ctrl The Control to position * @return The position of the Control */ protected Point2D getControlTarget(final Group ctrl) { final int offsetY = -((controls.size() - 1) * DEFAULT_CONTROL_SPACING) / 2; final Point2D target = new Point2D(DEFAULT_CONTROL_POSITION_X_OFFSET, DEFAULT_CONTROL_POSITION_Y_OFFSET + offsetY + (controls.indexOf(ctrl) * DEFAULT_CONTROL_SPACING)); return target; } @Override public void destroy() { if (isControlsVisible) { for (Group ctrl : controls) { getLayer().remove(ctrl); } isControlsVisible = false; } Layer layer = getLayer(); layer.remove(this); layer.batch(); } //Move the Controls to match where the descendant has been moved private void updateControlLocations() { if (controls == null) { return; } if (!isControlsVisible) { return; } for (Group ctrl : controls) { final Point2D target = getControlTarget(ctrl).add(WiresBaseShape.this.getLocation()); ctrl.setLocation(target); } } //Move the Controls to match where the descendant has been moved private void updateControlLocations(final double dx, final double dy) { if (controls == null) { return; } if (!isControlsVisible) { return; } for (Group ctrl : controls) { ctrl.setLocation(ctrl.getLocation().add(new Point2D(dx, dy))); } } @Override public Group setX(final double x) { final double dx = x - super.getX(); final Group g = super.setX(x); updateControlLocations(dx, 0); return g; } @Override public Group setY(final double y) { final double dy = y - super.getY(); final Group g = super.setY(y); updateControlLocations(0, dy); return g; } }