Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ jsvg = { group = "com.github.weisj", name = "jsvg", version = "1.4.0" }

handlebars = { group = "com.github.jknack", name = "handlebars", version.ref = "handlebars" }
handlebars-helpers = { group = "com.github.jknack", name = "handlebars-helpers", version.ref = "handlebars" }
handlebars-json = { group = "com.github.jknack", name = "handlebars-jackson2", version = "4.3.1" }

# Apache commons and other utilities
# parsing of configuration data
Expand Down Expand Up @@ -213,7 +214,7 @@ flatlaf = [
"flatlaf-extras",
"flatlaf-jide-oss",
]
handlebars = ["handlebars", "handlebars-helpers"]
handlebars = ["handlebars", "handlebars-helpers", "handlebars-json"]
junit = ["junit-api", "junit-engine", "junit-params"]
jai-imageio = ["jai-imageio-core", "jai-imageio-jpeg"]
graalvm-js = ["graalvm-js", "graalvm-js-scriptengine"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public class DeveloperOptions {
new PreferenceStore(Preferences.userRoot().node(AppConstants.APP_NAME + "/dev"));

public static final class Toggle {
public static final Preference<Boolean> EnableHandlebarsDebugging =
store.defineBoolean(
"enableHandlebarsDebugging",
"Preferences.developer.enableHandlebarsDebugging.label",
"Preferences.developer.enableHandlebarsDebugging.tooltip",
false);
public static final Preference<Boolean> AutoSaveMeasuredInSeconds =
store.defineBoolean(
"autoSaveMeasuredInSeconds",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ private Object getProperties(
seg, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6]));
pi.next();
}

StringBuilder stringBuilder = new StringBuilder(sd.toNonLocalisedString());
stringBuilder.append("segments=").append(String.join(",", segments)).append(";");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,37 @@
*/
package net.rptools.maptool.client.ui.sheet.stats;

import com.github.jknack.handlebars.*;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import javafx.application.Platform;
import net.rptools.maptool.client.AppConstants;
import net.rptools.maptool.client.DeveloperOptions;
import net.rptools.maptool.client.MapTool;
import net.rptools.maptool.client.ui.htmlframe.HTMLContent;
import net.rptools.maptool.model.Token;
import net.rptools.maptool.model.sheet.stats.StatSheetContext;
import net.rptools.maptool.model.sheet.stats.StatSheetLocation;
import net.rptools.maptool.util.HBDebugUtil;
import net.rptools.maptool.util.HandlebarsUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Class that represents a pop up stat sheet. */
public class StatSheet {

/** Object for logging messages. */
private static final Logger log = LogManager.getLogger(StatSheet.class);
private static final Logger log = LoggerFactory.getLogger(StatSheet.class);

private static final HBDebugUtil HBD;

static {
HBDebugUtil hbd = null;
if (DeveloperOptions.Toggle.EnableHandlebarsDebugging.get()) {
hbd = new HBDebugUtil();
}
HBD = hbd;
}

Comment thread
bubblobill marked this conversation as resolved.
Outdated
/**
* Sets the content for the stat sheet. The content is a HTML page that is rendered using the
Expand Down Expand Up @@ -67,6 +80,10 @@ public void setContent(Token token, String content, URL entry, StatSheetLocation
null);
}
});
if (HBD != null) {
Platform.runLater(
() -> HBD.publish(statSheetContext, token, content, entry, output.getHtmlString()));
}
} catch (IOException e) {
MapTool.showError("msg.error.renderingStatSheet", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,35 @@
*/
package net.rptools.maptool.model.sheet.stats;

import java.awt.Dimension;
import java.util.ArrayList;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.text.Collator;
import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
import net.rptools.lib.AwtUtil;
import net.rptools.lib.MD5Key;
import net.rptools.maptool.client.AppPreferences;
import net.rptools.maptool.client.AppUtil;
import net.rptools.maptool.client.MapTool;
import net.rptools.maptool.client.MapToolVariableResolver;
import net.rptools.maptool.client.ui.token.AbstractTokenOverlay;
import net.rptools.maptool.client.ui.token.BarTokenOverlay;
import net.rptools.maptool.model.Token;
import net.rptools.maptool.model.player.Player;
import net.rptools.maptool.util.HTMLUtil;
import net.rptools.maptool.util.ImageManager;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;

/** Class that extracts and provides the information needed to render a stat sheet. */
@SuppressWarnings("unused")
public class StatSheetContext {

/** Class that represents a token property on a stat sheet. */
static class Property {
public static class Property {
/** Name of the property. */
private final String name;

Expand All @@ -56,7 +65,7 @@ static class Property {
* @param displayName Display Name of the property.
* @param value Value of the property.
* @param gmOnly True if the property is GM only.
* @note GM only properties are only extracted if the player is a GM.
* @implNote GM only properties are only extracted if the player is a GM.
*/
Property(String name, String displayName, String shortName, Object value, boolean gmOnly) {
this.name = name;
Expand Down Expand Up @@ -139,6 +148,12 @@ public String getShortName() {
/** The properties of the token. */
private final List<Property> properties = new ArrayList<>();

/** The bars shown on the token. */
private final List<Map<String, Object>> bars = new ArrayList<>();

/** The states set on the token. */
private final List<Map<String, Object>> states = new ArrayList<>();

/** The notes of the token. */
private final String notes;

Expand Down Expand Up @@ -168,11 +183,27 @@ public String getShortName() {
* @param location The location of the stat sheet.
*/
public StatSheetContext(Token token, Player player, StatSheetLocation location) {
boolean playerOwns = AppUtil.playerOwns(token);
boolean playerIsGm = player.isGM();

name = token.getName();
tokenType = token.getType().name();

if (player.isGM()) {
/* Combined list of Bar and State names */
final List<String> OVERLAY_NAMES =
Stream.concat(
MapTool.getCampaign().getTokenBarsMap().keySet().stream(),
MapTool.getCampaign().getTokenStatesMap().keySet().stream())
.toList();

for (String stateName : OVERLAY_NAMES) {
Object stateValue = token.getState(stateName);
if (stateValue != null) {
addBarOrState(stateName, stateValue, playerOwns, playerIsGm);
}
}

if (playerIsGm) {
gmName = token.getGMName();
gmNotes = token.getGMNotes();
gmNotesType = token.getNotesType();
Expand All @@ -183,8 +214,8 @@ public StatSheetContext(Token token, Player player, StatSheetLocation location)
gmNotesType = null;
gm = false;
}
notes = AppUtil.playerOwns(token) ? token.getNotes() : null;
notesType = AppUtil.playerOwns(token) ? token.getNotesType() : null;
notes = playerOwns ? token.getNotes() : null;
notesType = playerOwns ? token.getNotesType() : null;
speechName = token.getSpeechName();

if (AppPreferences.showPortrait.get()) {
Expand All @@ -202,11 +233,11 @@ public StatSheetContext(Token token, Player player, StatSheetLocation location)
.forEach(
tp -> {
if (tp.isShowOnStatSheet()) {
if (tp.isGMOnly() && !MapTool.getPlayer().isGM()) {
if (tp.isGMOnly() && !playerIsGm) {
return;
}

if (tp.isOwnerOnly() && !AppUtil.playerOwns(token)) {
if (tp.isOwnerOnly() && !playerOwns) {
return;
}

Expand All @@ -215,10 +246,8 @@ public StatSheetContext(Token token, Player player, StatSheetLocation location)
return;
}

if (value instanceof String svalue) {
if (svalue.isBlank()) {
return;
}
if (value instanceof String sValue && sValue.isBlank()) {
return;
}
properties.add(
new Property(
Expand All @@ -232,11 +261,9 @@ public StatSheetContext(Token token, Player player, StatSheetLocation location)

Dimension dim;
if (token.getPortraitImage() != null) {
var image = ImageManager.getImage(token.getPortraitImage());
dim = new Dimension(image.getWidth(), image.getHeight());
dim = getImageDimensions.apply(token.getPortraitImage());
} else {
var image = ImageManager.getImage(token.getImageAssetId());
dim = new Dimension(image.getWidth(), image.getHeight());
dim = getImageDimensions.apply(token.getImageAssetId());
}
AwtUtil.constrainTo(dim, AppPreferences.portraitSize.get());
portraitWidth = dim.width;
Expand All @@ -255,6 +282,106 @@ public StatSheetContext(Token token, Player player, StatSheetLocation location)
};
}

private static final Function<MD5Key, Dimension> getImageDimensions =
md5Key -> {
BufferedImage image = ImageManager.getImage(md5Key);
return new Dimension(image.getWidth(), image.getHeight());
};

/** Comparator for sorting State Groups */
private static final Comparator<Map<String, Object>> stateComparator =
(o1, o2) -> {
String s1 = o1.get("group").toString();
String s2 = o2.get("group").toString();
// for different groups use natural order by group value
int result = Collator.getInstance().compare(s1, s2);
if (result != 0) {
return result;
}
// for the same group, use the "order" value - should always be present
if (Objects.equals(s1, s2)
&& o1.get("order") instanceof Integer i1
&& o2.get("order") instanceof Integer i2) {
return i1.compareTo(i2);
}
return 0; // should never reach this point
};

/**
* Method for filtering overlays and adding them to the appropriate data set
*
* @param overlayName Name of bar or state
* @param overlayValue Value attached to bar or state
* @param playerOwns Used for filtering what to display
* @param playerIsGm Used for filtering what to display
*/
private void addBarOrState(
String overlayName, Object overlayValue, boolean playerOwns, boolean playerIsGm) {

AbstractTokenOverlay ato;
if (MapTool.getCampaign().getTokenBarsMap().containsKey(overlayName)) {
ato = MapTool.getCampaign().getTokenBarsMap().get(overlayName);
} else {
ato = MapTool.getCampaign().getTokenStatesMap().get(overlayName);
}
if (ato == null) {
return;
}
if ((ato.isShowOthers() && !playerOwns)
Comment thread
bubblobill marked this conversation as resolved.
|| (playerOwns && ato.isShowOwner())
|| (playerIsGm && ato.isShowGM())) {
Map<String, Object> featureMap = new HashMap<>();
featureMap.put(
"type",
ato.getClass()
.getSimpleName()
.replaceAll("BarTokenOverlay", "")
.replaceAll("TokenOverlay", ""));
try {
PropertyUtilsBean pub = BeanUtilsBean.getInstance().getPropertyUtils();
featureMap.putAll(pub.describe(ato));
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
String mName;
Map<String, Object> aspectRatioMap = new HashMap<>();
for (Map.Entry<String, Object> entry : featureMap.entrySet()) {
Object value = entry.getValue();
if (value instanceof Color color) {
featureMap.put(
entry.getKey(),
String.format(
"rgba(%d,%d,%d,%#.3f)",
color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() / 255f));
} else if (value instanceof MD5Key id) {
featureMap.put(entry.getKey(), String.format("asset://%s", id));
Dimension dim = getImageDimensions.apply(id);
aspectRatioMap.put(entry.getKey() + "AspectRatio", dim.getWidth() / dim.getHeight());
} else if (value instanceof MD5Key[] idArray) {
String[] strOut = new String[idArray.length];
double[] arOut = new double[idArray.length];
for (int i = 0; i < idArray.length; i++) {
strOut[i] = String.format("asset://%s", idArray[i].toString());
Dimension dim = getImageDimensions.apply(idArray[i]);
arOut[i] = dim.getWidth() / dim.getHeight();
}
aspectRatioMap.put(entry.getKey() + "AspectRatio", arOut);
featureMap.put(entry.getKey(), strOut);
}
}
featureMap.putAll(aspectRatioMap);
if (ato instanceof BarTokenOverlay) {
featureMap.put(
"value", overlayValue instanceof BigDecimal bd ? bd.doubleValue() : overlayValue);
featureMap.remove("group"); // does not apply to bars
featureMap.remove("order"); // does not apply to bars
bars.add(featureMap);
} else {
states.add(featureMap);
}
}
}

/**
* Returns the name of the token.
*
Expand Down Expand Up @@ -328,9 +455,9 @@ public List<Property> getProperties() {
}

/**
* Returns the css class for the location of the stat sheet.
* Returns the CSS class for the location of the stat sheet.
*
* @return The css class for the location of the stat sheet.
* @return The CSS class for the location of the stat sheet.
*/
public String getStatSheetLocation() {
return statSheetLocation;
Expand Down Expand Up @@ -388,4 +515,19 @@ public String getTokenType() {
public boolean isGm() {
return gm;
}

/**
* @return States set on the token.
*/
public List<Map<String, Object>> getStates() {
states.sort(stateComparator);
return states;
}

/**
* @return Bars available on the token.
*/
public List<Map<String, Object>> getBars() {
return bars;
}
}
Loading
Loading