From 978d5db8fd61ea8fe9e27e02ec756d66202ea112 Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Sun, 3 Aug 2025 00:47:44 +0800 Subject: [PATCH 1/8] LabelRenderer - Moved caches to class, added setters and static isLabelVisible method. - Moved token label generation in token rendering sequence preparatory to moving it out to DecorationRenderer. New class TokenDecorationRenderer - Paints things before and after token rendering. - Hub for halos, bars, states, etc. More bits to follow. New class OverlayRenderer - For painting states and bars. FacingArrowRenderer - Facing arrow now follows grid cell shape. Made some variables non-local. GeometryUtil - Added functions for finding intersection points. HaloRenderer - Now honours preference for opacity. TokenRenderer - Updated to call decoration renderer before and after painting image. plus other tweaks. ZoneViewModel - Added method- isUsingVision(). ZoneRenderer - Inverted hasMoveSelectionSetMoved(). - Made imageLabelFactory a constant. - LabelRenderingCache offloaded to LabelRenderer. - Renamed renderLabels() method as it is confusing. - Removed redundant image loading and token flipping from renderTokens(). - Other housework. --- .../java/net/rptools/lib/GeometryUtil.java | 80 +++-- .../maptool/client/tool/PointerTool.java | 2 +- .../maptool/client/tool/StampTool.java | 2 +- .../maptool/client/ui/zone/ZoneViewModel.java | 4 + .../client/ui/zone/renderer/HaloRenderer.java | 29 +- .../client/ui/zone/renderer/ItemRenderer.java | 2 +- .../ui/zone/renderer/LabelRenderer.java | 57 +++- .../client/ui/zone/renderer/ZoneRenderer.java | 277 +++++++----------- .../tokenRender/FacingArrowRenderer.java | 90 ++++-- .../renderer/tokenRender/OverlayRenderer.java | 103 +++++++ .../tokenRender/TokenDecorationRenderer.java | 80 +++++ .../renderer/tokenRender/TokenRenderer.java | 40 ++- .../rptools/maptool/util/GraphicsUtil.java | 2 +- 13 files changed, 528 insertions(+), 240 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java create mode 100644 src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenDecorationRenderer.java 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 30cda463fa..c513158b2c 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 82730e8853..e33c649662 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/zone/ZoneViewModel.java b/src/main/java/net/rptools/maptool/client/ui/zone/ZoneViewModel.java index a836cb51cb..f1ef1bb20b 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 544ac2c42a..e1db482b72 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,10 @@ 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.TokenDecorationRenderer; 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 +68,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 +109,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 +132,8 @@ 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 TokenDecorationRenderer decorationRenderer; private final SelectionRenderer selectionRenderer; private final LightsRenderer lightsRenderer; private final DarknessRenderer darknessRenderer; @@ -159,20 +156,20 @@ 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.decorationRenderer = new TokenDecorationRenderer(renderHelper, zone); this.selectionRenderer = new SelectionRenderer(renderHelper, viewModel, zoneView); this.lightsRenderer = new LightsRenderer(renderHelper, zone, zoneView); this.darknessRenderer = new DarknessRenderer(renderHelper, zoneView); @@ -180,7 +177,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 +298,7 @@ public void setMouseOver(Token token) { if (tokenUnderMouse == token) { return; } - tokenUnderMouse = token; + this.tokenUnderMouse = token; repaintDebouncer.dispatch(); } @@ -324,13 +321,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) { @@ -546,15 +543,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; @@ -676,8 +671,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); @@ -979,7 +974,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); @@ -1010,12 +1005,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"); } @@ -1045,10 +1038,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); } @@ -1062,7 +1057,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"); @@ -1079,7 +1074,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"); } @@ -1261,7 +1256,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); // Other details. // Only draw these if the token is visible on screen where it is dragged to. @@ -1525,8 +1520,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"); @@ -1725,7 +1720,6 @@ protected void renderTokens( final var timer = CodeTimer.get(); Graphics2D clippedG = g; - var imageLabelFactory = new FlatImageLabelFactory(); boolean isGMView = view.isGMView(); // speed things up @@ -1758,12 +1752,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"); @@ -1780,8 +1772,8 @@ protected void renderTokens( } } else { tokenG = (Graphics2D) g.create(); - AppPreferences.renderQuality.get().setRenderingHints(tokenG); } + AppPreferences.renderQuality.get().setRenderingHints(tokenG); // Previous path timer.start("renderTokens:ShowPath"); @@ -1790,118 +1782,13 @@ 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, token == tokenUnderMouse); + 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; @@ -1923,10 +1810,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); @@ -1950,23 +1837,60 @@ 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()); + boolean isHover = token == tokenUnderMouse; + tokenRenderer.renderToken( + token, viewModel, position, tokenG, decorationRenderer, 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"); @@ -2462,7 +2386,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); @@ -2536,6 +2460,7 @@ public List getVisibleTokens() { @Override public void dropActionChanged(DropTargetDragEvent dtde) {} + @SuppressWarnings("unused") @Subscribe private void onSelectionChanged(SelectionModel.SelectionChanged event) { if (event.zone() != zone) { @@ -2546,6 +2471,7 @@ private void onSelectionChanged(SelectionModel.SelectionChanged event) { repaintDebouncer.dispatch(); } + @SuppressWarnings("unused") @Subscribe private void onTokensAdded(TokensAdded event) { if (event.zone() != this.zone) { @@ -2559,6 +2485,7 @@ private void onTokensAdded(TokensAdded event) { repaintDebouncer.dispatch(); } + @SuppressWarnings("unused") @Subscribe private void onTokensRemoved(TokensRemoved event) { if (event.zone() != this.zone) { @@ -2572,6 +2499,7 @@ private void onTokensRemoved(TokensRemoved event) { repaintDebouncer.dispatch(); } + @SuppressWarnings("unused") @Subscribe private void onTokensChanged(TokensChanged event) { if (event.zone() != this.zone) { @@ -2585,6 +2513,7 @@ private void onTokensChanged(TokensChanged event) { repaintDebouncer.dispatch(); } + @SuppressWarnings("unused") @Subscribe private void onFogChanged(FogChanged event) { if (event.zone() != this.zone) { @@ -2603,6 +2532,7 @@ private void onTopologyChanged() { repaintDebouncer.dispatch(); } + @SuppressWarnings("unused") @Subscribe private void onTopologyChanged(WallTopologyChanged event) { if (event.zone() != this.zone) { @@ -2611,6 +2541,7 @@ private void onTopologyChanged(WallTopologyChanged event) { onTopologyChanged(); } + @SuppressWarnings("unused") @Subscribe private void onTopologyChanged(MaskTopologyChanged event) { if (event.zone() != this.zone) { @@ -2623,6 +2554,7 @@ private void markDrawableLayerDirty(Layer layer) { drawableRenderers.get(layer).setDirty(); } + @SuppressWarnings("unused") @Subscribe private void onDrawableAdded(DrawableAdded event) { if (event.zone() != this.zone) { @@ -2633,6 +2565,7 @@ private void onDrawableAdded(DrawableAdded event) { repaintDebouncer.dispatch(); } + @SuppressWarnings("unused") @Subscribe private void onDrawableRemoved(DrawableRemoved event) { if (event.zone() != this.zone) { @@ -2643,6 +2576,7 @@ private void onDrawableRemoved(DrawableRemoved event) { repaintDebouncer.dispatch(); } + @SuppressWarnings("unused") @Subscribe private void onBoardChanged(BoardChanged event) { if (event.zone() != this.zone) { @@ -2652,6 +2586,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..3463e764dc 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,24 @@ 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(); + 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 +76,11 @@ public FacingArrowRenderer(RenderHelper renderHelper, Zone zone) { for (int i = 89; i >= 0; i--) { figureFillColours.add(figureFillColours.get(i)); } + 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 +101,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 +115,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 +125,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/OverlayRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java new file mode 100644 index 0000000000..8d713c95ae --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java @@ -0,0 +1,103 @@ +/* + * 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.AbstractTokenOverlay; +import net.rptools.maptool.client.ui.token.BarTokenOverlay; +import net.rptools.maptool.client.ui.token.BooleanTokenOverlay; +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 OverlayRenderer { + private static final Logger log = LogManager.getLogger(OverlayRenderer.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 OverlayRenderer(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()); + Graphics2D overlayG = + (Graphics2D) + g2d.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 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; + } + overlay.paintOverlay(overlayG, position.token(), bounds, value); + } + } +} 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..58cbd5af8d --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/TokenDecorationRenderer.java @@ -0,0 +1,80 @@ +/* + * 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 OverlayRenderer 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 OverlayRenderer(renderHelper, zone); + } + + public void renderDecorations( + boolean under, + ZoneViewModel viewModel, + ZoneViewModel.TokenPosition position, + Graphics2D g2d, + boolean selected, + boolean moving, + boolean hover) { + var timer = CodeTimer.get(); + 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"); + // 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"); + } + } +} 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 fd5c5237b8..ee5c3791e0 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.*; @@ -46,11 +47,39 @@ public TokenRenderer(RenderHelper renderHelper, Zone zone) { this.zone = 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) { + renderToken(token, viewModel, position, g2d, null, isSelected, isMoving, false); + } + + public void renderToken( + Token token, + ZoneViewModel viewModel, + TokenPosition position, + Graphics2D g2d, + TokenDecorationRenderer decorationRenderer, + boolean isSelected, + boolean isMoving, + boolean isHover) { var timer = CodeTimer.get(); + if (decorationRenderer != null) { + 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()) + ? token.getTokenOpacity() / 2f + : isSelected && isHover ? 1 : token.getTokenOpacity(); + timer.start("TokenRenderer-loadImageTable"); if (token.getHasImageTable() && !imageTableMap.containsKey(token.getImageTableName())) { (new CacheTableImagesWorker(token.getImageTableName())).execute(); @@ -58,9 +87,14 @@ 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"); + + if (decorationRenderer != null) { + 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); From b47c568ab817759e636d3f99088e9256a5d66ebe Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:15:03 +0800 Subject: [PATCH 2/8] Fix to pass tests --- .../tokenRender/FacingArrowRenderer.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) 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 3463e764dc..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 @@ -62,8 +62,13 @@ public class FacingArrowRenderer { private void onGridChanged(GridChanged event) { if (event.zone() != null) { this.zone = event.zone(); - isIsometric = this.zone.getGrid().isIsometric(); - isSquare = GridFactory.getGridType(this.zone.getGrid()).equals(GridFactory.SQUARE); + if (zone.getGrid() == null) { + isIsometric = false; + isSquare = false; + } else { + isIsometric = this.zone.getGrid().isIsometric(); + isSquare = GridFactory.getGridType(this.zone.getGrid()).equals(GridFactory.SQUARE); + } } } @@ -76,8 +81,13 @@ public FacingArrowRenderer(RenderHelper renderHelper, Zone zone) { for (int i = 89; i >= 0; i--) { figureFillColours.add(figureFillColours.get(i)); } - isIsometric = this.zone.getGrid().isIsometric(); - isSquare = GridFactory.getGridType(this.zone.getGrid()).equals(GridFactory.SQUARE); + 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 g2d, TokenPosition position) { From 67d37ec896945ecbae0d62a517a3213b3e9ff5bf Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Sun, 3 Aug 2025 20:23:14 +0800 Subject: [PATCH 3/8] Removed clipping from overlay graphics object --- .../client/ui/zone/renderer/tokenRender/OverlayRenderer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java index 8d713c95ae..0144e9bc86 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java @@ -85,6 +85,7 @@ public void renderOverlay( (int) tokenBounds.getY(), (int) tokenBounds.getWidth(), (int) tokenBounds.getHeight()); + overlayG.setClip(null); Rectangle bounds = new Rectangle(0, 0, (int) tokenBounds.getWidth(), (int) tokenBounds.getHeight()); @@ -99,5 +100,6 @@ public void renderOverlay( } overlay.paintOverlay(overlayG, position.token(), bounds, value); } + overlayG.dispose(); } } From 6bff7d66c9ea2236c6318406a85bc7eb2c13de84 Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:35:32 +0800 Subject: [PATCH 4/8] Renamed OverlayRenderer to StateRenderer. Moved state opacity check to StateRenderer. Moved StateRenderer out of ZR and into TokenRenderer. Changed rendering for translucent tokens to be opaque on hover or selected. --- .../client/ui/token/BooleanTokenOverlay.java | 6 ---- .../client/ui/zone/renderer/ZoneRenderer.java | 15 ++++----- ...verlayRenderer.java => StateRenderer.java} | 19 +++++++----- .../tokenRender/TokenDecorationRenderer.java | 22 +++++++++---- .../renderer/tokenRender/TokenRenderer.java | 31 ++++++------------- 5 files changed, 43 insertions(+), 50 deletions(-) rename src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/{OverlayRenderer.java => StateRenderer.java} (88%) 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/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index e1db482b72..b8ac29fd70 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 @@ -55,7 +55,6 @@ 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.TokenDecorationRenderer; import net.rptools.maptool.client.ui.zone.renderer.tokenRender.TokenRenderer; import net.rptools.maptool.client.walker.ZoneWalker; import net.rptools.maptool.events.MapToolEventBus; @@ -133,7 +132,6 @@ public class ZoneRenderer extends JComponent implements DropTargetListener { private final EnumSet disabledLayers = EnumSet.noneOf(Layer.class); private final GridRenderer gridRenderer; private final TokenRenderer tokenRenderer; - private final TokenDecorationRenderer decorationRenderer; private final SelectionRenderer selectionRenderer; private final LightsRenderer lightsRenderer; private final DarknessRenderer darknessRenderer; @@ -169,7 +167,6 @@ public ZoneRenderer(Zone zone) { this.gridRenderer = new GridRenderer(this); this.tokenRenderer = new TokenRenderer(renderHelper, zone); - this.decorationRenderer = new TokenDecorationRenderer(renderHelper, zone); this.selectionRenderer = new SelectionRenderer(renderHelper, viewModel, zoneView); this.lightsRenderer = new LightsRenderer(renderHelper, zone, zoneView); this.darknessRenderer = new DarknessRenderer(renderHelper, zoneView); @@ -1256,7 +1253,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, viewModel, newPosition, g, true, true); + 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. @@ -1775,6 +1772,8 @@ protected void renderTokens( } AppPreferences.renderQuality.get().setRenderingHints(tokenG); + boolean isHover = token == tokenUnderMouse; + // Previous path timer.start("renderTokens:ShowPath"); if (showPathList.contains(token) && token.getLastPath() != null) { @@ -1784,8 +1783,7 @@ protected void renderTokens( timer.start("renderTokens:LabelCheck"); // Token name and label - boolean showCurrentTokenLabel = - LabelRenderer.isLabelVisible(position, viewModel, token == tokenUnderMouse); + boolean showCurrentTokenLabel = LabelRenderer.isLabelVisible(position, viewModel, isHover); timer.stop("renderTokens:LabelCheck"); if (showCurrentTokenLabel) { timer.start("renderTokens:LabelBuild"); @@ -1870,9 +1868,8 @@ protected void renderTokens( // Clipping is handled in the isTokenInNeedOfClipping() call far above. boolean isSelected = selectionModel.isSelected(token.getId()); boolean isMoving = viewModel.isTokenMoving(token.getId()); - boolean isHover = token == tokenUnderMouse; - tokenRenderer.renderToken( - token, viewModel, position, tokenG, decorationRenderer, isSelected, isMoving, isHover); + + tokenRenderer.renderToken(token, viewModel, position, tokenG, isSelected, isMoving, isHover); tokenG.dispose(); timer.stop("token-list-7"); diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/StateRenderer.java similarity index 88% rename from src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java rename to src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/StateRenderer.java index 0144e9bc86..627cd93d9c 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/OverlayRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/StateRenderer.java @@ -18,17 +18,15 @@ import java.awt.geom.Rectangle2D; import java.util.*; import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.token.AbstractTokenOverlay; -import net.rptools.maptool.client.ui.token.BarTokenOverlay; -import net.rptools.maptool.client.ui.token.BooleanTokenOverlay; +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 OverlayRenderer { - private static final Logger log = LogManager.getLogger(OverlayRenderer.class); +public class StateRenderer { + private static final Logger log = LogManager.getLogger(StateRenderer.class); private final RenderHelper renderHelper; private final Zone zone; private final Map barMap = @@ -38,7 +36,7 @@ public class OverlayRenderer { private Set overlayNames; private boolean isPaintBars = false; - public OverlayRenderer(RenderHelper renderHelper, Zone zone) { + public StateRenderer(RenderHelper renderHelper, Zone zone) { this.renderHelper = renderHelper; this.zone = zone; } @@ -78,6 +76,8 @@ public void renderOverlay( 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( @@ -86,8 +86,6 @@ public void renderOverlay( (int) tokenBounds.getWidth(), (int) tokenBounds.getHeight()); overlayG.setClip(null); - Rectangle bounds = - new Rectangle(0, 0, (int) tokenBounds.getWidth(), (int) tokenBounds.getHeight()); // Check each of the set values for (String name : overlayNames) { @@ -98,6 +96,11 @@ public void renderOverlay( || !overlay.showPlayer(position.token(), MapTool.getPlayer())) { continue; } + if (overlay instanceof TwoToneBarTokenOverlay tt) { + overlay = + new TwoToneCircleBarTokenOverlay( + tt.getName(), tt.getBarColor(), tt.getBgColor(), tt.getThickness(), tt.getSide()); + } 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 index 58cbd5af8d..f1c7a5d798 100644 --- 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 @@ -38,14 +38,14 @@ public class TokenDecorationRenderer { private final Zone zone; private final FacingArrowRenderer FACING_ARROW_RENDERER; private final HaloRenderer HALO_RENDERER; - private final OverlayRenderer OVERLAY_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 OverlayRenderer(renderHelper, zone); + OVERLAY_RENDERER = new StateRenderer(renderHelper, zone); } public void renderDecorations( @@ -57,6 +57,13 @@ public void renderDecorations( 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"); @@ -66,15 +73,18 @@ public void renderDecorations( timer.stop("TokenDecorationRenderer-renderUnder"); } else { timer.start("TokenDecorationRenderer-renderOver"); - // paint STATE - OVERLAY_RENDERER.renderStates(viewModel, position, g2d, selected, hover); - // paint BAR - OVERLAY_RENDERER.renderBars(viewModel, position, g2d, selected, hover); + 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 ee5c3791e0..973b0b5963 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 @@ -41,10 +41,12 @@ 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( @@ -53,32 +55,20 @@ public void renderToken( TokenPosition position, Graphics2D g2d, boolean isSelected, - boolean isMoving) { - renderToken(token, viewModel, position, g2d, null, isSelected, isMoving, false); - } - - public void renderToken( - Token token, - ZoneViewModel viewModel, - TokenPosition position, - Graphics2D g2d, - TokenDecorationRenderer decorationRenderer, - boolean isSelected, boolean isMoving, boolean isHover) { var timer = CodeTimer.get(); - if (decorationRenderer != null) { - decorationRenderer.renderDecorations( - true, viewModel, position, g2d, isSelected, isMoving, isHover); - } + 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()) - ? token.getTokenOpacity() / 2f - : isSelected && isHover ? 1 : token.getTokenOpacity(); + ? Math.max(0.5f, token.getTokenOpacity() / 2f) + : isSelected || isHover ? 1 : token.getTokenOpacity(); timer.start("TokenRenderer-loadImageTable"); if (token.getHasImageTable() && !imageTableMap.containsKey(token.getImageTableName())) { @@ -91,10 +81,9 @@ public void renderToken( renderHelper.render(g2d, worldG -> paintTokenImage(worldG, position, opacity)); timer.stop("TokenRenderer-paintTokenImage"); - if (decorationRenderer != null) { - decorationRenderer.renderDecorations( - false, viewModel, position, g2d, isSelected, isMoving, isHover); - } + decorationRenderer.renderDecorations( + false, viewModel, position, g2d, isSelected, isMoving, isHover); + timer.stop("TokenRenderer-renderToken"); } From 11ca0d0919c466c770d3316b880bb4ae24d00195 Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:39:25 +0800 Subject: [PATCH 5/8] tweak --- .../client/ui/zone/renderer/tokenRender/StateRenderer.java | 5 ----- 1 file changed, 5 deletions(-) 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 index 627cd93d9c..73b59f470e 100644 --- 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 @@ -96,11 +96,6 @@ public void renderOverlay( || !overlay.showPlayer(position.token(), MapTool.getPlayer())) { continue; } - if (overlay instanceof TwoToneBarTokenOverlay tt) { - overlay = - new TwoToneCircleBarTokenOverlay( - tt.getName(), tt.getBarColor(), tt.getBgColor(), tt.getThickness(), tt.getSide()); - } overlay.paintOverlay(overlayG, position.token(), bounds, value); } overlayG.dispose(); From 6e37e0cec22b87db7a04a8676cfe34062523b793 Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:00:53 +0800 Subject: [PATCH 6/8] moved state opacity calculations from state classes to StateRenderer. Opacity is now multiple of state opacity and token opacity --- .../client/ui/token/ColorDotTokenOverlay.java | 7 ------- .../client/ui/token/CrossTokenOverlay.java | 7 ------- .../client/ui/token/DiamondTokenOverlay.java | 8 +------- .../client/ui/token/FlowColorDotTokenOverlay.java | 7 ------- .../client/ui/token/ImageTokenOverlay.java | 5 ----- .../maptool/client/ui/token/OTokenOverlay.java | 7 ------- .../client/ui/token/ShadedTokenOverlay.java | 7 ------- .../client/ui/token/TriangleTokenOverlay.java | 7 ------- .../maptool/client/ui/token/XTokenOverlay.java | 8 -------- .../client/ui/token/YieldTokenOverlay.java | 7 ------- .../zone/renderer/tokenRender/StateRenderer.java | 15 +++++++++++++++ 11 files changed, 16 insertions(+), 69 deletions(-) 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/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/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/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/renderer/tokenRender/StateRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/tokenRender/StateRenderer.java index 73b59f470e..16680393cb 100644 --- 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 @@ -87,6 +87,12 @@ public void renderOverlay( (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); @@ -96,6 +102,15 @@ public void renderOverlay( || !overlay.showPlayer(position.token(), MapTool.getPlayer())) { continue; } + overlayG.setComposite( + AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, alpha * (float) overlay.getOpacity() / 100)); + + if (overlay instanceof TwoToneBarTokenOverlay tt) { + overlay = + new CircleBarTokenOverlay( + tt.getName(), tt.getBarColor(), tt.getBgColor(), tt.getThickness(), tt.getSide()); + } overlay.paintOverlay(overlayG, position.token(), bounds, value); } overlayG.dispose(); From 983bd597edbee0342aa5708c7a89fd8a8eee8e8f Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:07:07 +0800 Subject: [PATCH 7/8] moved bar opacity calculations from bar classes to StateRenderer. --- .../client/ui/token/MultipleImageBarTokenOverlay.java | 8 -------- .../client/ui/token/SingleImageBarTokenOverlay.java | 8 -------- .../maptool/client/ui/token/TwoImageBarTokenOverlay.java | 8 -------- 3 files changed, 24 deletions(-) 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/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/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); } /** From a02b0436c3eaf5d14ecd76d1e6ee2ce1f4b876fc Mon Sep 17 00:00:00 2001 From: bubblobill <45483160+bubblobill@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:09:07 +0800 Subject: [PATCH 8/8] sigh --- .../client/ui/zone/renderer/tokenRender/StateRenderer.java | 5 ----- 1 file changed, 5 deletions(-) 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 index 16680393cb..ee883efb67 100644 --- 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 @@ -106,11 +106,6 @@ public void renderOverlay( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha * (float) overlay.getOpacity() / 100)); - if (overlay instanceof TwoToneBarTokenOverlay tt) { - overlay = - new CircleBarTokenOverlay( - tt.getName(), tt.getBarColor(), tt.getBgColor(), tt.getThickness(), tt.getSide()); - } overlay.paintOverlay(overlayG, position.token(), bounds, value); } overlayG.dispose();