From 0fd85d65e5e09c46162fff864d7a0f5b3b68ad87 Mon Sep 17 00:00:00 2001 From: Tyler Hardy Date: Thu, 9 Nov 2017 21:04:03 -0500 Subject: [PATCH] runelite-client: add tooltip system --- .../client/ui/overlay/OverlayRenderer.java | 13 +- .../client/ui/overlay/tooltips/Tooltip.java | 47 +++++ .../ui/overlay/tooltips/TooltipPriority.java | 32 ++++ .../ui/overlay/tooltips/TooltipRenderer.java | 176 ++++++++++++++++++ 4 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/Tooltip.java create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/TooltipPriority.java create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/TooltipRenderer.java diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java index f5bdbce3a0..b9de6813be 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -28,17 +28,18 @@ import java.awt.image.BufferedImage; import net.runelite.client.RuneLite; import net.runelite.client.plugins.Plugin; import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay; +import net.runelite.client.ui.overlay.tooltips.TooltipRenderer; public class OverlayRenderer { - private final InfoBoxOverlay infoBoxOverlay = new InfoBoxOverlay(); + private final TooltipRenderer tooltipRenderer = new TooltipRenderer(); + private final InfoBoxOverlay infoBoxOverlay = new InfoBoxOverlay(tooltipRenderer); public void render(BufferedImage clientBuffer) { TopDownRendererLeft tdl = new TopDownRendererLeft(); TopDownRendererRight tdr = new TopDownRendererRight(); DynamicRenderer dr = new DynamicRenderer(); - for (Plugin plugin : RuneLite.getRunelite().getPluginManager().getPlugins()) { for (Overlay overlay : plugin.getOverlays()) @@ -63,5 +64,13 @@ public class OverlayRenderer tdl.render(clientBuffer); tdr.render(clientBuffer); dr.render(clientBuffer); + + // tooltips are always rendered on top of other overlays + tooltipRenderer.render(clientBuffer); + } + + public TooltipRenderer getTooltipRenderer() + { + return tooltipRenderer; } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/Tooltip.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/Tooltip.java new file mode 100644 index 0000000000..459a700db4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/Tooltip.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, Tyler + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.overlay.tooltips; + +public class Tooltip +{ + private final TooltipPriority priority; // if multiple overlays exist in the same position, who wins + private final String tooltipText; + + public Tooltip(TooltipPriority priority, String tooltipText) + { + this.priority = priority; + this.tooltipText = tooltipText; + } + + public TooltipPriority getPriority() + { + return priority; + } + + public String getText() + { + return tooltipText; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/TooltipPriority.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/TooltipPriority.java new file mode 100644 index 0000000000..f63e4ceac0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/TooltipPriority.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017, Tyler + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.overlay.tooltips; + +public enum TooltipPriority +{ + LOW, + MED, + HIGH; +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/TooltipRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/TooltipRenderer.java new file mode 100644 index 0000000000..157a62bc60 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/tooltips/TooltipRenderer.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2017, Tyler + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.ui.overlay.tooltips; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.runelite.api.Client; +import net.runelite.api.Point; +import net.runelite.client.RuneLite; +import net.runelite.client.config.RuneliteConfig; +import net.runelite.client.ui.overlay.Renderer; + +public class TooltipRenderer implements Renderer +{ + private static final Pattern COLOR_SPLIT = Pattern.compile("<\\/?col=?([^>]+)?>"); + private static final Pattern BR = Pattern.compile("
"); + + private static final int BORDER_SIZE = 2; + private static final int JSWING_BORDER_RIGHT = 5; + private static final Color BACKGROUND_COLOR = new Color(Color.gray.getRed(), Color.gray.getGreen(), Color.gray.getBlue(), 150); + private static final Color BORDER_COLOR = Color.black; + private static final Color FONT_COLOR = Color.white; + + private final Client client = RuneLite.getClient(); + private final RuneliteConfig config = RuneLite.getRunelite().getConfig(); + + private Tooltip tooltip; + + @Override + public void render(BufferedImage clientBuffer) + { + Point mousePos = client.getMouseCanvasPosition(); + Graphics2D graphics = clientBuffer.createGraphics(); + Renderer.setAntiAliasing(graphics); + drawTooltip(graphics, mousePos.getX(), mousePos.getY()); + graphics.dispose(); + + this.tooltip = null; + } + + public void add(Tooltip tooltip) + { + if (this.tooltip != null && tooltip.getPriority().compareTo(this.tooltip.getPriority()) < 0) + { + return; + } + + this.tooltip = tooltip; + } + + private void drawTooltip(Graphics2D graphics, int x, int y) + { + if (tooltip == null || tooltip.getText() == null || tooltip.getText().isEmpty()) + { + return; + } + + FontMetrics metrics = graphics.getFontMetrics(); + String tooltipText = tooltip.getText(); + int tooltipWidth = 0; + int tooltipHeight = 0; + int textHeight = metrics.getHeight(); + int textDescent = metrics.getDescent(); + + // Tooltip size + String[] lines = BR.split(tooltipText); + for (String line : lines) + { + String lineClean = COLOR_SPLIT.matcher(line).replaceAll(""); + int textWidth = metrics.stringWidth(lineClean); + if (textWidth > tooltipWidth) + { + tooltipWidth = textWidth; + } + + tooltipHeight += textHeight; + } + + // Position tooltip + if (config.tooltipLeft()) + { + x = x - tooltipWidth; + if (x < 0) + { + x = 0; + } + } + else + { + int clientWidth = client.getCanvas().getWidth(); + if (x + tooltipWidth + JSWING_BORDER_RIGHT > clientWidth) + { + x = clientWidth - tooltipWidth - JSWING_BORDER_RIGHT; + } + } + + y = y - tooltipHeight; + if (y < 0) + { + y = 0; + } + + // Render tooltip - background + graphics.setColor(BACKGROUND_COLOR); + graphics.fillRect(x, y, tooltipWidth + BORDER_SIZE * 2, tooltipHeight); + graphics.setColor(BORDER_COLOR); + graphics.drawRect(x, y, tooltipWidth + BORDER_SIZE * 2, tooltipHeight); + graphics.setColor(FONT_COLOR); + + // Render tooltip - text - line by line + int lineX; + Color nextColor = Color.WHITE; + for (int i = 0; i < lines.length; i++) + { + lineX = x; + String line = lines[i]; + Matcher m = COLOR_SPLIT.matcher(line); + + int begin = 0; + while (m.find()) + { + // Draw text prior to color tag + String preText = line.substring(begin, m.start()); + graphics.setColor(Color.BLACK); + graphics.drawString(preText, lineX + BORDER_SIZE + 1, y + (i + 1) * textHeight - textDescent + 1); // shadow + graphics.setColor(nextColor); + graphics.drawString(preText, lineX + BORDER_SIZE, y + (i + 1) * textHeight - textDescent); // text + + // Set color for next text part + if (m.group(1) == null) + { + // no color tag + nextColor = Color.WHITE; + } + else + { + // color tag + nextColor = Color.decode("#" + m.group(1)); + } + begin = m.end(); + lineX += metrics.stringWidth(preText); + } + // Draw trailing text (after last tag) + graphics.setColor(Color.BLACK); + graphics.drawString(line.substring(begin, line.length()), lineX + BORDER_SIZE + 1, y + (i + 1) * textHeight - textDescent + 1); + graphics.setColor(nextColor); + graphics.drawString(line.substring(begin, line.length()), lineX + BORDER_SIZE, y + (i + 1) * textHeight - textDescent); + } + } +}