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 774a4e1f65..d638930821 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -38,7 +38,6 @@ import net.rptools.lib.AwtUtil; import net.rptools.lib.CodeTimer; import net.rptools.lib.MD5Key; -import net.rptools.lib.StringUtil; import net.rptools.maptool.client.*; import net.rptools.maptool.client.events.TokenHoverEnter; import net.rptools.maptool.client.events.TokenHoverExit; @@ -49,6 +48,7 @@ import net.rptools.maptool.client.ui.theme.Images; import net.rptools.maptool.client.ui.theme.RessourceManager; import net.rptools.maptool.client.ui.zone.FogUtil; +import net.rptools.maptool.client.ui.zone.Marker; import net.rptools.maptool.client.ui.zone.PlayerView; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.events.MapToolEventBus; @@ -88,7 +88,7 @@ public class PointerTool extends DefaultTool { private boolean mouseButtonDown = false; private Token tokenUnderMouse; - private Token markerUnderMouse; + private Marker markerUnderMouse; private int keysDown; // used to record whether Shift/Ctrl/Meta keys are down private final TokenStackPanel tokenStackPanel = new TokenStackPanel(); @@ -400,7 +400,7 @@ public void mousePressed(MouseEvent e) { if (isShowingHover) { isShowingHover = false; hoverTokenNotes = null; - markerUnderMouse = renderer.getMarkerAt(e.getX(), e.getY()); + markerUnderMouse = renderer.getViewModel().getMarkerAt(e.getX(), e.getY()); repaint(); } if (isShowingTokenStackPopup) { @@ -668,14 +668,11 @@ public void mouseMoved(MouseEvent e) { SwingUtil.isShiftDown(keysDown), SwingUtil.isControlDown(keysDown))); } - Token marker = renderer.getMarkerAt(mouseX, mouseY); - if (!AppUtil.tokenIsVisible(renderer.getZone(), marker, renderer.getPlayerView())) { - marker = null; - } + Marker marker = renderer.getViewModel().getMarkerAt(mouseX, mouseY); if (marker != markerUnderMouse && marker != null) { markerUnderMouse = marker; renderer.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - MapTool.getFrame().setStatusMessage(markerUnderMouse.getName()); + MapTool.getFrame().setStatusMessage(markerUnderMouse.name()); } else if (marker == null && markerUnderMouse != null) { markerUnderMouse = null; renderer.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); @@ -1678,42 +1675,36 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet())) { } } - private String createHoverNote(Token marker) { - var notes = HTMLUtil.htmlize(marker.getNotes(), marker.getNotesType()); - var gmNotes = HTMLUtil.htmlize(marker.getGMNotes(), marker.getGmNotesType()); - - boolean showGMNotes = MapTool.getPlayer().isGM() && !StringUtil.isEmpty(gmNotes); - boolean showNotes = !StringUtil.isEmpty(notes); + private String createHoverNote(Marker marker) { + boolean showGMNotes = marker.gmNotes() != null; + boolean showNotes = marker.playerNotes() != null; StringBuilder builder = new StringBuilder(); - if (marker.getPortraitImage() != null) { + if (marker.imageKey() != null) { builder.append("
"); } if (showGMNotes || showNotes) { - builder.append("").append(marker.getName()); - if (MapTool.getPlayer().isGM() - && !StringUtil.isEmpty(marker.getGMName()) - && !marker.getName().equals(marker.getGMName())) { - builder.append(" (").append(marker.getGMName()).append(")"); + builder.append("").append(marker.name()); + if (marker.gmName() != null && !marker.name().equals(marker.gmName())) { + builder.append(" (").append(marker.gmName()).append(")"); } builder.append("
"); } + if (showNotes) { - builder.append(notes); - // add a gap between player and gmNotes - if (showGMNotes) { - builder.append("

"); - } + builder.append( + HTMLUtil.htmlize(marker.playerNotes().note(), marker.playerNotes().mimeType())); } if (showGMNotes) { if (showNotes) { - builder.append("GM Notes
"); + // add a gap and heading between player and gmNotes + builder.append("

GM Notes
"); } - builder.append(gmNotes); + builder.append(HTMLUtil.htmlize(marker.gmNotes().note(), marker.gmNotes().mimeType())); } - if (marker.getPortraitImage() != null) { - BufferedImage image = ImageManager.getImageAndWait(marker.getPortraitImage()); + if (marker.imageKey() != null) { + BufferedImage image = ImageManager.getImageAndWait(marker.imageKey()); Dimension imgSize = new Dimension(image.getWidth(), image.getHeight()); if (imgSize.width > AppConstants.NOTE_PORTRAIT_SIZE || imgSize.height > AppConstants.NOTE_PORTRAIT_SIZE) { @@ -1722,15 +1713,14 @@ private String createHoverNote(Token marker) { builder.append("
"); builder .append("
"); } - String hoverText = builder.toString(); - return hoverText; + return builder.toString(); } private static final class TokenDragOp { diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/Marker.java b/src/main/java/net/rptools/maptool/client/ui/zone/Marker.java new file mode 100644 index 0000000000..626915072d --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/zone/Marker.java @@ -0,0 +1,27 @@ +/* + * 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; + +import java.awt.geom.Area; +import javax.annotation.Nullable; +import net.rptools.lib.MD5Key; + +public record Marker( + Area bounds, + String name, + @Nullable String gmName, + @Nullable Notes playerNotes, + @Nullable Notes gmNotes, + @Nullable MD5Key imageKey) {} diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/Notes.java b/src/main/java/net/rptools/maptool/client/ui/zone/Notes.java new file mode 100644 index 0000000000..11885235dc --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/ui/zone/Notes.java @@ -0,0 +1,17 @@ +/* + * 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; + +public record Notes(String note, String mimeType) {} 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 913e8d33e4..e91e16363f 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 @@ -118,7 +118,7 @@ public static TokenPosition fromToken(Token token, Zone zone) { private final Map> tokenPositionsByLayer = CollectionUtil.newFilledEnumMap(Zone.Layer.class, l -> new LinkedList<>()); - private final List markerList = new ArrayList<>(); + private final List markerList = new ArrayList<>(); private final Map> tokenStackMap = new HashMap<>(); private final Map> visibleTokensByLayer = @@ -229,8 +229,14 @@ public List getTokenPositionsForLayer(Zone.Layer layer) { tokenPositionsByLayer.getOrDefault(layer, Collections.emptyList())); } - public List getMarkerPositions() { - return Collections.unmodifiableList(markerList); + public @Nullable Marker getMarkerAt(int x, int y) { + var zonePoint = zoneScale.toWorldSpace(x, y); + for (var marker : markerList.reversed()) { + if (marker.bounds().contains(zonePoint)) { + return marker; + } + } + return null; } public Map> getTokenStackMap() { @@ -277,7 +283,7 @@ public void update() { updateVisibleArea(); updateMovingTokens(); updateTokenPositions(); - updateMarkerPositions(); + updateMarkerList(); updateTokenStacks(); updateVisibleTokens(); updateLightPosition(); @@ -397,17 +403,48 @@ private void updateTokenPositions() { } } - /** Updates {@link #markerList} based on {@link #tokenPositionsByLayer}. */ - private void updateMarkerPositions() { + /** + * Updates {@link #markerList} based on {@link #playerView}, {@link #tokenPositionsByLayer}, and + * {@link #visibleTokensByLayer} + */ + private void updateMarkerList() { + var isGM = playerView.isGMView(); + markerList.clear(); - for (var list : tokenPositionsByLayer.values()) { - for (var tokenPosition : list) { + for (var entry : tokenPositionsByLayer.entrySet()) { + var layer = entry.getKey(); + if (!layer.isMarkerLayer()) { + // No markers in this layer, so don't bother iterating over them. + continue; + } + + for (var tokenPosition : entry.getValue()) { var token = tokenPosition.token(); - var playerCanSeeMarker = - MapTool.getPlayer().isGM() || !StringUtil.isEmpty(token.getNotes()); - if (tokenPosition.token().isMarker() && playerCanSeeMarker) { - markerList.add(tokenPosition); + if (!visibleTokensByLayer.get(layer).contains(token.getId())) { + // No value in storing markers the player can't see. + continue; + } + + var marker = + new Marker( + tokenPosition.transformedBounds(), + token.getName(), + isGM && !StringUtil.isEmpty(token.getGMName()) ? token.getGMName() : null, + StringUtil.isEmpty(token.getNotes()) + ? null + : new Notes(token.getNotes(), token.getNotesType()), + StringUtil.isEmpty(token.getGMNotes()) || !isGM + ? null + : new Notes(token.getGMNotes(), token.getGmNotesType()), + token.getPortraitImage()); + + // A GM sees a marker if it has any notes or a portrait. + // A player sees a marker only if it has player notes. + if (marker.playerNotes() != null + || marker.imageKey() != null + || (isGM && marker.gmNotes() != null)) { + markerList.add(marker); } } } 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 7ebc2dcdfd..6a4ea1ad55 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 @@ -2162,20 +2162,6 @@ public Rectangle getLabelBounds(Label label) { return null; } - public @Nullable Token getMarkerAt(int x, int y) { - var zonePoint = viewModel.getZoneScale().toWorldSpace(x, y); - - List positionList = - new ArrayList<>(viewModel.getMarkerPositions()); - Collections.reverse(positionList); - for (ZoneViewModel.TokenPosition position : positionList) { - if (position.transformedBounds().contains(zonePoint)) { - return position.token(); - } - } - return null; - } - public List getTokenStackAt(int x, int y) { var tokenStackMap = viewModel.getTokenStackMap(); diff --git a/src/main/java/net/rptools/maptool/model/Token.java b/src/main/java/net/rptools/maptool/model/Token.java index 43881adf30..073a0f85e3 100644 --- a/src/main/java/net/rptools/maptool/model/Token.java +++ b/src/main/java/net/rptools/maptool/model/Token.java @@ -43,7 +43,6 @@ import javax.swing.ImageIcon; import net.rptools.CaseInsensitiveHashMap; import net.rptools.lib.MD5Key; -import net.rptools.lib.StringUtil; import net.rptools.lib.image.ImageUtil; import net.rptools.lib.transferable.TokenTransferData; import net.rptools.maptool.client.AppUtil; @@ -613,11 +612,6 @@ public int getHeight() { } } - public boolean isMarker() { - return getLayer().isMarkerLayer() - && (!StringUtil.isEmpty(notes) || !StringUtil.isEmpty(gmNotes) || portraitImage != null); - } - public String getPropertyType() { return propertyType; }