/* * Copyright 2017 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.client.lienzo.shape.view.ext; import com.ait.lienzo.client.core.shape.Group; import com.ait.lienzo.client.core.shape.IPrimitive; import com.ait.lienzo.client.core.shape.MultiPath; import com.ait.lienzo.client.core.shape.Shape; import com.ait.lienzo.client.core.shape.wires.ControlHandleList; import com.ait.lienzo.client.core.shape.wires.IControlHandle; import com.ait.lienzo.client.core.shape.wires.LayoutContainer; import com.ait.lienzo.client.core.shape.wires.WiresShapeControlHandleList; import com.ait.lienzo.client.core.types.BoundingBox; import com.ait.lienzo.shared.core.types.ColorName; import org.kie.workbench.common.stunner.core.client.shape.view.HasSize; import org.kie.workbench.common.stunner.core.client.shape.view.event.ViewEventType; /** * This Shape is an extension of WiresShapeViewExt, but instead * of the need of a multipath instance for constructing it, which is being used * for attaching the different control points to the view instance, it only * requires a single Lienzo shape instance that is being wrapped by an instance * of a multi-path. * So the internal multi-path instance "decorates" the given shape and childre, if * any, by providing a non visible square in which the different control points. * <p> * This way any kind of path or primitive instance can be added * as child for this shape and even if that instance cannot be resized * or supports some control point handler, due to it's state, the already * provided multi-path instance is used for these goals. * <p> * When scaling this shape, it scales all children for fitting the given * new size, and the multi-path instance is rebuild to provide the right * magnets and control points. */ public class DecoratedShapeView<T extends WiresShapeViewExt> extends WiresShapeViewExt<T> implements HasSize<T> { private final Shape<?> theShape; protected final Group transformableContainer = new Group(); protected double width = 0; protected double height = 0; public DecoratedShapeView(final ViewEventType[] supportedEventTypes, final LayoutContainer layoutContainer, final Shape<?> theShape, final double width, final double height) { super(supportedEventTypes, setupDecorator(new MultiPath(), 0, 0, width, height), layoutContainer); this.theShape = theShape; this.theShape.setFillBoundsForSelection(true); initializeHandlerManager(getGroup(), theShape, supportedEventTypes); initializeTextView(); getGroup().add(transformableContainer); transformableContainer.add(theShape); resize(0, 0, width, height, false); } @Override public Shape<?> getShape() { return theShape; } @Override public Shape<?> getAttachableShape() { return theShape; } public DecoratedShapeView addScalableChild(final IPrimitive<?> child) { transformableContainer.add(child); return this; } @Override @SuppressWarnings("unchecked") public T setSize(final double width, final double height) { resize(0, 0, width, height, true); return (T) this; } @Override protected WiresShapeControlHandleList createControlHandles(final IControlHandle.ControlHandleType type, final ControlHandleList controls) { return new DecoratedWiresShapeControlHandleList(type, controls); } @Override protected void initialize(final ViewEventType[] supportedEventTypes) { // Initialize handlers for the primitive shape instead that on the path, as parent does. } void resize(final double x, final double y, final double width, final double height, final boolean refresh) { // Avoid recurrent calls, if any. if (this.width != width || this.height != height) { this.width = width; this.height = height; final BoundingBox bb = transformableContainer.getBoundingBox(); final double sx = width / bb.getWidth(); final double sy = height / bb.getHeight(); setupDecorator(getPath(), x, y, width, height); transformableContainer.setX(x).setY(y).setScale(sx, sy); } if (refresh) { refresh(); } } private final class DecoratedWiresShapeControlHandleList extends WiresShapeControlHandleList { public DecoratedWiresShapeControlHandleList(final IControlHandle.ControlHandleType controlsType, final ControlHandleList controls) { super(DecoratedShapeView.this, controlsType, controls); } @Override protected void resize(final Double x, final Double y, final double width, final double height, final boolean refresh) { super.resize(x, y, width, height, refresh); DecoratedShapeView.this.resize(null != x ? x : 0, null != y ? y : 0, width, height, false); } } private static MultiPath setupDecorator(final MultiPath path, final double x, final double y, final double width, final double height) { return path.clear().rect(x, y, width, height) .setStrokeColor(ColorName.BLACK) .setStrokeAlpha(0) .setFillAlpha(0.001); } }