Commit 78ca958e authored by Nico Mack's avatar Nico Mack

Coronas are now tetherable

Added ShapeUtils class
parent 3970bcd1
......@@ -123,6 +123,7 @@ OBJECT_NODE=object
OBJECTS_NODE=objects
OPACITY_NODE=opacity
ORIGIN_NODE=origin
ORIGIN_OFFSET_NODE=originOffset
OUTER_RADIUS_NODE=outerRadius
PAGE_NODE=page
PATH_NODE=path
......
......@@ -158,6 +158,7 @@ public class Externalization extends NLS {
public static String OBJECTS_NODE;
public static String OPACITY_NODE;
public static String ORIGIN_NODE;
public static String ORIGIN_OFFSET_NODE;
public static String OUTER_RADIUS_NODE;
public static String PAGE_NODE;
public static String PATH_NODE;
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, version 3 of the
* License.
*
* TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.utility;
import java.awt.Shape;
import java.awt.geom.PathIterator;
/**
* @author mack
* @since [major].[minor]
* @version [major].[minor].[micro]
*/
public class ShapeUtils {
// ***************************************************************************
// * Constants *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
private ShapeUtils() {
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param shape
* @return
*/
// ---------------------------------------------------------------------------
public static Point centroidOf(Shape shape) {
final double flatness = 0.1;
double coords[] = new double[6];
double centroidX = 0;
double centroidY = 0;
double x0 = Double.NaN;
double y0 = Double.NaN;
double x1 = 0;
double y1 = 0;
double partialArea = 0;
double signedArea = 0;
PathIterator pi = shape.getPathIterator(null, flatness);
while (!pi.isDone()) {
int s = pi.currentSegment(coords);
switch (s) {
case PathIterator.SEG_MOVETO:
if (Double.isNaN(x0)) {
x0 = coords[0];
y0 = coords[1];
}
break;
case PathIterator.SEG_LINETO:
x1 = coords[0];
y1 = coords[1];
partialArea = x0 * y1 - x1 * y0;
signedArea += partialArea;
centroidX += (x0 + x1) * partialArea;
centroidY += (y0 + y1) * partialArea;
x0 = x1;
y0 = y1;
break;
case PathIterator.SEG_CLOSE:
// Ignore
break;
case PathIterator.SEG_QUADTO:
throw new AssertionError(
"SEG_QUADTO in flattening path iterator"); //$NON-NLS-1$
case PathIterator.SEG_CUBICTO:
throw new AssertionError(
"SEG_CUBICTO in flattening path iterator"); //$NON-NLS-1$
}
pi.next();
}
signedArea *= 0.5;
centroidX /= (6d * signedArea);
centroidY /= (6d * signedArea);
return new Point((float) centroidX, (float) centroidY, 0f, ScreenCoordinates.class);
}
}
......@@ -264,6 +264,28 @@ public class ModalWidget extends TetherableWidget {
return coronaBundles.get(bundle);
}
// ---------------------------------------------------------------------------
/**
* Method for retrieving all bundled coronas of a particular class.
*
* @param <T>
* The type of the class of bundled corona to retrieve.
* @param clazz
* The class of corona's to retrieve.
* @return A list of coronas matching the criterion.
*/
// ---------------------------------------------------------------------------
public <T> List<T> getBundledCoronas(Class<T> clazz) {
List<T> results = new ArrayList<>();
for (CoronaBundle bundle : coronaBundles.values()) {
results.addAll(bundle.getCoronas(clazz));
}
return results;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class *
......
......@@ -25,14 +25,20 @@ import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.bootstrapping.BootstrapCallback;
import lu.list.itis.dkd.tui.bootstrapping.BootstrapContext;
import lu.list.itis.dkd.tui.bootstrapping.BootstrappingUtils;
import lu.list.itis.dkd.tui.bootstrapping.CoordinateStateBootstrapper;
import lu.list.itis.dkd.tui.bootstrapping.ScriptBootstrapper;
import lu.list.itis.dkd.tui.bootstrapping.ShapeBootstrapper;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.scripting.Script;
import lu.list.itis.dkd.tui.utility.CoordinateState;
import lu.list.itis.dkd.tui.utility.Externalization;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
import lu.list.itis.dkd.tui.widget.corona.Corona;
import com.google.common.base.Strings;
import com.jgoodies.common.base.Preconditions;
import org.jdom2.Element;
import java.awt.Shape;
......@@ -95,6 +101,13 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
/** */
public List<Script> scripts = null;
public double tetheringDistance;
public Point originOffset = new Point();
public boolean rotatesWithTether = false;
public boolean exclusive = true;
public List<String> providers = new ArrayList<>();
public List<String> receivers = new ArrayList<>();
/**
......@@ -181,8 +194,78 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
}
}
}
Element tetherableNode = rootElement.getChild(Externalization.TETHERABLE_NODE);
if (null != tetherableNode) {
this.originOffset = new Point(tetherableNode.getChild(Externalization.ORIGIN_OFFSET_NODE), context, callback);
Element distanceNode = tetherableNode.getChild(Externalization.DISTANCE_NODE);
if (distanceNode != null) {
this.tetheringDistance = this.buildDistance(distanceNode, context, callback);
}
Element providersNode = tetherableNode.getChild(Externalization.PROVIDERS_NODE);
this.providers = this.buildProviders(providersNode, context, callback);
Element receiversNode = tetherableNode.getChild(Externalization.RECEIVERS_NODE);
this.receivers = this.buildReceivers(receiversNode, context, callback);
this.exclusive = BootstrappingUtils.getContentAsBoolean(tetherableNode, Externalization.EXCLUSIVE_NODE, BootstrappingUtils.OPTIONAL, false, context);
this.rotatesWithTether = BootstrappingUtils.getContentAsBoolean(tetherableNode, Externalization.ROTATE_WITH_TETHER_NODE, BootstrappingUtils.OPTIONAL, false, context);
}
}
// ---------------------------------------------------------------------------
private double buildDistance(@Nullable Element distanceNode, BootstrapContext context, BootstrapCallback callback) throws BuildException {
Preconditions.checkNotNull(distanceNode, "Distance node MUST not be null!"); //$NON-NLS-1$
double distance = BootstrappingUtils.getContentAsDouble(distanceNode, null, BootstrappingUtils.MANDATORY, null, context);
Point convertor = new Point(ScreenCoordinates.class);
CoordinateState coordinates;
String state = BootstrappingUtils.getContentAsString(distanceNode, Externalization.STATE_NODE, BootstrappingUtils.OPTIONAL, null, context);
if (!Strings.isNullOrEmpty(state)) {
coordinates = CoordinateStateBootstrapper.getCoordinateState(distanceNode, convertor, context, callback);
} else {
coordinates = new ScreenCoordinates(convertor, 0, 0);
}
convertor.setState(coordinates);
convertor.setLocation(distance, distance);
convertor.toCoordinates(ScreenCoordinates.class);
distance = convertor.getX();
return distance;
}
// ---------------------------------------------------------------------------
private List<String> buildProviders(@Nullable Element providersNode, BootstrapContext context, BootstrapCallback callback) throws BuildException {
List<String> providers = new ArrayList<>();
if (providersNode != null) {
for (Element providerNode : providersNode.getChildren(Externalization.PROVIDER_NODE)) {
providers.add(BootstrappingUtils.getContentAsString(providerNode, null, BootstrappingUtils.MANDATORY, null, context));
}
}
return providers;
}
// ---------------------------------------------------------------------------
private List<String> buildReceivers(@Nullable Element receiversNode, BootstrapContext context, BootstrapCallback callback) throws BuildException {
List<String> receivers = new ArrayList<>();
if (receiversNode != null) {
for (Element providerNode : receiversNode.getChildren(Externalization.RECEIVER_NODE)) {
receivers.add(BootstrappingUtils.getContentAsString(providerNode, null, BootstrappingUtils.MANDATORY, null, context));
}
}
return receivers;
}
// ---------------------------------------------------------------------------
/**
* Method used to set the initialTranslation of the corona from its centre.
*
......@@ -191,6 +274,8 @@ public abstract class CoronaBuilder<B extends CoronaBuilder<B>> {
* values of the centre.
* @return An instance of the builder for chain calling.
*/
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
public B withInitialTranslation(Point translation) {
this.initialTranslation = translation;
......
......@@ -35,6 +35,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
......@@ -59,6 +60,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class TetherManager {
private Tetherable tetherable;
private Point originOffset;
private Map<Tetherable, Tether> potentialTethers;
private Map<Tetherable, Tether> currentlyTethered;
private boolean exclusive;
......@@ -91,6 +93,7 @@ public class TetherManager {
* @param exclusive
*/
public TetherManager(Tetherable tetherable, boolean exclusive) {
this.originOffset = null;
this.currentlyTethered = new ConcurrentHashMap<>();
this.potentialTethers = new ConcurrentHashMap<>();
this.tetherable = tetherable;
......@@ -111,6 +114,34 @@ public class TetherManager {
return distance;
}
// ---------------------------------------------------------------------------
/**
* Computes the absolute drawing point in screen coordinates, taking into account the coronas'
* center, its initial translation as well as the optional center of the asset to be drawn.
*
* @param assetCentre
* specifies the optional center of the asset to be drawn. Specify <code>null</code> if not
* required
* @return a point expressed in absolute screen coordinates
*/
// ---------------------------------------------------------------------------
private Point getTetherOrigin(Point newPosition) {
Point tetherOrigin = new Point(ScreenCoordinates.class);
if (this.originOffset != null) {
Point position = newPosition.toCoordinates(ScreenCoordinates.class);
AffineTransform originTransform = AffineTransform.getTranslateInstance(position.x, position.y);
originTransform.rotate(newPosition.getAngle() + this.originOffset.getAngle());
originTransform.translate(this.originOffset.x, this.originOffset.y);
originTransform.transform(tetherOrigin, tetherOrigin);
} else {
tetherOrigin = newPosition.toCoordinates(ScreenCoordinates.class);
}
return tetherOrigin;
}
// ---------------------------------------------------------------------------
/**
* Calculates the intersection of a line given by points a and b with a circle at position origin
......@@ -289,6 +320,17 @@ public class TetherManager {
return this.exclusive;
}
// ---------------------------------------------------------------------------
/**
* Sets the origin offset to be applied to the new position specified when calling the move method.
*
* @param offset
*/
// ---------------------------------------------------------------------------
public void setOriginOffset(Point offset) {
this.originOffset = offset;
}
// ---------------------------------------------------------------------------
/**
* Sets the list of potential providers for tethers managed by this instance.
......@@ -715,9 +757,9 @@ public class TetherManager {
public List<Tether> move(Point newPosition) {
List<Tether> draggedTethers = null;
Point tetherOrigin = newPosition.toScreenCoordinates();
Point tetherOrigin = this.getTetherOrigin(newPosition); // newPosition.toScreenCoordinates();
this.tetherable.setTetherOrigin(newPosition);
this.tetherable.setTetherOrigin(tetherOrigin);
double shortestTether = this.shortestTether();
double tetheringDistance = this.tetherable.getTetheringDistance();
double angle = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment