diff --git a/common/src/main/java/net/rptools/lib/GeometryUtil.java b/common/src/main/java/net/rptools/lib/GeometryUtil.java
index a77e46e61e..4a2010cf36 100644
--- a/common/src/main/java/net/rptools/lib/GeometryUtil.java
+++ b/common/src/main/java/net/rptools/lib/GeometryUtil.java
@@ -16,29 +16,17 @@
import java.awt.Shape;
import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.locationtech.jts.algorithm.InteriorPointArea;
import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.algorithm.PointLocation;
import org.locationtech.jts.awt.ShapeReader;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.CoordinateArrays;
-import org.locationtech.jts.geom.Envelope;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.Location;
-import org.locationtech.jts.geom.MultiPolygon;
-import org.locationtech.jts.geom.Polygon;
-import org.locationtech.jts.geom.PrecisionModel;
+import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.util.GeometryFixer;
import org.locationtech.jts.operation.valid.IsValidOp;
import org.locationtech.jts.precision.GeometryPrecisionReducer;
@@ -91,8 +79,8 @@ public static Area union(Collection areas) {
}
/**
- * Like {@link #union(java.util.Collection)}, but will modify the areas and collection for
- * performance gains.
+ * Like {@link #union(Collection)}, but will modify the areas and collection for performance
+ * gains.
*
* @param areas The areas to union.
* @return The union of {@code areas}
@@ -137,6 +125,62 @@ public static MultiPolygon toJts(Shape shape) {
return geometry;
}
+ /**
+ * Use for a simple shape that is a closed polygon without holes.
+ *
+ * @param shape a closed polygon
+ * @return LinearRing geometry
+ */
+ public static LinearRing shapeToLinearRing(Shape shape) {
+ List coordinates = new ArrayList<>();
+ final PathIterator iterator = shape.getPathIterator(null);
+ final double[] pathCoordinate = new double[2];
+ iterator.currentSegment(pathCoordinate);
+ coordinates.add(new CoordinateXY(pathCoordinate[0], pathCoordinate[1]));
+ iterator.next();
+ while (!iterator.isDone()) {
+ iterator.currentSegment(pathCoordinate);
+ coordinates.add(new CoordinateXY(pathCoordinate[0], pathCoordinate[1]));
+ iterator.next();
+ }
+ coordinates.add(coordinates.getFirst()); // close the polygon
+ return getGeometryFactory().createLinearRing(coordinates.toArray(Coordinate[]::new));
+ }
+
+ /**
+ * Converts a line2D to the jts geometry LinearString
+ *
+ * @param line2D line to convert
+ * @return LinearString geometry
+ */
+ public static LineString line2DToLinearString(final Line2D line2D) {
+ return getGeometryFactory()
+ .createLineString(
+ new Coordinate[] {
+ GeometryUtil.point2DToCoordinate(line2D.getP1()),
+ GeometryUtil.point2DToCoordinate(line2D.getP2())
+ });
+ }
+
+ /**
+ * Find the points of intersection between a line and a shape
+ *
+ * @param line the intersecting line
+ * @param shape the shape to intersect
+ * @return Array of intersecting points
+ */
+ public static Point2D[] lineSegmentShapeIntersection(final Line2D line, final Shape shape) {
+ LineString lineString = line2DToLinearString(line);
+ LinearRing linearRing = shapeToLinearRing(shape);
+ Geometry intersection = lineString.intersection(linearRing);
+ if (intersection.getNumPoints() > 0) {
+ return Arrays.stream(intersection.getCoordinates())
+ .map(GeometryUtil::coordinateToPoint2D)
+ .toArray(Point2D[]::new);
+ }
+ return new Point2D.Double[] {};
+ }
+
public static Collection toJtsPolygons(Shape shape) {
if (shape instanceof Area area && area.isEmpty()) {
return Collections.emptyList();
diff --git a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java
index ae45d0de35..9ea8ce996c 100644
--- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java
+++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java
@@ -1831,7 +1831,7 @@ private void doDragTo(ZonePoint newAnchorPoint) {
}
// Don't bother if there isn't any movement
- if (!renderer.hasMoveSelectionSetMoved(tokenBeingDragged.getId(), newAnchorPoint)) {
+ if (renderer.isMoveSelectionSetUnchanged(tokenBeingDragged.getId(), newAnchorPoint)) {
return;
}
diff --git a/src/main/java/net/rptools/maptool/client/tool/StampTool.java b/src/main/java/net/rptools/maptool/client/tool/StampTool.java
index 9da5f9e942..89662cc690 100644
--- a/src/main/java/net/rptools/maptool/client/tool/StampTool.java
+++ b/src/main/java/net/rptools/maptool/client/tool/StampTool.java
@@ -1206,7 +1206,7 @@ public void moveByKey(int dx, int dy, boolean micro) {
private void doDragTo(ZonePoint newAnchorPoint) {
// Don't bother if there isn't any movement
- if (!renderer.hasMoveSelectionSetMoved(tokenBeingDragged.getId(), newAnchorPoint)) {
+ if (renderer.isMoveSelectionSetUnchanged(tokenBeingDragged.getId(), newAnchorPoint)) {
return;
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/BooleanTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/BooleanTokenOverlay.java
index 6bacee5fac..f2258690c2 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/BooleanTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/BooleanTokenOverlay.java
@@ -14,7 +14,6 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import net.rptools.maptool.model.Token;
@@ -52,11 +51,6 @@ protected BooleanTokenOverlay(String aName) {
@Override
public void paintOverlay(Graphics2D g, Token token, Rectangle bounds, Object value) {
if (FunctionUtil.getBooleanValue(value)) {
- // Apply Alpha Transparency
- float opacity = token.getTokenOpacity();
- if (opacity < 1.0f)
- g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
-
paintOverlay(g, token, bounds);
}
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/ColorDotTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/ColorDotTokenOverlay.java
index 939044a5f1..13e24ed803 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/ColorDotTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/ColorDotTokenOverlay.java
@@ -14,9 +14,7 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
@@ -78,7 +76,6 @@ public Object clone() {
public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
Color tempColor = g.getColor();
Stroke tempStroke = g.getStroke();
- Composite tempComposite = g.getComposite();
try {
g.setColor(getColor());
g.setStroke(getStroke());
@@ -101,14 +98,10 @@ public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
break;
} // endswitch
Shape s = new Ellipse2D.Double(x, y, size, size);
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
g.fill(s);
} finally {
g.setColor(tempColor);
g.setStroke(tempStroke);
- g.setComposite(tempComposite);
}
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/CrossTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/CrossTokenOverlay.java
index 6e424262c4..b29ad7c474 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/CrossTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/CrossTokenOverlay.java
@@ -14,9 +14,7 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
@@ -74,17 +72,12 @@ public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
g.setColor(getColor());
Stroke tempStroke = g.getStroke();
g.setStroke(getStroke());
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
g.draw(
new Line2D.Double(0, (double) bounds.height / 2, bounds.width, (double) bounds.height / 2));
g.draw(
new Line2D.Double((double) bounds.width / 2, 0, (double) bounds.width / 2, bounds.height));
g.setColor(tempColor);
g.setStroke(tempStroke);
- g.setComposite(tempComposite);
}
public static CrossTokenOverlay fromDto(BooleanTokenOverlayDto dto) {
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/DiamondTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/DiamondTokenOverlay.java
index 6faf5a9dd8..aef6f46e32 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/DiamondTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/DiamondTokenOverlay.java
@@ -14,9 +14,7 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
@@ -76,17 +74,13 @@ public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
g.setColor(getColor());
Stroke tempStroke = g.getStroke();
g.setStroke(getStroke());
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
+
g.draw(new Line2D.Double(0, vc, hc, 0));
g.draw(new Line2D.Double(hc, 0, bounds.width, vc));
g.draw(new Line2D.Double(bounds.width, vc, hc, bounds.height));
g.draw(new Line2D.Double(hc, bounds.height, 0, vc));
g.setColor(tempColor);
g.setStroke(tempStroke);
- g.setComposite(tempComposite);
}
public static DiamondTokenOverlay fromDto(BooleanTokenOverlayDto dto) {
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/FlowColorDotTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/FlowColorDotTokenOverlay.java
index c4558d5255..1326e82970 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/FlowColorDotTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/FlowColorDotTokenOverlay.java
@@ -14,9 +14,7 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
@@ -91,19 +89,14 @@ protected TokenOverlayFlow getFlow() {
public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
Color tempColor = g.getColor();
Stroke tempStroke = g.getStroke();
- Composite tempComposite = g.getComposite();
try {
g.setColor(getColor());
g.setStroke(getStroke());
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
Shape s = getShape(bounds, aToken);
g.fill(s);
} finally {
g.setColor(tempColor);
g.setStroke(tempStroke);
- g.setComposite(tempComposite);
}
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/ImageTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/ImageTokenOverlay.java
index 55e6fa546b..d18d7d82ee 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/ImageTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/ImageTokenOverlay.java
@@ -89,12 +89,7 @@ public void paintOverlay(Graphics2D g, Token token, Rectangle bounds) {
int height = size.height;
int x = iBounds.x + (d.width - width) / 2;
int y = iBounds.y + (d.height - height) / 2;
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
g.drawImage(image, x, y, size.width, size.height, null);
- g.setComposite(tempComposite);
}
/**
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/MultipleImageBarTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/MultipleImageBarTokenOverlay.java
index cde12ee7b2..872638f636 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/MultipleImageBarTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/MultipleImageBarTokenOverlay.java
@@ -14,8 +14,6 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
-import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
@@ -98,13 +96,7 @@ public void paintOverlay(Graphics2D g, Token token, Rectangle bounds, double val
y = d.height - size.height;
}
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100) {
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
- }
g.drawImage(image, x, y, size.width, size.height, null);
- g.setComposite(tempComposite);
}
/**
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/OTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/OTokenOverlay.java
index 00fc54da28..e720f76ded 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/OTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/OTokenOverlay.java
@@ -14,9 +14,7 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
@@ -73,17 +71,12 @@ public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
g.setColor(getColor());
Stroke tempStroke = g.getStroke();
g.setStroke(getStroke());
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
double offset = getStroke().getLineWidth() / 2.0;
g.draw(
new Ellipse2D.Double(
0 + offset, 0 + offset, bounds.width - offset * 2, bounds.height - offset * 2));
g.setColor(tempColor);
g.setStroke(tempStroke);
- g.setComposite(tempComposite);
}
public static OTokenOverlay fromDto(BooleanTokenOverlayDto dto) {
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/ShadedTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/ShadedTokenOverlay.java
index ed904e503d..b392ef8e29 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/ShadedTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/ShadedTokenOverlay.java
@@ -14,9 +14,7 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import net.rptools.maptool.model.Token;
@@ -71,13 +69,8 @@ public ShadedTokenOverlay(String aName, Color aColor) {
public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
Color temp = g.getColor();
g.setColor(color);
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
g.fill(bounds);
g.setColor(temp);
- g.setComposite(tempComposite);
}
/**
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/SingleImageBarTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/SingleImageBarTokenOverlay.java
index 3b621ac125..ed33976d82 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/SingleImageBarTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/SingleImageBarTokenOverlay.java
@@ -14,8 +14,6 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
-import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
@@ -95,11 +93,6 @@ public void paintOverlay(Graphics2D g, Token token, Rectangle bounds, double val
y = d.height - size.height;
}
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100) {
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
- }
int width =
(getSide() == Side.TOP || getSide() == Side.BOTTOM)
? calcBarSize(image.getWidth(), value)
@@ -129,7 +122,6 @@ public void paintOverlay(Graphics2D g, Token token, Rectangle bounds, double val
image.getWidth(),
image.getHeight(),
null);
- g.setComposite(tempComposite);
}
/**
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/TriangleTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/TriangleTokenOverlay.java
index cf89173ecc..a470fa61a3 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/TriangleTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/TriangleTokenOverlay.java
@@ -14,9 +14,7 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
@@ -76,16 +74,11 @@ public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
g.setColor(getColor());
Stroke tempStroke = g.getStroke();
g.setStroke(getStroke());
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
g.draw(new Line2D.Double(0, vc, bounds.width, vc));
g.draw(new Line2D.Double(bounds.width, vc, hc, 0));
g.draw(new Line2D.Double(hc, 0, 0, vc));
g.setColor(tempColor);
g.setStroke(tempStroke);
- g.setComposite(tempComposite);
}
public static TriangleTokenOverlay fromDto(BooleanTokenOverlayDto dto) {
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/TwoImageBarTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/TwoImageBarTokenOverlay.java
index 0a51d722e7..998bc15459 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/TwoImageBarTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/TwoImageBarTokenOverlay.java
@@ -14,8 +14,6 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
-import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
@@ -102,11 +100,6 @@ public void paintOverlay(Graphics2D g, Token token, Rectangle bounds, double val
y = d.height - size.height;
}
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
-
int width =
(getSide() == Side.TOP || getSide() == Side.BOTTOM)
? calcBarSize(images[0].getWidth(), value)
@@ -141,7 +134,6 @@ public void paintOverlay(Graphics2D g, Token token, Rectangle bounds, double val
} else {
g.drawImage(images[0], x, y, x + screenWidth, y + screenHeight, 0, 0, width, height, null);
}
- g.setComposite(tempComposite);
}
/**
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/XTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/XTokenOverlay.java
index dd490186db..b40faccfd9 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/XTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/XTokenOverlay.java
@@ -14,10 +14,8 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
@@ -73,16 +71,10 @@ public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
g.setColor(color);
Stroke tempStroke = g.getStroke();
g.setStroke(stroke);
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100) {
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
- }
g.draw(new Line2D.Double(0, 0, bounds.width, bounds.height));
g.draw(new Line2D.Double(0, bounds.height, bounds.width, 0));
g.setColor(tempColor);
g.setStroke(tempStroke);
- g.setComposite(tempComposite);
}
/**
diff --git a/src/main/java/net/rptools/maptool/client/ui/token/YieldTokenOverlay.java b/src/main/java/net/rptools/maptool/client/ui/token/YieldTokenOverlay.java
index f810f6e5a8..1cf61113bc 100644
--- a/src/main/java/net/rptools/maptool/client/ui/token/YieldTokenOverlay.java
+++ b/src/main/java/net/rptools/maptool/client/ui/token/YieldTokenOverlay.java
@@ -14,9 +14,7 @@
*/
package net.rptools.maptool.client.ui.token;
-import java.awt.AlphaComposite;
import java.awt.Color;
-import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
@@ -76,16 +74,11 @@ public void paintOverlay(Graphics2D g, Token aToken, Rectangle bounds) {
g.setColor(getColor());
Stroke tempStroke = g.getStroke();
g.setStroke(getStroke());
- Composite tempComposite = g.getComposite();
- if (getOpacity() != 100)
- g.setComposite(
- AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) getOpacity() / 100));
g.draw(new Line2D.Double(0, vc, bounds.width, vc));
g.draw(new Line2D.Double(bounds.width, vc, hc, bounds.height));
g.draw(new Line2D.Double(hc, bounds.height, 0, vc));
g.setColor(tempColor);
g.setStroke(tempStroke);
- g.setComposite(tempComposite);
}
public static YieldTokenOverlay fromDto(BooleanTokenOverlayDto dto) {
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneViewModel.java b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneViewModel.java
index 778d5ca3b3..dcd2db4d5c 100644
--- a/src/main/java/net/rptools/maptool/client/ui/zone/ZoneViewModel.java
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneViewModel.java
@@ -161,6 +161,10 @@ public Rectangle2D getViewport() {
viewport.getMinX(), viewport.getMinY(), viewport.getWidth(), viewport.getHeight());
}
+ public boolean isUsingVision() {
+ return zoneView.isUsingVision();
+ }
+
public Area getVisibleArea() {
return visibleArea;
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/HaloRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/HaloRenderer.java
index f48b5dbf56..cb66277222 100644
--- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/HaloRenderer.java
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/HaloRenderer.java
@@ -30,6 +30,13 @@
public class HaloRenderer {
private final RenderHelper renderHelper;
private final Zone zone;
+ private float opacity = AppPreferences.haloOverlayOpacity.get() / 255f;
+ private float lineWeight = AppPreferences.haloLineWidth.get();
+
+ {
+ AppPreferences.haloOverlayOpacity.onChange(i -> opacity = i / 255f);
+ AppPreferences.haloLineWidth.onChange(i -> lineWeight = i);
+ }
// region These fields need to be recalculated whenever the grid changes.
@@ -69,16 +76,15 @@ private Shape getHaloShape(Grid grid) {
.createTransformedShape(cachedHaloShape);
}
}
-
return cachedHaloShape;
}
// Render Halos
- public void renderHalo(Graphics2D g2d, Token token, ZoneViewModel.TokenPosition position) {
+ public void renderHalo(Graphics2D g2d, ZoneViewModel.TokenPosition position) {
+ Token token = position.token();
if (token.getHaloColor() == null) {
return;
}
-
var grid = zone.getGrid();
if (grid == null) {
return;
@@ -105,29 +111,32 @@ public void renderHalo(Graphics2D g2d, Token token, ZoneViewModel.TokenPosition
position.transformedBounds().getBounds2D().getCenterX(),
position.transformedBounds().getBounds2D().getCenterY())
.createTransformedShape(paintShape);
-
// this will eventually hold forks for painting different types of halo
renderHelper.render(
g2d,
worldG -> {
- paintLineHalo(worldG, token, grid, positionedPaintShape);
+ paintLineHalo(worldG, position.token(), grid, positionedPaintShape);
});
}
private void paintLineHalo(Graphics2D g2d, Token token, Grid grid, Shape paintShape) {
+ Stroke oldStroke = g2d.getStroke();
+ g2d.setColor(token.getHaloColor());
// double width because we will clip the inside half
g2d.setStroke(
new BasicStroke(
- (float)
- (2f
- * Math.min(1f, token.getFootprint(grid).getScale())
- * AppPreferences.haloLineWidth.get())));
- g2d.setColor(token.getHaloColor());
+ (float) (2f * lineWeight * Math.min(1f, token.getFootprint(grid).getScale()))));
Shape oldClip = g2d.getClip();
+ Composite oldComposite = g2d.getComposite();
+ if (opacity < 1f) {
+ g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
+ }
Area a = new Area(g2d.getClipBounds());
a.subtract(new Area(paintShape));
g2d.setClip(a);
g2d.draw(paintShape);
g2d.setClip(oldClip);
+ g2d.setComposite(oldComposite);
+ g2d.setStroke(oldStroke);
}
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ItemRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ItemRenderer.java
index 9fa98abd4e..4b31ac4c0b 100644
--- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ItemRenderer.java
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ItemRenderer.java
@@ -16,7 +16,7 @@
import java.awt.*;
-interface ItemRenderer {
+public interface ItemRenderer {
public void render(Graphics2D g);
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LabelRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LabelRenderer.java
index 598e0c4be6..e3f1aa85a6 100644
--- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LabelRenderer.java
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/LabelRenderer.java
@@ -16,18 +16,34 @@
import java.awt.*;
import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
import javax.swing.*;
+import net.rptools.maptool.client.AppState;
+import net.rptools.maptool.client.MapTool;
import net.rptools.maptool.client.swing.ImageLabel;
+import net.rptools.maptool.client.ui.zone.ZoneViewModel;
import net.rptools.maptool.model.GUID;
import net.rptools.maptool.util.GraphicsUtil;
/** Represents a delayed label render */
-class LabelRenderer implements ItemRenderer {
+public class LabelRenderer implements ItemRenderer {
+
+ private static final Map labelCache = new HashMap<>();
+ private static final Map labelImageCache = new HashMap<>();
+
+ public static Map getLabelCache() {
+ return labelCache;
+ }
+
+ public static Map getLabelImageCache() {
+ return labelImageCache;
+ }
private final ZoneRenderer renderer;
private final String text;
private int x;
- private final int y;
+ private int y;
private final int align;
private final Color foreground;
private final ImageLabel background;
@@ -52,8 +68,8 @@ public LabelRenderer(ZoneRenderer renderer, String text, int x, int y, GUID tId)
this.foreground = Color.black;
tokenId = tId;
if (tokenId != null) {
- width = renderer.labelRenderingCache.get(tokenId).getWidth();
- height = renderer.labelRenderingCache.get(tokenId).getHeight();
+ width = labelImageCache.get(tokenId).getWidth();
+ height = labelImageCache.get(tokenId).getHeight();
}
}
@@ -87,11 +103,22 @@ public LabelRenderer(
this.background = background;
tokenId = tId;
if (tokenId != null) {
- width = renderer.labelRenderingCache.get(tokenId).getWidth();
- height = renderer.labelRenderingCache.get(tokenId).getHeight();
+ width = labelImageCache.get(tokenId).getWidth();
+ height = labelImageCache.get(tokenId).getHeight();
}
}
+ public static boolean isLabelVisible(
+ ZoneViewModel.TokenPosition position, ZoneViewModel viewModel, boolean hover) {
+ if (!(AppState.isShowTokenNames() || hover)) {
+ return false;
+ }
+ // if policy does not auto-reveal FoW, check if fog covers the token (slow)
+ return viewModel.getPlayerView().isGMView()
+ || (viewModel.isUsingVision() && MapTool.getServerPolicy().isAutoRevealOnMovement())
+ || viewModel.zone.isTokenVisible(position.token());
+ }
+
public void render(Graphics2D g) {
if (tokenId != null) { // Use cached image.
switch (align) {
@@ -104,7 +131,7 @@ public void render(Graphics2D g) {
case SwingUtilities.LEFT:
break;
}
- BufferedImage img = renderer.labelRenderingCache.get(tokenId);
+ BufferedImage img = labelImageCache.get(tokenId);
if (img != null) {
g.drawImage(img, x, y, width, height, null);
} else { // Draw as normal
@@ -114,4 +141,20 @@ public void render(Graphics2D g) {
GraphicsUtil.drawBoxedString(g, text, x, y, align, background, foreground);
}
}
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java
index b7c49a7658..dd0e0547a2 100644
--- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java
@@ -28,6 +28,7 @@
import java.awt.font.TextLayout;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
+import java.io.Serial;
import java.text.NumberFormat;
import java.util.*;
import java.util.List;
@@ -51,12 +52,9 @@
import net.rptools.maptool.client.ui.Scale;
import net.rptools.maptool.client.ui.theme.Images;
import net.rptools.maptool.client.ui.theme.RessourceManager;
-import net.rptools.maptool.client.ui.token.AbstractTokenOverlay;
-import net.rptools.maptool.client.ui.token.BarTokenOverlay;
import net.rptools.maptool.client.ui.token.dialog.create.NewTokenDialog;
import net.rptools.maptool.client.ui.zone.*;
import net.rptools.maptool.client.ui.zone.gdx.GdxRenderer;
-import net.rptools.maptool.client.ui.zone.renderer.tokenRender.FacingArrowRenderer;
import net.rptools.maptool.client.ui.zone.renderer.tokenRender.TokenRenderer;
import net.rptools.maptool.client.walker.ZoneWalker;
import net.rptools.maptool.events.MapToolEventBus;
@@ -69,16 +67,17 @@
import net.rptools.maptool.model.zones.*;
import net.rptools.maptool.util.GraphicsUtil;
import net.rptools.maptool.util.ImageManager;
-import net.rptools.maptool.util.ImageSupport;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** */
public class ZoneRenderer extends JComponent implements DropTargetListener {
- private static final long serialVersionUID = 3832897780066104884L;
+ @Serial private static final long serialVersionUID = 3832897780066104884L;
private static final Logger log = LogManager.getLogger(ZoneRenderer.class);
+ private static final FlatImageLabelFactory IMAGE_LABEL_FACTORY = new FlatImageLabelFactory();
+
/** DebounceExecutor for throttling repaint() requests. */
private final DebounceExecutor repaintDebouncer;
@@ -109,9 +108,7 @@ public class ZoneRenderer extends JComponent implements DropTargetListener {
private final List showPathList = new ArrayList<>();
// Optimizations
- final Map labelRenderingCache = new HashMap<>();
private Token tokenUnderMouse;
-
private ScreenPoint pointUnderMouse;
private @Nonnull Zone.Layer activeLayer = Layer.getDefaultPlayerLayer();
@@ -134,9 +131,7 @@ public class ZoneRenderer extends JComponent implements DropTargetListener {
private final EnumSet disabledLayers = EnumSet.noneOf(Layer.class);
private final GridRenderer gridRenderer;
- private final HaloRenderer haloRenderer;
private final TokenRenderer tokenRenderer;
- private final FacingArrowRenderer facingArrowRenderer;
private final SelectionRenderer selectionRenderer;
private final LightsRenderer lightsRenderer;
private final DarknessRenderer darknessRenderer;
@@ -159,20 +154,19 @@ public ZoneRenderer(Zone zone) {
throw new IllegalArgumentException("Zone cannot be null");
}
this.zone = zone;
- selectionModel = new SelectionModel(zone);
- zoneView = new ZoneView(zone);
+ this.selectionModel = new SelectionModel(zone);
+ this.zoneView = new ZoneView(zone);
this.viewModel = new ZoneViewModel(zone, zoneView, selectionModel);
setZoneScale(new Scale());
- drawableRenderers =
+ this.drawableRenderers =
CollectionUtil.newFilledEnumMap(
Zone.Layer.class, layer -> new PartitionedDrawableRenderer(zone));
var renderHelper = new RenderHelper(this, tempBufferPool);
this.gridRenderer = new GridRenderer(this);
- this.haloRenderer = new HaloRenderer(renderHelper, zone);
+
this.tokenRenderer = new TokenRenderer(renderHelper, zone);
- this.facingArrowRenderer = new FacingArrowRenderer(renderHelper, zone);
this.selectionRenderer = new SelectionRenderer(renderHelper, viewModel, zoneView);
this.lightsRenderer = new LightsRenderer(renderHelper, zone, zoneView);
this.darknessRenderer = new DarknessRenderer(renderHelper, zoneView);
@@ -180,7 +174,7 @@ public ZoneRenderer(Zone zone) {
this.fogRenderer = new FogRenderer(renderHelper, zone, zoneView);
this.visionOverlayRenderer = new VisionOverlayRenderer(renderHelper, zone, zoneView);
this.debugRenderer = new DebugRenderer(renderHelper);
- repaintDebouncer =
+ this.repaintDebouncer =
new DebounceExecutor(1000 / AppPreferences.frameRateCap.get(), this::repaint);
setFocusable(true);
@@ -301,7 +295,7 @@ public void setMouseOver(Token token) {
if (tokenUnderMouse == token) {
return;
}
- tokenUnderMouse = token;
+ this.tokenUnderMouse = token;
repaintDebouncer.dispatch();
}
@@ -324,13 +318,13 @@ public void addMoveSelectionSet(String playerId, GUID keyToken, Set tokenL
return set.getKeyTokenDragAnchorPosition();
}
- public boolean hasMoveSelectionSetMoved(GUID keyToken, ZonePoint dragAnchorPosition) {
+ public boolean isMoveSelectionSetUnchanged(GUID keyToken, ZonePoint dragAnchorPosition) {
SelectionSet set = selectionSetMap.get(keyToken);
if (set == null) {
- return false;
+ return true;
}
- return !set.getKeyTokenDragAnchorPosition().equals(dragAnchorPosition);
+ return set.getKeyTokenDragAnchorPosition().equals(dragAnchorPosition);
}
public void updateMoveSelectionSet(GUID keyToken, ZonePoint latestPoint) {
@@ -554,15 +548,13 @@ public void centerOn(CellPoint point) {
}
/**
- * Remove the token from: {@link #labelRenderingCache}. Set the {@link #visibleScreenArea} to
- * null. Flush the token from {@link #zoneView}.
+ * Set the {@link #visibleScreenArea} to null. Flush the token from {@link #zoneView}.
*
* @param token the token to flush
*/
public void flush(Token token) {
// This method can be called from a non-EDT thread so if that happens, make sure we synchronize
// with the EDT.
- labelRenderingCache.remove(token.getId());
// This should be smarter, but whatever
visibleScreenArea = null;
@@ -684,8 +676,8 @@ public void enforceView(int x, int y, double scale, int gmWidth, int gmHeight) {
}
public void restoreView() {
- log.info("Restoring view: " + previousZonePoint);
- log.info("previousScale: " + previousScale);
+ log.info("Restoring view: {}", previousZonePoint);
+ log.info("previousScale: {}", previousScale);
centerOn(previousZonePoint);
setScale(previousScale);
@@ -987,7 +979,7 @@ public void renderZone(Graphics2D g2d, @Nullable PlayerView view) {
// (This method has its own 'timer' calls)
if (AppState.getShowTextLabels()) {
- renderLabels(g2d, view);
+ renderMapLabels(g2d, view);
}
this.fogRenderer.render(g2d, view);
@@ -1018,12 +1010,10 @@ public void renderZone(Graphics2D g2d, @Nullable PlayerView view) {
showBlockedMoves(g2d, view, getOwnedMovementSet(view));
timer.stop("owned movement");
- // Text associated with tokens being moved is added to a list to be drawn after, i.e. on top
- // of, the tokens themselves.
- // So if one moving token is on top of another moving token, at least the textual identifiers
- // will be visible.
+ // To ensure text associated with moving tokens is visible we add it to the delayed render
+ // list last so it is painted on top of other features.
timer.start("token name/labels");
- renderRenderables(g2d);
+ renderDelayedPaintRenderables(g2d);
timer.stop("token name/labels");
}
@@ -1053,10 +1043,12 @@ public void renderZone(Graphics2D g2d, @Nullable PlayerView view) {
}
private void delayRendering(ItemRenderer renderer) {
- itemRenderList.add(renderer);
+ if (!itemRenderList.contains(renderer)) {
+ itemRenderList.add(renderer);
+ }
}
- private void renderRenderables(Graphics2D g) {
+ private void renderDelayedPaintRenderables(Graphics2D g) {
for (ItemRenderer renderer : itemRenderList) {
renderer.render(g);
}
@@ -1070,7 +1062,7 @@ private void renderRenderables(Graphics2D g) {
*/
private final BufferedImagePool tempBufferPool = new BufferedImagePool(2);
- private void renderLabels(Graphics2D g, PlayerView view) {
+ private void renderMapLabels(Graphics2D g, PlayerView view) {
final var timer = CodeTimer.get();
timer.start("labels-1");
@@ -1087,7 +1079,7 @@ private void renderLabels(Graphics2D g, PlayerView view) {
var dim = fLabel.getDimensions(g, label.getLabel());
Rectangle bounds =
fLabel.render(
- g, (int) (sp.x - dim.width / 2), (int) (sp.y - dim.height / 2), label.getLabel());
+ g, (int) (sp.x - dim.width / 2d), (int) (sp.y - dim.height / 2d), label.getLabel());
labelLocationList.add(new LabelLocation(bounds, label));
timer.stop("labels-1.1");
}
@@ -1269,7 +1261,7 @@ protected void showBlockedMoves(Graphics2D g, PlayerView view, Set
newArea.transform(AffineTransform.getTranslateInstance(set.getOffsetX(), set.getOffsetY()));
var newPosition = new ZoneViewModel.TokenPosition(token, newBounds, newArea);
- tokenRenderer.renderToken(token, newPosition, g, 1);
+ tokenRenderer.renderToken(token, viewModel, newPosition, g, true, true, false);
// Other details.
// Only draw these if the token is visible on screen where it is dragged to.
@@ -1533,8 +1525,8 @@ public void renderPath(
}
p =
new ZonePoint(
- (int) (p.x + (footprintBounds.width / 2) * footprint.getScale()),
- (int) (p.y + (footprintBounds.height / 2) * footprint.getScale()));
+ (int) (p.x + (footprintBounds.width / 2d) * footprint.getScale()),
+ (int) (p.y + (footprintBounds.height / 2d) * footprint.getScale()));
highlightCell(g, p, RessourceManager.getImage(Images.ZONE_RENDERER_CELL_WAYPOINT), .333f);
}
timer.stop("renderPath-3");
@@ -1733,7 +1725,6 @@ protected void renderTokens(
final var timer = CodeTimer.get();
Graphics2D clippedG = g;
- var imageLabelFactory = new FlatImageLabelFactory();
boolean isGMView = view.isGMView(); // speed things up
@@ -1766,12 +1757,10 @@ protected void renderTokens(
position = viewModel.getTokenPositions().get(token.getId());
if (position == null) {
- // Unknown token?
- continue;
+ continue; // Unknown token?
}
if (!viewModel.getVisibleTokens(token.getLayer()).contains(token.getId())) {
- // Token not on screen or otherwise not visible.
- continue;
+ continue; // Token not on screen or otherwise not visible.
}
} finally {
timer.stop("token-list-1");
@@ -1788,8 +1777,10 @@ protected void renderTokens(
}
} else {
tokenG = (Graphics2D) g.create();
- AppPreferences.renderQuality.get().setRenderingHints(tokenG);
}
+ AppPreferences.renderQuality.get().setRenderingHints(tokenG);
+
+ boolean isHover = token == tokenUnderMouse;
// Previous path
timer.start("renderTokens:ShowPath");
@@ -1798,118 +1789,12 @@ protected void renderTokens(
}
timer.stop("renderTokens:ShowPath");
- timer.start("token-list-1b");
- // get token image, using image table if present
- BufferedImage image = ImageSupport.getTokenImage(token, this);
- timer.stop("token-list-1b");
-
- timer.start("token-list-5a");
- if (token.getIsFlippedIso() && getZone().getGrid().isIsometric()) {
- int newSize = (image.getWidth() + image.getHeight());
- token.setWidth(newSize);
- token.setHeight(newSize / 2);
- }
- timer.stop("token-list-5a");
-
- // Render Halo
- haloRenderer.renderHalo(tokenG, token, position);
-
- // Calculate alpha Transparency from token and use opacity to indicate that token is moving
- float opacity = token.getTokenOpacity();
- if (viewModel.isTokenMoving(token.getId())) {
- opacity = opacity / 2.0f;
- }
- // Finally render the token image
- timer.start("token-list-7");
- // Clipping is handled in the isTokenInNeedOfClipping() call far above.
- tokenRenderer.renderToken(token, position, tokenG, opacity);
- timer.stop("token-list-7");
-
- timer.start("token-list-8");
- // Facing
- facingArrowRenderer.paintArrow(tokenG, position);
- timer.stop("token-list-8");
-
- timer.start("token-list-9");
- // Set up the graphics so that the overlay can just be painted.
- Rectangle2D tokenBounds = zoneScale.toScreenSpace(position.transformedBounds().getBounds2D());
- Graphics2D locG =
- (Graphics2D)
- tokenG.create(
- (int) tokenBounds.getX(),
- (int) tokenBounds.getY(),
- (int) tokenBounds.getWidth(),
- (int) tokenBounds.getHeight());
- Rectangle bounds =
- new Rectangle(0, 0, (int) tokenBounds.getWidth(), (int) tokenBounds.getHeight());
-
- // Check each of the set values
- for (String state : MapTool.getCampaign().getTokenStatesMap().keySet()) {
- Object stateValue = token.getState(state);
- AbstractTokenOverlay overlay = MapTool.getCampaign().getTokenStatesMap().get(state);
- if (stateValue instanceof AbstractTokenOverlay) {
- overlay = (AbstractTokenOverlay) stateValue;
- }
- if (overlay == null
- || overlay.isMouseover() && token != tokenUnderMouse
- || !overlay.showPlayer(token, MapTool.getPlayer())) {
- continue;
- }
- overlay.paintOverlay(locG, token, bounds, stateValue);
- }
- timer.stop("token-list-9");
-
- timer.start("token-list-10");
-
- for (String bar : MapTool.getCampaign().getTokenBarsMap().keySet()) {
- Object barValue = token.getState(bar);
- BarTokenOverlay overlay = MapTool.getCampaign().getTokenBarsMap().get(bar);
- if (overlay == null
- || overlay.isMouseover() && token != tokenUnderMouse
- || !overlay.showPlayer(token, MapTool.getPlayer())) {
- continue;
- }
-
- overlay.paintOverlay(locG, token, bounds, barValue);
- }
- locG.dispose();
- timer.stop("token-list-10");
-
- timer.start("token-list-11");
- // Keep track of which tokens have been drawn for post-processing on them later
- // (such as selection borders and names/labels)
- if (getActiveLayer().equals(token.getLayer())) {
- tokenPostProcessing.add(position);
- }
- timer.stop("token-list-11");
- }
-
- // Selection and labels
- timer.start("token-list-12");
- for (ZoneViewModel.TokenPosition position : tokenPostProcessing) {
- var token = position.token();
-
- // Count moving tokens as "selected" so that a border is drawn around them.
- boolean isSelected =
- selectionModel.isSelected(token.getId()) || viewModel.isTokenMoving(token.getId());
- if (isSelected) {
- selectionRenderer.drawSelectBorder(clippedG, position);
- // Remove labels from the cache if the corresponding tokens are deselected
- } else if (!AppState.isShowTokenNames()) {
- labelRenderingCache.remove(token.getId());
- }
-
- // Token names and labels
- boolean showCurrentTokenLabel = AppState.isShowTokenNames() || token == tokenUnderMouse;
-
- // if policy does not auto-reveal FoW, check if fog covers the token (slow)
- if (showCurrentTokenLabel
- && !isGMView
- && (!zoneView.isUsingVision() || !MapTool.getServerPolicy().isAutoRevealOnMovement())
- && !zone.isTokenVisible(token)) {
- showCurrentTokenLabel = false;
- }
+ timer.start("renderTokens:LabelCheck");
+ // Token name and label
+ boolean showCurrentTokenLabel = LabelRenderer.isLabelVisible(position, viewModel, isHover);
+ timer.stop("renderTokens:LabelCheck");
if (showCurrentTokenLabel) {
+ timer.start("renderTokens:LabelBuild");
GUID tokId = token.getId();
int offset = 3; // Keep it from tramping on the token border.
ImageLabel background;
@@ -1931,10 +1816,10 @@ protected void renderTokens(
if (isGMView && token.getGMName() != null && !StringUtil.isEmpty(token.getGMName())) {
name += " (" + token.getGMName() + ")";
}
- if (!view.equals(lastView) || !labelRenderingCache.containsKey(tokId)) {
+ if (!view.equals(lastView) || !LabelRenderer.getLabelImageCache().containsKey(tokId)) {
boolean hasLabel = false;
- var flatImgLabel = imageLabelFactory.getMapImageLabel(token);
+ var flatImgLabel = IMAGE_LABEL_FACTORY.getMapImageLabel(token);
var nameDimension = flatImgLabel.getDimensions(g, name);
var labelDimension = new Dimension(0, 0);
@@ -1958,23 +1843,59 @@ protected void renderTokens(
token.getLabel());
}
flatImgLabel.render(gLabelRender, (width - nameDimension.width) / 2, 0, name);
-
// Add image to cache
- labelRenderingCache.put(tokId, labelRender);
+ LabelRenderer.getLabelImageCache().put(tokId, labelRender);
}
// Create LabelRenderer using cached label.
- Rectangle r =
- zoneScale.toScreenSpace(position.transformedBounds().getBounds2D()).getBounds();
- delayRendering(
- new LabelRenderer(
- this,
- name,
- r.x + r.width / 2,
- r.y + r.height + offset,
- SwingUtilities.CENTER,
- background,
- foreground,
- tokId));
+ Rectangle r = zoneScale.toScreenSpace(position.footprintBounds().getBounds2D()).getBounds();
+ LabelRenderer label = LabelRenderer.getLabelCache().get(tokId);
+ if (label != null) {
+ label.setX(r.x + r.width / 2);
+ label.setY(r.y + r.height + offset);
+ } else {
+ label =
+ new LabelRenderer(
+ this,
+ name,
+ r.x + r.width / 2,
+ r.y + r.height + offset,
+ SwingUtilities.CENTER,
+ background,
+ foreground,
+ tokId);
+ }
+ LabelRenderer.getLabelCache().put(tokId, label);
+ delayRendering(label);
+ timer.stop("renderTokens:LabelBuild");
+ } else {
+ LabelRenderer.getLabelCache().remove(token.getId());
+ }
+
+ // Render the token image and decorations
+ timer.start("token-list-7");
+ // Clipping is handled in the isTokenInNeedOfClipping() call far above.
+ boolean isSelected = selectionModel.isSelected(token.getId());
+ boolean isMoving = viewModel.isTokenMoving(token.getId());
+
+ tokenRenderer.renderToken(token, viewModel, position, tokenG, isSelected, isMoving, isHover);
+ tokenG.dispose();
+ timer.stop("token-list-7");
+
+ timer.start("token-list-11");
+ // Keep track of which tokens have been drawn for post-processing selection border
+ if (getActiveLayer().equals(token.getLayer())) {
+ tokenPostProcessing.add(position);
+ }
+ timer.stop("token-list-11");
+ }
+
+ // Selection
+ timer.start("token-list-12");
+ for (ZoneViewModel.TokenPosition position : tokenPostProcessing) {
+ var token = position.token();
+ // Count moving tokens as "selected" so that a border is drawn around them.
+ if (selectionModel.isSelected(token.getId()) || viewModel.isTokenMoving(token.getId())) {
+ selectionRenderer.drawSelectBorder(clippedG, position);
}
}
timer.stop("token-list-12");
@@ -2470,7 +2391,7 @@ sure the current Player owns the token being duplicated (to avoid subtle ways of
for (MD5Key id : token.getAllImageAssets()) {
Asset asset = AssetManager.getAsset(id);
if (asset == null) {
- log.error("Could not find image for asset: " + id);
+ log.error("Could not find image for asset: {}", id);
continue;
}
MapToolUtil.uploadAsset(asset);
@@ -2544,6 +2465,7 @@ public List getVisibleTokens() {
@Override
public void dropActionChanged(DropTargetDragEvent dtde) {}
+ @SuppressWarnings("unused")
@Subscribe
private void onSelectionChanged(SelectionModel.SelectionChanged event) {
if (event.zone() != zone) {
@@ -2554,6 +2476,7 @@ private void onSelectionChanged(SelectionModel.SelectionChanged event) {
repaintDebouncer.dispatch();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onTokensAdded(TokensAdded event) {
if (event.zone() != this.zone) {
@@ -2567,6 +2490,7 @@ private void onTokensAdded(TokensAdded event) {
repaintDebouncer.dispatch();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onTokensRemoved(TokensRemoved event) {
if (event.zone() != this.zone) {
@@ -2580,6 +2504,7 @@ private void onTokensRemoved(TokensRemoved event) {
repaintDebouncer.dispatch();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onTokensChanged(TokensChanged event) {
if (event.zone() != this.zone) {
@@ -2593,6 +2518,7 @@ private void onTokensChanged(TokensChanged event) {
repaintDebouncer.dispatch();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onFogChanged(FogChanged event) {
if (event.zone() != this.zone) {
@@ -2611,6 +2537,7 @@ private void onTopologyChanged() {
repaintDebouncer.dispatch();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onTopologyChanged(WallTopologyChanged event) {
if (event.zone() != this.zone) {
@@ -2619,6 +2546,7 @@ private void onTopologyChanged(WallTopologyChanged event) {
onTopologyChanged();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onTopologyChanged(MaskTopologyChanged event) {
if (event.zone() != this.zone) {
@@ -2631,6 +2559,7 @@ private void markDrawableLayerDirty(Layer layer) {
drawableRenderers.get(layer).setDirty();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onDrawableAdded(DrawableAdded event) {
if (event.zone() != this.zone) {
@@ -2641,6 +2570,7 @@ private void onDrawableAdded(DrawableAdded event) {
repaintDebouncer.dispatch();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onDrawableRemoved(DrawableRemoved event) {
if (event.zone() != this.zone) {
@@ -2651,6 +2581,7 @@ private void onDrawableRemoved(DrawableRemoved event) {
repaintDebouncer.dispatch();
}
+ @SuppressWarnings("unused")
@Subscribe
private void onBoardChanged(BoardChanged event) {
if (event.zone() != this.zone) {
@@ -2660,6 +2591,7 @@ private void onBoardChanged(BoardChanged event) {
}
// Should this be moved to GridRenderer? No. Lots of things depend on the grid.
+ @SuppressWarnings("unused")
@Subscribe
private void onGridChanged(GridChanged event) {
if (event.zone() != this.zone) {
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/FacingArrowRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/FacingArrowRenderer.java
index 5b0aeef38f..050edf177e 100644
--- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/FacingArrowRenderer.java
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/FacingArrowRenderer.java
@@ -14,17 +14,20 @@
*/
package net.rptools.maptool.client.ui.zone.renderer.tokenRender;
+import com.google.common.eventbus.Subscribe;
import java.awt.*;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Path2D;
-import java.awt.geom.Rectangle2D;
+import java.awt.geom.*;
import java.util.ArrayList;
import net.rptools.lib.CodeTimer;
+import net.rptools.lib.GeometryUtil;
import net.rptools.maptool.client.AppPreferences;
import net.rptools.maptool.client.ui.zone.ZoneViewModel.TokenPosition;
import net.rptools.maptool.client.ui.zone.renderer.RenderHelper;
+import net.rptools.maptool.model.GridFactory;
import net.rptools.maptool.model.Token.TokenShape;
import net.rptools.maptool.model.Zone;
+import net.rptools.maptool.model.zones.GridChanged;
+import net.rptools.maptool.util.GraphicsUtil;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
@@ -45,11 +48,29 @@ public class FacingArrowRenderer {
}
private final RenderHelper renderHelper;
- private final Zone zone;
+ private Zone zone;
private final ArrayList figureFillColours = new ArrayList<>();
+
private final Color fillColour = Color.YELLOW;
private final Color borderColour = Color.DARK_GRAY;
+ private boolean isIsometric;
+ private boolean isSquare;
+
+ @SuppressWarnings("unused")
+ @Subscribe
+ private void onGridChanged(GridChanged event) {
+ if (event.zone() != null) {
+ this.zone = event.zone();
+ if (zone.getGrid() == null) {
+ isIsometric = false;
+ isSquare = false;
+ } else {
+ isIsometric = this.zone.getGrid().isIsometric();
+ isSquare = GridFactory.getGridType(this.zone.getGrid()).equals(GridFactory.SQUARE);
+ }
+ }
+ }
public FacingArrowRenderer(RenderHelper renderHelper, Zone zone) {
this.renderHelper = renderHelper;
@@ -60,9 +81,16 @@ public FacingArrowRenderer(RenderHelper renderHelper, Zone zone) {
for (int i = 89; i >= 0; i--) {
figureFillColours.add(figureFillColours.get(i));
}
+ if (zone.getGrid() == null) {
+ isIsometric = false;
+ isSquare = false;
+ } else {
+ isIsometric = this.zone.getGrid().isIsometric();
+ isSquare = GridFactory.getGridType(this.zone.getGrid()).equals(GridFactory.SQUARE);
+ }
}
- public void paintArrow(Graphics2D tokenG, TokenPosition position) {
+ public void paintArrow(Graphics2D g2d, TokenPosition position) {
var timer = CodeTimer.get();
var token = position.token();
var tokenShape = token.getShape();
@@ -83,8 +111,10 @@ public void paintArrow(Graphics2D tokenG, TokenPosition position) {
timer.stop("FacingArrowRenderer-preCheck");
timer.start("FacingArrowRenderer-render");
+ // set the stroke to shrink for tiny tokens to prevent it crowding out the fill
+ g2d.setStroke(new BasicStroke((float) (0.85f * position.token().getSizeScale())));
renderHelper.render(
- tokenG,
+ g2d,
worldG ->
paintArrowWorld(worldG, token.getFacing(), tokenShape, position.footprintBounds()));
timer.stop("FacingArrowRenderer-render");
@@ -95,8 +125,6 @@ private void paintArrowWorld(
var timer = CodeTimer.get();
timer.start("FacingArrowRenderer-paintArrow");
try {
- final var isIsometric = zone.getGrid().isIsometric();
-
timer.start("FacingArrowRenderer-calculateTransform");
int angle = Math.floorMod(facing + (isIsometric ? 45 : 0), 360);
AffineTransform transform =
@@ -107,49 +135,67 @@ private void paintArrowWorld(
Shape facingArrow = transform.createTransformedShape(UNIT_ARROW);
timer.stop("FacingArrowRenderer-transformArrow");
- timer.start("FacingArrowRenderer-fill");
+ // draw first so that fill is always visible
+ tokenG.setColor(borderColour);
+ tokenG.draw(facingArrow);
+
if (TokenShape.FIGURE.equals(tokenShape) && angle <= 180) {
tokenG.setColor(figureFillColours.get(angle));
} else {
tokenG.setColor(fillColour);
}
tokenG.fill(facingArrow);
- timer.stop("FacingArrowRenderer-fill");
-
- timer.start("FacingArrowRenderer-draw");
- tokenG.setColor(borderColour);
- tokenG.draw(facingArrow);
- timer.stop("FacingArrowRenderer-draw");
} catch (Exception e) {
log.error("Failed to paint facing arrow.", e);
- } finally {
- timer.stop("FacingArrowRenderer-paintArrow");
}
+ timer.stop("FacingArrowRenderer-paintArrow");
}
- private static AffineTransform buildArrowTransform(
+ private AffineTransform buildArrowTransform(
TokenShape shape, Rectangle2D footprintBounds, int angle, boolean isIsometric) {
double radFacing = Math.toRadians(angle);
AffineTransform transform = new AffineTransform();
+ // move to footprint centre
transform.translate(footprintBounds.getCenterX(), footprintBounds.getCenterY());
if (isIsometric) {
transform.scale(1.0, 0.5);
}
+ // spin to face correct direction. Not linear for isometric
transform.rotate(-radFacing);
- double distanceToPoint = footprintBounds.getWidth() / 2;
- if (TokenShape.SQUARE.equals(shape) && !isIsometric) {
- if (angle >= 45 && angle <= 135 || angle >= 225 && angle <= 315) { // Top or bottom face.
- distanceToPoint = footprintBounds.getHeight() / 2 / Math.abs(Math.sin(radFacing));
- } else { // Left or right face
- distanceToPoint = footprintBounds.getWidth() / 2 / Math.abs(Math.cos(radFacing));
- }
+ // calculate distance to edge
+ double distanceToPoint;
+ Shape cellShape = this.zone.getGrid().getCellShape();
+ if (cellShape != null) {
+ Point2D centre = new Point2D.Double(0, 0);
+ // centre the cell shape
+ cellShape =
+ AffineTransform.getTranslateInstance(
+ -cellShape.getBounds2D().getCenterX(), -cellShape.getBounds2D().getCenterY())
+ .createTransformedShape(cellShape);
+ double scale = footprintBounds.getWidth() / cellShape.getBounds2D().getWidth();
+ // size the cell shape to the footprint - compensate for previous isometric scaling
+ cellShape =
+ AffineTransform.getScaleInstance(scale, isIsometric ? 2 * scale : scale)
+ .createTransformedShape(cellShape);
+ // create a line from the centre with token facing angle
+ Point2D farPoint = GraphicsUtil.getPointAtVector(centre, angle, 300 * scale);
+ Line2D.Double ray = new Line2D.Double(centre, farPoint);
+ // obtain the point the line intersects the cell shape
+ Point2D[] point2D = GeometryUtil.lineSegmentShapeIntersection(ray, cellShape);
+ distanceToPoint = Math.hypot(point2D[0].getX(), point2D[0].getY());
+ } else {
+ // fallback for gridless, just use radius based on size
+ distanceToPoint = footprintBounds.getWidth() / 2;
}
+ // move out to edge
transform.translate(distanceToPoint, 0);
- var size = footprintBounds.getWidth() / 2d;
- transform.scale(size, size);
+ var sizeW = footprintBounds.getWidth() / 2d;
+ var sizeH = footprintBounds.getHeight() / 2d;
+ // make it look big
+ transform.scale(sizeW, sizeH);
return transform;
}
}
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/StateRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/StateRenderer.java
new file mode 100644
index 0000000000..ee883efb67
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/StateRenderer.java
@@ -0,0 +1,113 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.client.ui.zone.renderer.tokenRender;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.util.*;
+import net.rptools.maptool.client.MapTool;
+import net.rptools.maptool.client.ui.token.*;
+import net.rptools.maptool.client.ui.zone.ZoneViewModel;
+import net.rptools.maptool.client.ui.zone.renderer.RenderHelper;
+import net.rptools.maptool.model.Zone;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+public class StateRenderer {
+ private static final Logger log = LogManager.getLogger(StateRenderer.class);
+ private final RenderHelper renderHelper;
+ private final Zone zone;
+ private final Map barMap =
+ Collections.synchronizedMap(MapTool.getCampaign().getTokenBarsMap());
+ private final Map stateMap =
+ Collections.synchronizedMap(MapTool.getCampaign().getTokenStatesMap());
+ private Set overlayNames;
+ private boolean isPaintBars = false;
+
+ public StateRenderer(RenderHelper renderHelper, Zone zone) {
+ this.renderHelper = renderHelper;
+ this.zone = zone;
+ }
+
+ public void renderStates(
+ ZoneViewModel viewModel,
+ ZoneViewModel.TokenPosition position,
+ Graphics2D g2d,
+ boolean selected,
+ boolean hover) {
+ isPaintBars = false;
+ synchronized (stateMap) {
+ overlayNames = stateMap.keySet();
+ }
+ renderOverlay(viewModel, position, g2d, selected, hover);
+ }
+
+ public void renderBars(
+ ZoneViewModel viewModel,
+ ZoneViewModel.TokenPosition position,
+ Graphics2D g2d,
+ boolean selected,
+ boolean hover) {
+ isPaintBars = true;
+ synchronized (barMap) {
+ overlayNames = barMap.keySet();
+ }
+ renderOverlay(viewModel, position, g2d, selected, hover);
+ }
+
+ @SuppressWarnings("unused")
+ public void renderOverlay(
+ ZoneViewModel viewModel,
+ ZoneViewModel.TokenPosition position,
+ Graphics2D g2d,
+ boolean selected,
+ boolean hover) {
+ Rectangle2D tokenBounds =
+ viewModel.getZoneScale().toScreenSpace(position.footprintBounds().getBounds2D());
+ Rectangle bounds =
+ new Rectangle(0, 0, (int) tokenBounds.getWidth(), (int) tokenBounds.getHeight());
+ Graphics2D overlayG =
+ (Graphics2D)
+ g2d.create(
+ (int) tokenBounds.getX(),
+ (int) tokenBounds.getY(),
+ (int) tokenBounds.getWidth(),
+ (int) tokenBounds.getHeight());
+ overlayG.setClip(null);
+
+ Composite oldComposite = g2d.getComposite();
+ float alpha = 1f;
+ if (oldComposite instanceof AlphaComposite alphaComposite) {
+ alpha = alphaComposite.getAlpha();
+ }
+
+ // Check each of the set values
+ for (String name : overlayNames) {
+ AbstractTokenOverlay overlay = isPaintBars ? barMap.get(name) : stateMap.get(name);
+ Object value = position.token().getState(name);
+ if (overlay == null
+ || overlay.isMouseover() && hover
+ || !overlay.showPlayer(position.token(), MapTool.getPlayer())) {
+ continue;
+ }
+ overlayG.setComposite(
+ AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, alpha * (float) overlay.getOpacity() / 100));
+
+ overlay.paintOverlay(overlayG, position.token(), bounds, value);
+ }
+ overlayG.dispose();
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenDecorationRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenDecorationRenderer.java
new file mode 100644
index 0000000000..f1c7a5d798
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenDecorationRenderer.java
@@ -0,0 +1,90 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.client.ui.zone.renderer.tokenRender;
+
+import java.awt.*;
+import net.rptools.lib.CodeTimer;
+import net.rptools.maptool.client.ui.zone.ZoneViewModel;
+import net.rptools.maptool.client.ui.zone.renderer.HaloRenderer;
+import net.rptools.maptool.client.ui.zone.renderer.RenderHelper;
+import net.rptools.maptool.model.Zone;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+public class TokenDecorationRenderer {
+ /* Render order by increasing z-index
+ AURA,
+ HALO,
+ TOKEN,
+ STATE,
+ BAR,
+ FACING,
+ LABEL;
+ */
+ private static final Logger log = LogManager.getLogger(TokenDecorationRenderer.class);
+ private final RenderHelper renderHelper;
+ private final Zone zone;
+ private final FacingArrowRenderer FACING_ARROW_RENDERER;
+ private final HaloRenderer HALO_RENDERER;
+ private final StateRenderer OVERLAY_RENDERER;
+
+ public TokenDecorationRenderer(RenderHelper renderHelper, Zone zone) {
+ this.renderHelper = renderHelper;
+ this.zone = zone;
+ FACING_ARROW_RENDERER = new FacingArrowRenderer(renderHelper, zone);
+ HALO_RENDERER = new HaloRenderer(renderHelper, zone);
+ OVERLAY_RENDERER = new StateRenderer(renderHelper, zone);
+ }
+
+ public void renderDecorations(
+ boolean under,
+ ZoneViewModel viewModel,
+ ZoneViewModel.TokenPosition position,
+ Graphics2D g2d,
+ boolean selected,
+ boolean moving,
+ boolean hover) {
+ var timer = CodeTimer.get();
+ Composite oldComposite = g2d.getComposite();
+ if (hover || selected && !moving) {
+ g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
+ } else {
+ g2d.setComposite(
+ AlphaComposite.getInstance(AlphaComposite.SRC_OVER, position.token().getTokenOpacity()));
+ }
+ timer.increment("TokenDecorationRenderer-render");
+ if (under) {
+ timer.start("TokenDecorationRenderer-renderUnder");
+ // paint Halo
+ renderHelper.render(g2d, worldG -> HALO_RENDERER.renderHalo(worldG, position));
+ // paint TOKEN
+ timer.stop("TokenDecorationRenderer-renderUnder");
+ } else {
+ timer.start("TokenDecorationRenderer-renderOver");
+ if (!moving) {
+ // paint STATE
+ OVERLAY_RENDERER.renderStates(viewModel, position, g2d, selected, hover);
+ // paint BAR
+ OVERLAY_RENDERER.renderBars(viewModel, position, g2d, selected, hover);
+ }
+ // paint FACING
+ FACING_ARROW_RENDERER.paintArrow(g2d, position);
+ // paint LABEL
+ // Not yet implemented;
+ timer.stop("TokenDecorationRenderer-renderOver");
+ }
+ g2d.setComposite(oldComposite);
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenRenderer.java
index c0c6a0f42d..6282b5658e 100644
--- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenRenderer.java
+++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenRenderer.java
@@ -25,6 +25,7 @@
import net.rptools.lib.CodeTimer;
import net.rptools.lib.MD5Key;
import net.rptools.maptool.client.MapTool;
+import net.rptools.maptool.client.ui.zone.ZoneViewModel;
import net.rptools.maptool.client.ui.zone.ZoneViewModel.TokenPosition;
import net.rptools.maptool.client.ui.zone.renderer.RenderHelper;
import net.rptools.maptool.model.*;
@@ -41,17 +42,35 @@ public class TokenRenderer {
private final RenderHelper renderHelper;
private final Zone zone;
+ private final TokenDecorationRenderer decorationRenderer;
public TokenRenderer(RenderHelper renderHelper, Zone zone) {
this.renderHelper = renderHelper;
this.zone = zone;
+ this.decorationRenderer = new TokenDecorationRenderer(renderHelper, zone);
}
- public void renderToken(Token token, TokenPosition position, Graphics2D g2d, float opacity) {
+ public void renderToken(
+ Token token,
+ ZoneViewModel viewModel,
+ TokenPosition position,
+ Graphics2D g2d,
+ boolean isSelected,
+ boolean isMoving,
+ boolean isHover) {
var timer = CodeTimer.get();
+ decorationRenderer.renderDecorations(
+ true, viewModel, position, g2d, isSelected, isMoving, isHover);
+
timer.increment("TokenRenderer-renderToken");
timer.start("TokenRenderer-renderToken");
+ // Calculate alpha Transparency from token and use opacity to indicate that token is moving
+ float opacity =
+ viewModel.isTokenMoving(token.getId())
+ ? Math.max(0.5f, token.getTokenOpacity() / 2f)
+ : isSelected || isHover ? 1 : token.getTokenOpacity();
+
timer.start("TokenRenderer-loadImageTable");
if (token.getHasImageTable() && !imageTableMap.containsKey(token.getImageTableName())) {
(new CacheTableImagesWorker(token.getImageTableName())).execute();
@@ -59,9 +78,13 @@ public void renderToken(Token token, TokenPosition position, Graphics2D g2d, flo
timer.stop("TokenRenderer-loadImageTable");
timer.start("TokenRenderer-paintTokenImage");
- renderHelper.render(
- g2d, worldG -> paintTokenImage(worldG, position, opacity * token.getTokenOpacity()));
+
+ renderHelper.render(g2d, worldG -> paintTokenImage(worldG, position, opacity));
timer.stop("TokenRenderer-paintTokenImage");
+
+ decorationRenderer.renderDecorations(
+ false, viewModel, position, g2d, isSelected, isMoving, isHover);
+
timer.stop("TokenRenderer-renderToken");
}
diff --git a/src/main/java/net/rptools/maptool/util/GraphicsUtil.java b/src/main/java/net/rptools/maptool/util/GraphicsUtil.java
index 5a80cf7e42..7b085ee1ba 100644
--- a/src/main/java/net/rptools/maptool/util/GraphicsUtil.java
+++ b/src/main/java/net/rptools/maptool/util/GraphicsUtil.java
@@ -408,7 +408,7 @@ public static Area createLine(int width, Point2D... points) {
return new Area(path);
}
- private static Point2D getPointAtVector(Point2D point, double angle, double length) {
+ public static Point2D getPointAtVector(Point2D point, double angle, double length) {
double x = point.getX() + length * Math.cos(Math.toRadians(angle));
double y = point.getY() - length * Math.sin(Math.toRadians(angle));
return new Point2D.Double(x, y);