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("
- .append(marker.getPortraitImage())
+ .append(marker.imageKey())
.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;
}