From 34c685a82d73a906d18347c756db94a4cbb7e17a Mon Sep 17 00:00:00 2001 From: Max Weber Date: Tue, 27 Aug 2019 13:32:46 -0600 Subject: [PATCH 1/3] runelite-api: Add setAllWidgetsAreOpTargetable --- .../src/main/java/net/runelite/api/Client.java | 11 +++++++++++ .../src/main/java/net/runelite/api/MenuAction.java | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 56e2995e32..31e7c8afe1 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -38,6 +38,7 @@ import net.runelite.api.hooks.Callbacks; import net.runelite.api.hooks.DrawCallbacks; import net.runelite.api.vars.AccountType; import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; import net.runelite.api.widgets.WidgetInfo; import org.slf4j.Logger; @@ -1635,6 +1636,11 @@ public interface Client extends GameEngine */ int getIf1DraggedItemIndex(); + /** + * Is a widget is in target mode? + */ + boolean getSpellSelected(); + /** * Sets if a widget is in target mode */ @@ -1660,4 +1666,9 @@ public interface Client extends GameEngine * Returns the max item index + 1 from cache */ int getItemCount(); + + /** + * Makes all widgets behave as if they are {@link WidgetConfig#WIDGET_USE_TARGET} + */ + void setAllWidgetsAreOpTargetable(boolean value); } diff --git a/runelite-api/src/main/java/net/runelite/api/MenuAction.java b/runelite-api/src/main/java/net/runelite/api/MenuAction.java index cafbbe7706..688b18f141 100644 --- a/runelite-api/src/main/java/net/runelite/api/MenuAction.java +++ b/runelite-api/src/main/java/net/runelite/api/MenuAction.java @@ -230,6 +230,11 @@ public enum MenuAction */ WIDGET_DEFAULT(57), + /** + * Casting a spell / op target on a widget + */ + SPELL_CAST_ON_WIDGET(58), + /** * Menu action triggered by examining an object. */ From c6632af7bf2c21cac0e9e02a5c12d4bff85f589f Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 26 Aug 2019 18:46:28 -0600 Subject: [PATCH 2/3] runelite-client: Don't add our menu options in spell casting/target mode --- .../src/main/java/net/runelite/client/menus/MenuManager.java | 5 +++++ .../java/net/runelite/client/ui/overlay/OverlayRenderer.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java b/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java index 955c3ef0e5..863e58ea10 100644 --- a/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java +++ b/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java @@ -117,6 +117,11 @@ public class MenuManager @Subscribe public void onMenuEntryAdded(MenuEntryAdded event) { + if (client.getSpellSelected()) + { + return; + } + int widgetId = event.getActionParam1(); Collection options = managedMenuOptions.get(widgetId); 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 e0810cef00..81b405fe83 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 @@ -304,7 +304,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener graphics.setColor(previous); } - if (menuEntries == null && !client.isMenuOpen() && bounds.contains(mouse)) + if (menuEntries == null && !client.isMenuOpen() && !client.getSpellSelected() && bounds.contains(mouse)) { menuEntries = createRightClickMenuEntries(overlay); } From 33d233d9a59bf1f6be0760e770708860e5e1ae99 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Tue, 27 Aug 2019 19:46:52 -0600 Subject: [PATCH 3/3] WidgetInspector: Add widget picker --- .../main/java/net/runelite/api/SpriteID.java | 1 + .../plugins/devtools/DevToolsOverlay.java | 78 ----- .../plugins/devtools/WidgetInspector.java | 323 ++++++++++++++++-- .../devtools/WidgetInspectorOverlay.java | 132 +++++++ 4 files changed, 431 insertions(+), 103 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspectorOverlay.java diff --git a/runelite-api/src/main/java/net/runelite/api/SpriteID.java b/runelite-api/src/main/java/net/runelite/api/SpriteID.java index 35419da483..962089c146 100644 --- a/runelite-api/src/main/java/net/runelite/api/SpriteID.java +++ b/runelite-api/src/main/java/net/runelite/api/SpriteID.java @@ -1564,6 +1564,7 @@ public final class SpriteID public static final int MOBILE_FUNCTION_MODE_DISABLED = 1624; public static final int MOBILE_YELLOW_TOUCH_ANIMATION_1 = 1625; public static final int MOBILE_YELLOW_TOUCH_ANIMATION_2 = 1626; + public static final int MOBILE_FINGER_ON_INTERFACE = 1653; /* Unmapped: 1627~1701 */ public static final int BUTTON_FRIENDS = 1702; public static final int BUTTON_IGNORES = 1703; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java index 9bb7292dc3..457a86b766 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java @@ -36,8 +36,6 @@ import java.awt.geom.Rectangle2D; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; -import lombok.Getter; -import lombok.Setter; import net.runelite.api.Client; import net.runelite.api.Constants; import net.runelite.api.DecorativeObject; @@ -72,9 +70,6 @@ import net.runelite.client.ui.overlay.tooltip.TooltipManager; @Singleton class DevToolsOverlay extends Overlay { - private static final int ITEM_EMPTY = 6512; - private static final int ITEM_FILLED = 20594; - private static final Font FONT = FontManager.getRunescapeFont().deriveFont(Font.BOLD, 16); private static final Color RED = new Color(221, 44, 0); private static final Color GREEN = new Color(0, 200, 83); @@ -92,13 +87,6 @@ class DevToolsOverlay extends Overlay private final DevToolsPlugin plugin; private final TooltipManager toolTipManager; - @Setter - @Getter - private Widget widget; - - @Setter - private int itemIndex = -1; - @Inject private DevToolsOverlay(Client client, DevToolsPlugin plugin, TooltipManager toolTipManager) { @@ -145,8 +133,6 @@ class DevToolsOverlay extends Overlay renderGraphicsObjects(graphics); } - renderWidgets(graphics); - return null; } @@ -430,70 +416,6 @@ class DevToolsOverlay extends Overlay } } - private void renderWidgets(Graphics2D graphics) - { - if (widget == null || widget.isHidden()) - { - return; - } - - Rectangle childBounds = widget.getBounds(); - graphics.setColor(CYAN); - graphics.draw(childBounds); - - if (itemIndex == -1) - { - return; - } - - if (widget.getItemId() != ITEM_EMPTY - && widget.getItemId() != ITEM_FILLED) - { - Rectangle componentBounds = widget.getBounds(); - - graphics.setColor(ORANGE); - graphics.draw(componentBounds); - - renderWidgetText(graphics, componentBounds, widget.getItemId(), YELLOW); - } - - WidgetItem widgetItem = widget.getWidgetItem(itemIndex); - if (widgetItem == null - || widgetItem.getId() < 0 - || widgetItem.getId() == ITEM_EMPTY - || widgetItem.getId() == ITEM_FILLED) - { - return; - } - - Rectangle itemBounds = widgetItem.getCanvasBounds(); - - graphics.setColor(ORANGE); - graphics.draw(itemBounds); - - renderWidgetText(graphics, itemBounds, widgetItem.getId(), YELLOW); - } - - private void renderWidgetText(Graphics2D graphics, Rectangle bounds, int itemId, Color color) - { - if (itemId == -1) - { - return; - } - - String text = itemId + ""; - FontMetrics fm = graphics.getFontMetrics(); - Rectangle2D textBounds = fm.getStringBounds(text, graphics); - - int textX = (int) (bounds.getX() + (bounds.getWidth() / 2) - (textBounds.getWidth() / 2)); - int textY = (int) (bounds.getY() + (bounds.getHeight() / 2) + (textBounds.getHeight() / 2)); - - graphics.setColor(Color.BLACK); - graphics.drawString(text, textX + 1, textY + 1); - graphics.setColor(color); - graphics.drawString(text, textX, textY); - } - private void renderPlayerWireframe(Graphics2D graphics, Player player, Color color) { Polygon[] polys = player.getPolygons(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java index cea9a2b9c1..44839a52de 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java @@ -27,13 +27,20 @@ package net.runelite.client.plugins.devtools; import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Dimension; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Collection; +import java.util.Comparator; +import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.Stack; +import java.util.stream.Stream; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; @@ -45,31 +52,68 @@ import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.SpriteID; import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.widgets.JavaScriptCallback; import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetItem; +import net.runelite.api.widgets.WidgetType; import net.runelite.client.callback.ClientThread; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.ColorUtil; @Slf4j +@Singleton class WidgetInspector extends JFrame { + private static final Map widgetIdMap = new HashMap<>(); + + static final Color SELECTED_WIDGET_COLOR = Color.CYAN; + private static final float SELECTED_WIDGET_HUE; + + static + { + float[] hsb = new float[3]; + Color.RGBtoHSB(SELECTED_WIDGET_COLOR.getRed(), SELECTED_WIDGET_COLOR.getGreen(), SELECTED_WIDGET_COLOR.getBlue(), hsb); + SELECTED_WIDGET_HUE = hsb[0]; + } + private final Client client; private final ClientThread clientThread; private final DevToolsConfig config; - private final DevToolsOverlay overlay; - private final DevToolsPlugin plugin; + private final Provider overlay; + private final OverlayManager overlayManager; private final JTree widgetTree; private final WidgetInfoTableModel infoTableModel; private final JCheckBox alwaysOnTop; + private final JCheckBox hideHidden; - private static final Map widgetIdMap = new HashMap<>(); + private DefaultMutableTreeNode root; + + @Getter + private Widget selectedWidget; + + @Getter + private int selectedItem; + + private Widget picker = null; + + @Getter + private boolean pickerSelected = false; @Inject private WidgetInspector( @@ -77,16 +121,17 @@ class WidgetInspector extends JFrame ClientThread clientThread, WidgetInfoTableModel infoTableModel, DevToolsConfig config, + DevToolsPlugin plugin, EventBus eventBus, - DevToolsOverlay overlay, - DevToolsPlugin plugin) + Provider overlay, + OverlayManager overlayManager) { this.client = client; this.clientThread = clientThread; this.infoTableModel = infoTableModel; this.config = config; this.overlay = overlay; - this.plugin = plugin; + this.overlayManager = overlayManager; eventBus.register(this); @@ -116,16 +161,12 @@ class WidgetInspector extends JFrame { WidgetTreeNode node = (WidgetTreeNode) selected; Widget widget = node.getWidget(); - overlay.setWidget(widget); - overlay.setItemIndex(widget.getItemId()); - refreshInfo(); - log.debug("Set widget to {} and item index to {}", widget, widget.getItemId()); + setSelectedWidget(widget, -1, false); } else if (selected instanceof WidgetItemNode) { WidgetItemNode node = (WidgetItemNode) selected; - overlay.setItemIndex(node.getWidgetItem().getIndex()); - log.debug("Set item index to {}", node.getWidgetItem().getIndex()); + setSelectedWidget(node.getWidgetItem().getWidget(), node.getWidgetItem().getIndex(), false); } }); @@ -151,15 +192,20 @@ class WidgetInspector extends JFrame onConfigChanged(null); bottomPanel.add(alwaysOnTop); + hideHidden = new JCheckBox("Hide hidden"); + hideHidden.setSelected(true); + hideHidden.addItemListener(ev -> refreshWidgets()); + bottomPanel.add(hideHidden); + final JButton revalidateWidget = new JButton("Revalidate"); revalidateWidget.addActionListener(ev -> clientThread.invokeLater(() -> { - if (overlay.getWidget() == null) + if (selectedWidget == null) { return; } - overlay.getWidget().revalidate(); + selectedWidget.revalidate(); })); bottomPanel.add(revalidateWidget); @@ -182,10 +228,13 @@ class WidgetInspector extends JFrame clientThread.invokeLater(() -> { Widget[] rootWidgets = client.getWidgetRoots(); - DefaultMutableTreeNode root = new DefaultMutableTreeNode(); + root = new DefaultMutableTreeNode(); - overlay.setWidget(null); - overlay.setItemIndex(-1); + Widget wasSelectedWidget = selectedWidget; + int wasSelectedItem = selectedItem; + + selectedWidget = null; + selectedItem = -1; for (Widget widget : rootWidgets) { @@ -198,17 +247,15 @@ class WidgetInspector extends JFrame SwingUtilities.invokeLater(() -> { - overlay.setWidget(null); - overlay.setItemIndex(-1); - refreshInfo(); widgetTree.setModel(new DefaultTreeModel(root)); + setSelectedWidget(wasSelectedWidget, wasSelectedItem, true); }); }); } private DefaultMutableTreeNode addWidget(String type, Widget widget) { - if (widget == null || widget.isHidden()) + if (widget == null || (hideHidden.isSelected() && widget.isHidden())) { return null; } @@ -271,9 +318,70 @@ class WidgetInspector extends JFrame return node; } - private void refreshInfo() + private void setSelectedWidget(Widget widget, int item, boolean updateTree) { - infoTableModel.setWidget(overlay.getWidget()); + infoTableModel.setWidget(widget); + + if (this.selectedWidget == widget && this.selectedItem == item) + { + return; + } + + this.selectedWidget = widget; + this.selectedItem = item; + + if (root == null || !updateTree) + { + return; + } + + clientThread.invoke(() -> + { + Stack treePath = new Stack<>(); + for (Widget w = widget; w != null; w = w.getParent()) + { + treePath.push(w); + } + + DefaultMutableTreeNode node = root; + deeper: + for (; !treePath.empty(); ) + { + Widget w = treePath.pop(); + for (Enumeration it = node.children(); it.hasMoreElements(); ) + { + WidgetTreeNode inner = (WidgetTreeNode) it.nextElement(); + if (inner.getWidget().getId() == w.getId() && inner.getWidget().getIndex() == w.getIndex()) + { + node = inner; + continue deeper; + } + } + } + if (selectedItem != -1) + { + for (Enumeration it = node.children(); it.hasMoreElements(); ) + { + Object wiw = it.nextElement(); + if (wiw instanceof WidgetItemNode) + { + WidgetItemNode inner = (WidgetItemNode) wiw; + if (inner.getWidgetItem().getIndex() == selectedItem) + { + node = inner; + break; + } + } + } + } + + final DefaultMutableTreeNode fnode = node; + SwingUtilities.invokeLater(() -> + { + widgetTree.getSelectionModel().clearSelection(); + widgetTree.getSelectionModel().addSelectionPath(new TreePath(fnode.getPath())); + }); + }); } static WidgetInfo getWidgetInfo(int packedId) @@ -297,12 +405,177 @@ class WidgetInspector extends JFrame setVisible(true); toFront(); repaint(); + overlayManager.add(this.overlay.get()); + clientThread.invokeLater(this::addPickerWidget); } public void close() { - overlay.setWidget(null); - overlay.setItemIndex(-1); + overlayManager.remove(this.overlay.get()); + clientThread.invokeLater(this::removePickerWidget); + setSelectedWidget(null, -1, false); setVisible(false); } + + private void removePickerWidget() + { + if (picker == null) + { + return; + } + + Widget parent = picker.getParent(); + if (parent == null) + { + return; + } + + Widget[] children = parent.getChildren(); + if (children == null || children.length <= picker.getIndex() || children[picker.getIndex()] != picker) + { + return; + } + + children[picker.getIndex()] = null; + } + + private void addPickerWidget() + { + removePickerWidget(); + + int x = 10, y = 2; + Widget parent = client.getWidget(WidgetInfo.MINIMAP_ORBS); + if (parent == null) + { + Widget[] roots = client.getWidgetRoots(); + + parent = Stream.of(roots) + .filter(w -> w.getType() == WidgetType.LAYER && w.getContentType() == 0 && !w.isSelfHidden()) + .sorted(Comparator.comparing((Widget w) -> w.getRelativeX() + w.getRelativeY()) + .reversed() + .thenComparing(Widget::getId) + .reversed()) + .findFirst().get(); + x = 4; + y = 4; + } + + picker = parent.createChild(-1, WidgetType.GRAPHIC); + + log.info("Picker is {}.{} [{}]", WidgetInfo.TO_GROUP(picker.getId()), WidgetInfo.TO_CHILD(picker.getId()), picker.getIndex()); + + picker.setSpriteId(SpriteID.MOBILE_FINGER_ON_INTERFACE); + picker.setOriginalWidth(15); + picker.setOriginalHeight(17); + picker.setOriginalX(x); + picker.setOriginalY(y); + picker.revalidate(); + picker.setTargetVerb("Select"); + picker.setName("Pick"); + picker.setClickMask(WidgetConfig.USE_WIDGET | WidgetConfig.USE_ITEM); + picker.setNoClickThrough(true); + picker.setOnTargetEnterListener((JavaScriptCallback) ev -> + { + pickerSelected = true; + picker.setOpacity(30); + client.setAllWidgetsAreOpTargetable(true); + }); + picker.setOnTargetLeaveListener((JavaScriptCallback) ev -> onPickerDeselect()); + } + + private void onPickerDeselect() + { + client.setAllWidgetsAreOpTargetable(false); + picker.setOpacity(0); + pickerSelected = false; + } + + @Subscribe + private void onMenuOptionClicked(MenuOptionClicked ev) + { + if (!pickerSelected) + { + return; + } + + onPickerDeselect(); + client.setSpellSelected(false); + ev.consume(); + + Object target = getWidgetOrWidgetItemForMenuOption(ev.getMenuAction().getId(), ev.getActionParam(), ev.getWidgetId()); + if (target == null) + { + return; + } + if (target instanceof WidgetItem) + { + WidgetItem iw = (WidgetItem) target; + setSelectedWidget(iw.getWidget(), iw.getIndex(), true); + } + else + { + setSelectedWidget((Widget) target, -1, true); + } + } + + @Subscribe + private void onMenuEntryAdded(MenuEntryAdded event) + { + if (!pickerSelected) + { + return; + } + + MenuEntry[] menuEntries = client.getMenuEntries(); + + for (int i = 0; i < menuEntries.length; i++) + { + MenuEntry entry = menuEntries[i]; + if (entry.getType() != MenuAction.ITEM_USE_ON_WIDGET.getId() + && entry.getType() != MenuAction.SPELL_CAST_ON_WIDGET.getId()) + { + continue; + } + String name = WidgetInfo.TO_GROUP(entry.getParam1()) + "." + WidgetInfo.TO_CHILD(entry.getParam1()); + + if (entry.getParam0() != -1) + { + name += " [" + entry.getParam0() + "]"; + } + + Color color = colorForWidget(i, menuEntries.length); + + entry.setTarget(ColorUtil.wrapWithColorTag(name, color)); + } + + client.setMenuEntries(menuEntries); + } + + Color colorForWidget(int index, int length) + { + float h = SELECTED_WIDGET_HUE + .1f + (.8f / length) * index; + + return Color.getHSBColor(h, 1, 1); + } + + Object getWidgetOrWidgetItemForMenuOption(int type, int param0, int param1) + { + if (type == MenuAction.SPELL_CAST_ON_WIDGET.getId()) + { + Widget w = client.getWidget(WidgetInfo.TO_GROUP(param1), WidgetInfo.TO_CHILD(param1)); + if (param0 != -1) + { + w = w.getChild(param0); + } + + return w; + } + else if (type == MenuAction.ITEM_USE_ON_WIDGET.getId()) + { + Widget w = client.getWidget(WidgetInfo.TO_GROUP(param1), WidgetInfo.TO_CHILD(param1)); + return w.getWidgetItem(param0); + } + + return null; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspectorOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspectorOverlay.java new file mode 100644 index 0000000000..c0ceab94a8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspectorOverlay.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2017, Kronos + * Copyright (c) 2017, Adam + * 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.plugins.devtools; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.geom.Rectangle2D; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.api.Client; +import net.runelite.api.MenuEntry; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; + +@Singleton +public class WidgetInspectorOverlay extends Overlay +{ + private final Client client; + private final WidgetInspector inspector; + + @Inject + public WidgetInspectorOverlay( + Client client, + WidgetInspector inspector + ) + { + this.client = client; + this.inspector = inspector; + + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + setPriority(OverlayPriority.HIGHEST); + } + + @Override + public Dimension render(Graphics2D g) + { + Widget w = inspector.getSelectedWidget(); + if (w != null) + { + Object wiw = w; + if (inspector.getSelectedItem() != -1) + { + wiw = w.getWidgetItem(inspector.getSelectedItem()); + } + + renderWiw(g, wiw, WidgetInspector.SELECTED_WIDGET_COLOR); + } + + if (inspector.isPickerSelected()) + { + boolean menuOpen = client.isMenuOpen(); + + MenuEntry[] entries = client.getMenuEntries(); + for (int i = menuOpen ? 0 : entries.length - 1; i < entries.length; i++) + { + MenuEntry e = entries[i]; + + Object wiw = inspector.getWidgetOrWidgetItemForMenuOption(e.getType(), e.getParam0(), e.getParam1()); + if (wiw == null) + { + continue; + } + + Color color = inspector.colorForWidget(i, entries.length); + renderWiw(g, wiw, color); + } + } + + return null; + } + + private void renderWiw(Graphics2D g, Object wiw, Color color) + { + g.setColor(color); + + if (wiw instanceof WidgetItem) + { + WidgetItem wi = (WidgetItem) wiw; + Rectangle bounds = wi.getCanvasBounds(); + g.draw(bounds); + + String text = wi.getId() + ""; + FontMetrics fm = g.getFontMetrics(); + Rectangle2D textBounds = fm.getStringBounds(text, g); + + int textX = (int) (bounds.getX() + (bounds.getWidth() / 2) - (textBounds.getWidth() / 2)); + int textY = (int) (bounds.getY() + (bounds.getHeight() / 2) + (textBounds.getHeight() / 2)); + + g.setColor(Color.BLACK); + g.drawString(text, textX + 1, textY + 1); + g.setColor(Color.ORANGE); + g.drawString(text, textX, textY); + } + else + { + Widget w = (Widget) wiw; + g.draw(w.getBounds()); + } + } +}