Skip to content

Commit

Permalink
Add overlay() function to display a html overlay
Browse files Browse the repository at this point in the history
- Add function overlay() to display a transparent html 3.2 overlay over the map
- Function is used similarly to frame() and dialog(), with [overlay():{ myhtml }] used to display "myhtml"
- Discussed in RPTools#1425
  • Loading branch information
Merudo committed Mar 31, 2020
1 parent 2c2cc4a commit 6437a0f
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 31 deletions.
15 changes: 15 additions & 0 deletions src/main/java/net/rptools/maptool/client/MapToolLineParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ private enum CodeType { // Mutually exclusive code-execution options
private enum OutputLoc { // Mutually exclusive output location
CHAT,
DIALOG,
OVERLAY,
DIALOG5,
FRAME,
FRAME5
Expand Down Expand Up @@ -357,6 +358,8 @@ private enum OptionType {
DIALOG5("dialog5", 1, 2, "\"\""),
// HTML webView
FRAME5("frame5", 1, 2, "\"\""),
// HTML overlay
OVERLAY("overlay", 0, 1, "\"\""),
// Run for another token
TOKEN("token", 1, 1);

Expand Down Expand Up @@ -517,6 +520,10 @@ private void parseOptionString(String optionString, int start) throws RollOption
matcher.region(start, endOfString);
List<String> paramList = new ArrayList<String>();
boolean lastItem = false; // true if last match ended in ")"
if (")".equals(optionString.substring(start))) {
lastItem = true;
start += 1;
}

while (!lastItem) {
if (matcher.find()) {
Expand Down Expand Up @@ -1028,6 +1035,11 @@ public String parseLine(
frameOpts = option.getParsedParam(1, resolver, tokenInContext).toString();
outputTo = OutputLoc.FRAME5;
break;
case OVERLAY:
codeType = CodeType.CODEBLOCK;
outputTo = OutputLoc.OVERLAY;
frameOpts = option.getParsedParam(0, resolver, tokenInContext).toString();
break;
///////////////////////////////////////////////////
// CODE OPTIONS
///////////////////////////////////////////////////
Expand Down Expand Up @@ -1424,6 +1436,9 @@ public String parseLine(
HTMLFrameFactory.show(
frameName, false, false, frameOpts, expressionBuilder.toString());
break;
case OVERLAY:
MapTool.getFrame().getHtmlOverlay().updateContents(expressionBuilder.toString());
break;
case CHAT:
builder.append(expressionBuilder);
break;
Expand Down
58 changes: 45 additions & 13 deletions src/main/java/net/rptools/maptool/client/ui/MapToolFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,7 @@

import com.jidesoft.docking.DefaultDockableHolder;
import com.jidesoft.docking.DockableFrame;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.GraphicsConfiguration;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.IllegalComponentStateException;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.*;
import java.awt.desktop.AboutEvent;
import java.awt.desktop.AboutHandler;
import java.awt.desktop.PreferencesEvent;
Expand Down Expand Up @@ -123,6 +112,7 @@
import net.rptools.maptool.client.ui.drawpanel.DrawPanelTreeCellRenderer;
import net.rptools.maptool.client.ui.drawpanel.DrawPanelTreeModel;
import net.rptools.maptool.client.ui.drawpanel.DrawablesPanel;
import net.rptools.maptool.client.ui.htmlframe.HTMLOverlay;
import net.rptools.maptool.client.ui.lookuptable.LookupTablePanel;
import net.rptools.maptool.client.ui.macrobuttons.buttons.MacroButton;
import net.rptools.maptool.client.ui.macrobuttons.panels.*;
Expand Down Expand Up @@ -181,6 +171,8 @@ public class MapToolFrame extends DefaultDockableHolder
private final ClientConnectionPanel connectionPanel;
/** The panel showing the initiative order. */
private final InitiativePanel initiativePanel;
/** The HTML pane showing the map overlay. */
private final HTMLOverlay htmlOverlay;

private final PointerOverlay pointerOverlay;
private final CommandPanel commandPanel;
Expand Down Expand Up @@ -462,6 +454,7 @@ public MapToolFrame(JMenuBar menuBar) {
connectionPanel = createConnectionPanel();
toolbox = new Toolbox();
initiativePanel = createInitiativePanel();
htmlOverlay = new HTMLOverlay();

zoneRendererList = new CopyOnWriteArrayList<ZoneRenderer>();
pointerOverlay = new PointerOverlay();
Expand Down Expand Up @@ -512,9 +505,15 @@ public MapToolFrame(JMenuBar menuBar) {
commandPanel = new CommandPanel();
MapTool.getMessageList().addObserver(commandPanel);

// Setup a JLayeredPane to display the HTML overlay over the map.
JLayeredPane zoneRenderLayered = new JLayeredPane();
zoneRenderLayered.setLayout(new LayeredPaneLayout());
zoneRenderLayered.add(zoneRendererPanel, JLayeredPane.DEFAULT_LAYER);
zoneRenderLayered.add(htmlOverlay, JLayeredPane.POPUP_LAYER);

rendererBorderPanel = new JPanel(new GridLayout());
rendererBorderPanel.setBorder(BorderFactory.createLineBorder(Color.darkGray));
rendererBorderPanel.add(zoneRendererPanel);
rendererBorderPanel.add(zoneRenderLayered);
toolbarPanel = new ToolbarPanel(toolbox);

// Put it all together
Expand Down Expand Up @@ -1509,6 +1508,11 @@ public ZoneRenderer getCurrentZoneRenderer() {
return currentRenderer;
}

/** @return the HTMLOverlay */
public HTMLOverlay getHtmlOverlay() {
return htmlOverlay;
}

public void addZoneRenderer(ZoneRenderer renderer) {
zoneRendererList.add(renderer);
}
Expand Down Expand Up @@ -2101,4 +2105,32 @@ public void actionPerformed(ActionEvent e) {
}
}
}

/**
* Layout for LayeredPanel where the bounds of every component is set to the size of the parent.
*/
private static class LayeredPaneLayout implements LayoutManager {
@Override
public void addLayoutComponent(String name, Component comp) {}

@Override
public void removeLayoutComponent(Component comp) {}

@Override
public Dimension preferredLayoutSize(Container parent) {
return parent.getSize();
}

@Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}

@Override
public void layoutContainer(Container parent) {
for (Component comp : parent.getComponents()) {
comp.setBounds(0, 0, parent.getWidth(), parent.getHeight());
}
}
}
}
181 changes: 181 additions & 0 deletions src/main/java/net/rptools/maptool/client/ui/htmlframe/HTMLOverlay.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* 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
* <http://www.gnu.org/licenses/> and specifically the Affero license
* text at <http://www.gnu.org/licenses/agpl.html>.
*/
package net.rptools.maptool.client.ui.htmlframe;

import java.awt.*;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.TooManyListenersException;
import javax.swing.*;
import javax.swing.text.*;
import net.rptools.maptool.client.AppPreferences;
import net.rptools.maptool.client.MapTool;
import net.rptools.maptool.client.ScreenPoint;
import net.rptools.maptool.client.TransferableHelper;
import net.rptools.maptool.client.ui.zone.ZoneRenderer;
import net.rptools.maptool.model.Token;
import net.rptools.maptool.model.ZonePoint;

/** Represents the transparent HTML overlay over the map. */
public class HTMLOverlay extends HTMLPane implements DropTargetListener {
/** The default rule for an invisible body tag. */
private static final String CSS_RULE_BODY =
"body { font-family: sans-serif; font-size: %dpt; background: none}";

public HTMLOverlay() {
super();
setFocusable(false);
setHighlighter(null);
setOpaque(false);
addMouseListeners();
setCaretColor(new Color(0, 0, 0, 0)); // invisible, needed or it shows in DnD operations

setTransferHandler(new TransferableHelper()); // set the Drag & Drop handler
try {
getDropTarget().addDropTargetListener(this);
} catch (TooManyListenersException e1) {
// Should never happen because the transfer handler fixes this problem.
}
}

/**
* Return the rule for an invisible body.
*
* @return the rule
*/
@Override
public String getRuleBody() {
return String.format(CSS_RULE_BODY, AppPreferences.getFontSize());
}

@Override
public void dragEnter(DropTargetDragEvent dtde) {}

@Override
public void dragOver(DropTargetDragEvent dtde) {}

@Override
public void dropActionChanged(DropTargetDragEvent dtde) {}

@Override
public void dragExit(DropTargetEvent dte) {}

/**
* Add the tokens to the current zone renderer if a token is dropped on the overlay.
*
* @param dtde the event of the drop
*/
@Override
public void drop(DropTargetDropEvent dtde) {
ZoneRenderer zr = MapTool.getFrame().getCurrentZoneRenderer();
Point point = SwingUtilities.convertPoint(this, dtde.getLocation(), zr);

ZonePoint zp = new ScreenPoint((int) point.getX(), (int) point.getY()).convertToZone(zr);
TransferableHelper th = (TransferableHelper) getTransferHandler();
List<Token> tokens = th.getTokens();
if (tokens != null && !tokens.isEmpty()) {
zr.addTokens(tokens, zp, th.getConfigureTokens(), false);
}
}

/** Add the mouse listeners to forward the mouse events to the current ZoneRenderer. */
private void addMouseListeners() {
addMouseWheelListener(this::passMouseEvent);
addMouseMotionListener(
new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
passMouseEvent(e);
}

@Override
public void mouseDragged(MouseEvent e) {
passMouseEvent(e);
}
});
addMouseListener(
new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
passMouseEvent(e, true);
}

@Override
public void mouseClicked(MouseEvent e) {
passMouseEvent(e, true);
}

@Override
public void mouseEntered(MouseEvent e) {
passMouseEvent(e);
}

@Override
public void mouseReleased(MouseEvent e) {
passMouseEvent(e);
}

@Override
public void mouseExited(MouseEvent e) {
passMouseEvent(e);
}
});
}

/**
* Passes a mouse event to the ZoneRenderer. If checking for transparency, only forwards the event
* if it happened over a transparent pixel.
*
* @param e the mouse event to forward
* @param checkForTransparency whether to check for transparency
*/
private void passMouseEvent(MouseEvent e, boolean checkForTransparency) {
SwingUtilities.invokeLater(
() -> {
if (checkForTransparency && isOpaque(e.getPoint())) {
return; // don't forward
}
Component c = MapTool.getFrame().getCurrentZoneRenderer();
c.dispatchEvent(SwingUtilities.convertMouseEvent(e.getComponent(), e, c));
});
}

/**
* Passes a mouse event to the ZoneRenderer.
*
* @param e the mouse event to forward
*/
private void passMouseEvent(MouseEvent e) {
passMouseEvent(e, false);
}

/**
* Returns true if the pixel of the component at the point is opaque.
*
* @param p the point
* @return true if the pixel is opaque
*/
public boolean isOpaque(Point p) {
Rectangle rect = getBounds();
BufferedImage img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_ARGB);
paintAll(img.createGraphics());
return new Color(img.getRGB(p.x, p.y), true).getAlpha() != 0;
}
}
Loading

0 comments on commit 6437a0f

Please sign in to comment.