Add movable overlay support
This overhauls most of the overlay renderer to do so. There are 4 (or 5) "snap corners" that let overlays change their position, or you can set a custom location which is at a set x/y. Calculate which layer overlays are on up front and build separate lists for each layer. The fixed-point under-widget overlays are moved to above-widget so that they work when moved above the interfaces.
This commit is contained in:
@@ -24,11 +24,16 @@
|
||||
*/
|
||||
package net.runelite.client.ui.overlay;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public abstract class Overlay implements RenderableEntity
|
||||
{
|
||||
private Point preferredLocation;
|
||||
private OverlayPosition preferredPosition;
|
||||
private Rectangle bounds = new Rectangle();
|
||||
private OverlayPosition position = OverlayPosition.TOP_LEFT;
|
||||
private OverlayPriority priority = OverlayPriority.NONE;
|
||||
private OverlayLayer layer = OverlayLayer.UNDER_WIDGETS;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||
* 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;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
import static net.runelite.client.ui.overlay.OverlayPosition.ABOVE_CHATBOX_RIGHT;
|
||||
import static net.runelite.client.ui.overlay.OverlayPosition.BOTTOM_LEFT;
|
||||
import static net.runelite.client.ui.overlay.OverlayPosition.BOTTOM_RIGHT;
|
||||
import static net.runelite.client.ui.overlay.OverlayPosition.TOP_LEFT;
|
||||
import static net.runelite.client.ui.overlay.OverlayPosition.TOP_RIGHT;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Value
|
||||
class OverlayBounds
|
||||
{
|
||||
private final Rectangle topLeft, topRight, bottomLeft, bottomRight, aboveChatboxRight;
|
||||
|
||||
OverlayBounds(OverlayBounds other)
|
||||
{
|
||||
topLeft = new Rectangle(other.topLeft);
|
||||
topRight = new Rectangle(other.topRight);
|
||||
bottomLeft = new Rectangle(other.bottomLeft);
|
||||
bottomRight = new Rectangle(other.bottomRight);
|
||||
aboveChatboxRight = new Rectangle(other.aboveChatboxRight);
|
||||
}
|
||||
|
||||
Rectangle forPosition(OverlayPosition overlayPosition)
|
||||
{
|
||||
switch (overlayPosition)
|
||||
{
|
||||
case TOP_LEFT:
|
||||
return topLeft;
|
||||
case TOP_RIGHT:
|
||||
return topRight;
|
||||
case BOTTOM_LEFT:
|
||||
return bottomLeft;
|
||||
case BOTTOM_RIGHT:
|
||||
return bottomRight;
|
||||
case ABOVE_CHATBOX_RIGHT:
|
||||
return aboveChatboxRight;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
OverlayPosition fromBounds(Rectangle bounds)
|
||||
{
|
||||
if (bounds == topLeft)
|
||||
{
|
||||
return TOP_LEFT;
|
||||
}
|
||||
else if (bounds == topRight)
|
||||
{
|
||||
return TOP_RIGHT;
|
||||
}
|
||||
else if (bounds == bottomLeft)
|
||||
{
|
||||
return BOTTOM_LEFT;
|
||||
}
|
||||
else if (bounds == bottomRight)
|
||||
{
|
||||
return BOTTOM_RIGHT;
|
||||
}
|
||||
else if (bounds == aboveChatboxRight)
|
||||
{
|
||||
return ABOVE_CHATBOX_RIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Rectangle> getBounds()
|
||||
{
|
||||
return Arrays.asList(topLeft, topRight, bottomLeft, bottomRight, aboveChatboxRight);
|
||||
}
|
||||
}
|
||||
+451
-135
@@ -31,6 +31,8 @@ import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -42,21 +44,28 @@ import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import javax.swing.SwingUtilities;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.ResizeableChanged;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.config.RuneLiteConfig;
|
||||
import net.runelite.client.events.PluginChanged;
|
||||
import net.runelite.client.input.KeyListener;
|
||||
import net.runelite.client.input.KeyManager;
|
||||
import net.runelite.client.input.MouseListener;
|
||||
import net.runelite.client.input.MouseManager;
|
||||
import net.runelite.client.plugins.PluginManager;
|
||||
import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
|
||||
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class OverlayRenderer
|
||||
public class OverlayRenderer extends MouseListener implements KeyListener
|
||||
{
|
||||
private static final int BORDER_LEFT_RESIZABLE = 5;
|
||||
private static final int BORDER_TOP_RESIZABLE = 20;
|
||||
@@ -66,29 +75,56 @@ public class OverlayRenderer
|
||||
private static final int BORDER_RIGHT = 2;
|
||||
private static final int BORDER_BOTTOM = 2;
|
||||
private static final int PADDING = 2;
|
||||
private static final Dimension SNAP_CORNER_SIZE = new Dimension(80, 80);
|
||||
private static final Color SNAP_CORNER_COLOR = new Color(0, 255, 255, 50);
|
||||
private static final Color SNAP_CORNER_ACTIVE_COLOR = new Color(0, 255, 0, 100);
|
||||
private static final Color MOVING_OVERLAY_COLOR = new Color(255, 255, 0, 100);
|
||||
private static final Color MOVING_OVERLAY_ACTIVE_COLOR = new Color(255, 255, 0, 200);
|
||||
private static final String OVERLAY_CONFIG_PREFERRED_LOCATION = "_preferredLocation";
|
||||
private static final String OVERLAY_CONFIG_PREFERRED_POSITION = "_preferredPosition";
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
private final Provider<Client> clientProvider;
|
||||
private final InfoBoxOverlay infoBoxOverlay;
|
||||
private final ConfigManager configManager;
|
||||
private final TooltipOverlay tooltipOverlay;
|
||||
private final List<Overlay> allOverlays = new CopyOnWriteArrayList<>();
|
||||
private final List<Overlay> overlaysAboveScene = new CopyOnWriteArrayList<>(),
|
||||
overlaysUnderWidgets = new CopyOnWriteArrayList<>(),
|
||||
overlaysAboveWidgets = new CopyOnWriteArrayList<>(),
|
||||
overlaysOnTop = new CopyOnWriteArrayList<>();
|
||||
private final ConcurrentLinkedQueue<Consumer<BufferedImage>> screenshotRequests = new ConcurrentLinkedQueue<>();
|
||||
private final String runeliteGroupName = RuneLiteConfig.class.getAnnotation(ConfigGroup.class).keyName();
|
||||
|
||||
// Overlay movement variables
|
||||
private final Point overlayOffset = new Point();
|
||||
private final Point mousePosition = new Point();
|
||||
private Overlay movedOverlay;
|
||||
private boolean inOverlayDraggingMode;
|
||||
|
||||
// Overlay state validation
|
||||
private Rectangle viewportBounds;
|
||||
private boolean chatboxHidden;
|
||||
private boolean isResizeable;
|
||||
private OverlayBounds snapCorners;
|
||||
|
||||
@Inject
|
||||
PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
Provider<Client> clientProvider;
|
||||
|
||||
@Inject
|
||||
InfoBoxOverlay infoBoxOverlay;
|
||||
|
||||
@Inject
|
||||
TooltipOverlay tooltipOverlay;
|
||||
|
||||
private final List<Overlay> overlays = new CopyOnWriteArrayList<>();
|
||||
private BufferedImage surface;
|
||||
private Graphics2D surfaceGraphics;
|
||||
|
||||
private ConcurrentLinkedQueue<Consumer<BufferedImage>> screenshotRequests = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@Subscribe
|
||||
public void onResizableChanged(ResizeableChanged event)
|
||||
private OverlayRenderer(
|
||||
final Provider<Client> clientProvider,
|
||||
final PluginManager pluginManager,
|
||||
final MouseManager mouseManager,
|
||||
final KeyManager keyManager,
|
||||
final TooltipOverlay tooltipOverlay,
|
||||
final InfoBoxOverlay infoBoxOverlay,
|
||||
final ConfigManager configManager)
|
||||
{
|
||||
updateSurface();
|
||||
this.clientProvider = clientProvider;
|
||||
this.pluginManager = pluginManager;
|
||||
this.tooltipOverlay = tooltipOverlay;
|
||||
this.infoBoxOverlay = infoBoxOverlay;
|
||||
this.configManager = configManager;
|
||||
keyManager.registerKeyListener(this);
|
||||
mouseManager.registerMouseListener(this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -101,32 +137,101 @@ public class OverlayRenderer
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getGameState().equals(GameState.LOGIN_SCREEN) || event.getGameState().equals(GameState.LOGGED_IN))
|
||||
if (event.getGameState().equals(GameState.LOGGED_IN))
|
||||
{
|
||||
refreshPlugins();
|
||||
updateSurface();
|
||||
rebuildOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPluginChanged(PluginChanged event)
|
||||
{
|
||||
refreshPlugins();
|
||||
rebuildOverlays();
|
||||
}
|
||||
|
||||
private void refreshPlugins()
|
||||
private List<Overlay> getOverlaysForLayer(OverlayLayer layer)
|
||||
{
|
||||
overlays.clear();
|
||||
overlays.addAll(Stream
|
||||
switch (layer)
|
||||
{
|
||||
case ABOVE_SCENE:
|
||||
return overlaysAboveScene;
|
||||
case UNDER_WIDGETS:
|
||||
return overlaysUnderWidgets;
|
||||
case ABOVE_WIDGETS:
|
||||
return overlaysAboveWidgets;
|
||||
case ALWAYS_ON_TOP:
|
||||
return overlaysOnTop;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildOverlays()
|
||||
{
|
||||
List<Overlay> overlays = Stream
|
||||
.concat(
|
||||
pluginManager.getPlugins()
|
||||
.stream()
|
||||
.filter(plugin -> pluginManager.isPluginEnabled(plugin))
|
||||
.filter(pluginManager::isPluginEnabled)
|
||||
.flatMap(plugin -> plugin.getOverlays().stream()),
|
||||
Stream.of(infoBoxOverlay, tooltipOverlay))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()));
|
||||
.collect(Collectors.toList());
|
||||
|
||||
sortOverlays(overlays);
|
||||
|
||||
allOverlays.clear();
|
||||
allOverlays.addAll(overlays);
|
||||
|
||||
final Client client = clientProvider.get();
|
||||
|
||||
for (final Overlay overlay : overlays)
|
||||
{
|
||||
final Point location = loadOverlayLocation(overlay);
|
||||
|
||||
if (location != null
|
||||
&& client.getCanvas() != null
|
||||
&& !client.getCanvas().contains(location))
|
||||
{
|
||||
overlay.setPreferredLocation(null);
|
||||
saveOverlayLocation(overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
overlay.setPreferredLocation(location);
|
||||
}
|
||||
|
||||
final OverlayPosition position = loadOverlayPosition(overlay);
|
||||
overlay.setPreferredPosition(position);
|
||||
}
|
||||
|
||||
rebuildOverlayLayers();
|
||||
}
|
||||
|
||||
private void rebuildOverlayLayers()
|
||||
{
|
||||
overlaysAboveScene.clear();
|
||||
overlaysUnderWidgets.clear();
|
||||
overlaysAboveWidgets.clear();
|
||||
overlaysOnTop.clear();
|
||||
|
||||
for (final Overlay overlay : allOverlays)
|
||||
{
|
||||
OverlayLayer layer = overlay.getLayer();
|
||||
|
||||
if (overlay.getPreferredLocation() != null && overlay.getPreferredPosition() == null)
|
||||
{
|
||||
// When UNDER_WIDGET overlays are in preferred locations, move to
|
||||
// ABOVE_WIDGETS so that it can draw over interfaces
|
||||
if (layer == OverlayLayer.UNDER_WIDGETS)
|
||||
{
|
||||
layer = OverlayLayer.ABOVE_WIDGETS;
|
||||
}
|
||||
}
|
||||
|
||||
List<Overlay> overlayLayer = getOverlaysForLayer(layer);
|
||||
overlayLayer.add(overlay);
|
||||
}
|
||||
}
|
||||
|
||||
static void sortOverlays(List<Overlay> overlays)
|
||||
@@ -151,89 +256,54 @@ public class OverlayRenderer
|
||||
});
|
||||
}
|
||||
|
||||
private void updateSurface()
|
||||
public void render(Graphics2D graphics, final OverlayLayer layer)
|
||||
{
|
||||
final Client client = clientProvider.get();
|
||||
List<Overlay> overlays = getOverlaysForLayer(layer);
|
||||
|
||||
if (client == null)
|
||||
if (client == null
|
||||
|| overlays.isEmpty()
|
||||
|| client.getViewportWidget() == null
|
||||
|| client.getGameState() != GameState.LOGGED_IN
|
||||
|| client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN) != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final Dimension size = client.getCanvas().getSize();
|
||||
final BufferedImage temp = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
|
||||
final Graphics2D subGraphics = temp.createGraphics();
|
||||
subGraphics.setBackground(new Color(0, 0, 0, 0));
|
||||
OverlayUtil.setGraphicProperties(subGraphics);
|
||||
|
||||
surface = temp;
|
||||
|
||||
if (surfaceGraphics != null)
|
||||
if (shouldInvalidateOverlays())
|
||||
{
|
||||
surfaceGraphics.dispose();
|
||||
snapCorners = buildSnapCorners();
|
||||
}
|
||||
|
||||
surfaceGraphics = subGraphics;
|
||||
}
|
||||
|
||||
public void render(Graphics2D graphics, OverlayLayer layer)
|
||||
{
|
||||
final Client client = clientProvider.get();
|
||||
|
||||
if (client == null || surface == null || overlays.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.getGameState() != GameState.LOGGED_IN)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN) != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean isResizeable = client.isResized();
|
||||
final Widget viewport = client.getViewportWidget();
|
||||
final Rectangle bounds = viewport != null
|
||||
? new Rectangle(viewport.getBounds())
|
||||
: new Rectangle(0, 0, surface.getWidth(), surface.getHeight());
|
||||
|
||||
final Widget chatbox = client.getWidget(WidgetInfo.CHATBOX_MESSAGES);
|
||||
final Rectangle chatboxBounds = chatbox != null
|
||||
? chatbox.getBounds() : new Rectangle(0, bounds.height, 519, 165);
|
||||
// Create copy of snap corners because overlays will modify them
|
||||
OverlayBounds snapCorners = new OverlayBounds(this.snapCorners);
|
||||
|
||||
OverlayUtil.setGraphicProperties(graphics);
|
||||
final Point topLeftPoint = new Point();
|
||||
topLeftPoint.move(
|
||||
isResizeable ? BORDER_LEFT_RESIZABLE : BORDER_LEFT_FIXED,
|
||||
isResizeable ? BORDER_TOP_RESIZABLE : BORDER_TOP_FIXED);
|
||||
final Point topRightPoint = new Point();
|
||||
topRightPoint.move(bounds.x + bounds.width - BORDER_RIGHT, BORDER_TOP_FIXED);
|
||||
final Point bottomLeftPoint = new Point();
|
||||
bottomLeftPoint.move(isResizeable ? BORDER_LEFT_RESIZABLE : BORDER_LEFT_FIXED, bounds.y + bounds.height - BORDER_BOTTOM);
|
||||
final Point bottomRightPoint = new Point();
|
||||
bottomRightPoint.move(bounds.x + bounds.width - BORDER_RIGHT, bounds.y + bounds.height - BORDER_BOTTOM);
|
||||
final Point rightChatboxPoint = new Point();
|
||||
rightChatboxPoint.move(bounds.x + chatboxBounds.width - BORDER_RIGHT, bounds.y + bounds.height - BORDER_BOTTOM);
|
||||
|
||||
//check to see if Chatbox is minimized
|
||||
if (chatbox != null && isResizeable && chatbox.isHidden())
|
||||
// Draw snap corners
|
||||
if (layer == OverlayLayer.UNDER_WIDGETS && movedOverlay != null)
|
||||
{
|
||||
rightChatboxPoint.y += chatboxBounds.height;
|
||||
bottomLeftPoint.y += chatboxBounds.height;
|
||||
final OverlayBounds translatedSnapCorners = translateSnapCorners(snapCorners);
|
||||
final Color previous = graphics.getColor();
|
||||
|
||||
for (final Rectangle corner : translatedSnapCorners.getBounds())
|
||||
{
|
||||
graphics.setColor(corner.contains(mousePosition) ? SNAP_CORNER_ACTIVE_COLOR : SNAP_CORNER_COLOR);
|
||||
graphics.fill(corner);
|
||||
}
|
||||
|
||||
graphics.setColor(previous);
|
||||
}
|
||||
|
||||
for (Overlay overlay : overlays)
|
||||
{
|
||||
if (overlay.getLayer() != layer)
|
||||
OverlayPosition overlayPosition = overlay.getPosition();
|
||||
|
||||
if (overlay.getPreferredPosition() != null)
|
||||
{
|
||||
continue;
|
||||
overlayPosition = overlay.getPreferredPosition();
|
||||
}
|
||||
|
||||
OverlayPosition overlayPosition = overlay.getPosition();
|
||||
if (overlayPosition == OverlayPosition.ABOVE_CHATBOX_RIGHT && !client.isResized())
|
||||
{
|
||||
// On fixed mode, ABOVE_CHATBOX_RIGHT is in the same location as
|
||||
@@ -241,25 +311,6 @@ public class OverlayRenderer
|
||||
// drawing over each other.
|
||||
overlayPosition = OverlayPosition.BOTTOM_RIGHT;
|
||||
}
|
||||
final Point subPosition = new Point();
|
||||
switch (overlayPosition)
|
||||
{
|
||||
case BOTTOM_LEFT:
|
||||
subPosition.setLocation(bottomLeftPoint);
|
||||
break;
|
||||
case BOTTOM_RIGHT:
|
||||
subPosition.setLocation(bottomRightPoint);
|
||||
break;
|
||||
case TOP_LEFT:
|
||||
subPosition.setLocation(topLeftPoint);
|
||||
break;
|
||||
case TOP_RIGHT:
|
||||
subPosition.setLocation(topRightPoint);
|
||||
break;
|
||||
case ABOVE_CHATBOX_RIGHT:
|
||||
subPosition.setLocation(rightChatboxPoint);
|
||||
break;
|
||||
}
|
||||
|
||||
if (overlayPosition == OverlayPosition.DYNAMIC || overlayPosition == OverlayPosition.TOOLTIP)
|
||||
{
|
||||
@@ -267,48 +318,313 @@ public class OverlayRenderer
|
||||
}
|
||||
else
|
||||
{
|
||||
final Dimension dimension = MoreObjects.firstNonNull(safeRender(overlay, surfaceGraphics, subPosition), new Dimension());
|
||||
if (dimension.width == 0 && dimension.height == 0)
|
||||
final Point location = overlay.getBounds().getLocation();
|
||||
final Dimension dimension = overlay.getBounds().getSize();
|
||||
|
||||
// If the final position is not modified, layout it
|
||||
if (overlay.getPreferredLocation() == null || overlay.getPreferredPosition() != null)
|
||||
{
|
||||
final Rectangle snapCorner = snapCorners.forPosition(overlayPosition);
|
||||
final Point translation = OverlayUtil.transformPosition(overlayPosition, dimension);
|
||||
location.setLocation(snapCorner.getX() + translation.x, snapCorner.getY() + translation.y);
|
||||
final Point padding = OverlayUtil.padPosition(overlayPosition, dimension, PADDING);
|
||||
snapCorner.translate(padding.x, padding.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
location.setLocation(overlay.getPreferredLocation());
|
||||
}
|
||||
|
||||
final Dimension overlayDimension = MoreObjects.firstNonNull(
|
||||
safeRender(overlay, graphics, location),
|
||||
new Dimension());
|
||||
|
||||
overlay.setBounds(new Rectangle(location, overlayDimension));
|
||||
|
||||
if (overlayDimension.width == 0 && overlayDimension.height == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
final BufferedImage clippedImage = surface.getSubimage(0, 0, dimension.width, dimension.height);
|
||||
|
||||
switch (overlayPosition)
|
||||
if (inOverlayDraggingMode)
|
||||
{
|
||||
case BOTTOM_LEFT:
|
||||
bottomLeftPoint.x += dimension.width + (dimension.width == 0 ? 0 : PADDING);
|
||||
break;
|
||||
case BOTTOM_RIGHT:
|
||||
bottomRightPoint.x -= dimension.width + (dimension.width == 0 ? 0 : PADDING);
|
||||
break;
|
||||
case TOP_LEFT:
|
||||
topLeftPoint.y += dimension.height + (dimension.height == 0 ? 0 : PADDING);
|
||||
break;
|
||||
case TOP_RIGHT:
|
||||
topRightPoint.y += dimension.height + (dimension.height == 0 ? 0 : PADDING);
|
||||
break;
|
||||
case ABOVE_CHATBOX_RIGHT:
|
||||
rightChatboxPoint.y -= dimension.height + (dimension.height == 0 ? 0 : PADDING);
|
||||
break;
|
||||
final Color previous = graphics.getColor();
|
||||
graphics.setColor(movedOverlay == overlay ? MOVING_OVERLAY_ACTIVE_COLOR : MOVING_OVERLAY_COLOR);
|
||||
graphics.drawRect(location.x, location.y, dimension.width - 1, dimension.height - 1);
|
||||
graphics.setColor(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Point transformed = OverlayUtil.transformPosition(overlayPosition, dimension);
|
||||
graphics.drawImage(clippedImage, subPosition.x + transformed.x, subPosition.y + transformed.y, null);
|
||||
surfaceGraphics.clearRect(0, 0, (int) dimension.getWidth(), (int) dimension.getHeight());
|
||||
@Override
|
||||
public MouseEvent mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
if (!inOverlayDraggingMode)
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
final Point mousePoint = mouseEvent.getPoint();
|
||||
mousePosition.setLocation(mousePoint);
|
||||
|
||||
for (Overlay overlay : allOverlays)
|
||||
{
|
||||
if (overlay.getBounds().contains(mousePoint))
|
||||
{
|
||||
if (SwingUtilities.isRightMouseButton(mouseEvent))
|
||||
{
|
||||
overlay.setPreferredLocation(null);
|
||||
overlay.setPreferredPosition(null);
|
||||
rebuildOverlayLayers();
|
||||
}
|
||||
else
|
||||
{
|
||||
mousePoint.translate(-overlay.getBounds().x, -overlay.getBounds().y);
|
||||
overlayOffset.setLocation(mousePoint);
|
||||
movedOverlay = overlay;
|
||||
}
|
||||
|
||||
mouseEvent.consume();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseEvent mouseDragged(MouseEvent mouseEvent)
|
||||
{
|
||||
if (!inOverlayDraggingMode)
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
final Client client = clientProvider.get();
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
final Point mousePoint = mouseEvent.getPoint();
|
||||
mousePosition.setLocation(mousePoint);
|
||||
final Rectangle canvasRect = new Rectangle(client.getRealDimensions());
|
||||
|
||||
if (!canvasRect.contains(mousePoint))
|
||||
{
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
if (movedOverlay != null)
|
||||
{
|
||||
mousePoint.translate(-overlayOffset.x, -overlayOffset.y);
|
||||
movedOverlay.setPreferredPosition(null);
|
||||
movedOverlay.setPreferredLocation(mousePoint);
|
||||
rebuildOverlayLayers();
|
||||
mouseEvent.consume();
|
||||
}
|
||||
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MouseEvent mouseReleased(MouseEvent mouseEvent)
|
||||
{
|
||||
if (movedOverlay != null)
|
||||
{
|
||||
mousePosition.setLocation(-1, -1);
|
||||
final OverlayBounds snapCorners = translateSnapCorners(buildSnapCorners());
|
||||
|
||||
for (Rectangle snapCorner : snapCorners.getBounds())
|
||||
{
|
||||
if (snapCorner.contains(mouseEvent.getPoint()))
|
||||
{
|
||||
OverlayPosition position = snapCorners.fromBounds(snapCorner);
|
||||
if (position == movedOverlay.getPosition())
|
||||
{
|
||||
// overlay moves back to default position
|
||||
position = null;
|
||||
}
|
||||
movedOverlay.setPreferredPosition(position);
|
||||
movedOverlay.setPreferredLocation(null); // from dragging
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
saveOverlayPosition(movedOverlay);
|
||||
saveOverlayLocation(movedOverlay);
|
||||
rebuildOverlayLayers();
|
||||
movedOverlay = null;
|
||||
mouseEvent.consume();
|
||||
}
|
||||
|
||||
return mouseEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e)
|
||||
{
|
||||
if (e.isAltDown())
|
||||
{
|
||||
inOverlayDraggingMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e)
|
||||
{
|
||||
if (!e.isAltDown())
|
||||
{
|
||||
inOverlayDraggingMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Dimension safeRender(RenderableEntity entity, Graphics2D graphics, Point point)
|
||||
{
|
||||
final Graphics2D subGraphics = (Graphics2D) graphics.create();
|
||||
subGraphics.translate(point.x, point.y);
|
||||
final Dimension dimension = entity.render(subGraphics, point);
|
||||
subGraphics.dispose();
|
||||
return dimension;
|
||||
}
|
||||
|
||||
private boolean shouldInvalidateOverlays()
|
||||
{
|
||||
final Client client = clientProvider.get();
|
||||
final Widget widget = client.getWidget(WidgetInfo.CHATBOX_MESSAGES);
|
||||
final boolean resizeableChanged = isResizeable != client.isResized();
|
||||
|
||||
if (resizeableChanged)
|
||||
{
|
||||
isResizeable = client.isResized();
|
||||
return true;
|
||||
}
|
||||
|
||||
final boolean chatboxHiddenChanged = chatboxHidden != (widget != null && widget.isHidden());
|
||||
|
||||
if (chatboxHiddenChanged)
|
||||
{
|
||||
chatboxHidden = widget != null && widget.isHidden();
|
||||
return true;
|
||||
}
|
||||
|
||||
final boolean viewportChanged = !client.getViewportWidget().getBounds().equals(viewportBounds);
|
||||
|
||||
if (viewportChanged)
|
||||
{
|
||||
viewportBounds = client.getViewportWidget().getBounds();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private OverlayBounds buildSnapCorners()
|
||||
{
|
||||
final Client client = clientProvider.get();
|
||||
|
||||
final Rectangle bounds = viewportBounds != null
|
||||
? viewportBounds
|
||||
: new Rectangle(0, 0, client.getCanvas().getWidth(), client.getCanvas().getHeight());
|
||||
|
||||
final Widget chatbox = client.getWidget(WidgetInfo.CHATBOX_MESSAGES);
|
||||
final Rectangle chatboxBounds = chatbox != null
|
||||
? chatbox.getBounds() : new Rectangle(0, bounds.height, 519, 165);
|
||||
|
||||
final Point topLeftPoint = new Point(
|
||||
isResizeable ? BORDER_LEFT_RESIZABLE : BORDER_LEFT_FIXED,
|
||||
isResizeable ? BORDER_TOP_RESIZABLE : BORDER_TOP_FIXED);
|
||||
final Point topRightPoint = new Point(bounds.x + bounds.width - BORDER_RIGHT, BORDER_TOP_FIXED);
|
||||
final Point bottomLeftPoint = new Point(isResizeable ? BORDER_LEFT_RESIZABLE : BORDER_LEFT_FIXED, bounds.y + bounds.height - BORDER_BOTTOM);
|
||||
final Point bottomRightPoint = new Point(bounds.x + bounds.width - BORDER_RIGHT, bounds.y + bounds.height - BORDER_BOTTOM);
|
||||
final Point rightChatboxPoint = new Point(bounds.x + chatboxBounds.width - BORDER_RIGHT, bounds.y + bounds.height - BORDER_BOTTOM);
|
||||
|
||||
// Check to see if chat box is minimized
|
||||
if (chatbox != null && isResizeable && chatboxHidden)
|
||||
{
|
||||
rightChatboxPoint.y += chatboxBounds.height;
|
||||
bottomLeftPoint.y += chatboxBounds.height;
|
||||
}
|
||||
|
||||
return new OverlayBounds(
|
||||
new Rectangle(topLeftPoint, SNAP_CORNER_SIZE),
|
||||
new Rectangle(topRightPoint, SNAP_CORNER_SIZE),
|
||||
new Rectangle(bottomLeftPoint, SNAP_CORNER_SIZE),
|
||||
new Rectangle(bottomRightPoint, SNAP_CORNER_SIZE),
|
||||
new Rectangle(rightChatboxPoint, SNAP_CORNER_SIZE));
|
||||
}
|
||||
|
||||
private OverlayBounds translateSnapCorners(OverlayBounds snapCorners)
|
||||
{
|
||||
return new OverlayBounds(
|
||||
snapCorners.getTopLeft(),
|
||||
translate(snapCorners.getTopRight(), -SNAP_CORNER_SIZE.width, 0),
|
||||
translate(snapCorners.getBottomLeft(), 0, -SNAP_CORNER_SIZE.height),
|
||||
translate(snapCorners.getBottomRight(), -SNAP_CORNER_SIZE.width, -SNAP_CORNER_SIZE.height),
|
||||
translate(snapCorners.getAboveChatboxRight(), -SNAP_CORNER_SIZE.width, -SNAP_CORNER_SIZE.height));
|
||||
}
|
||||
|
||||
private static Rectangle translate(Rectangle rectangle, int dx, int dy)
|
||||
{
|
||||
rectangle.translate(dx, dy);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
private void saveOverlayLocation(final Overlay overlay)
|
||||
{
|
||||
final String key = overlay.getClass().getSimpleName() + OVERLAY_CONFIG_PREFERRED_LOCATION;
|
||||
if (overlay.getPreferredLocation() != null)
|
||||
{
|
||||
configManager.setConfiguration(
|
||||
runeliteGroupName,
|
||||
key,
|
||||
overlay.getPreferredLocation());
|
||||
}
|
||||
else
|
||||
{
|
||||
configManager.unsetConfiguration(
|
||||
runeliteGroupName,
|
||||
key);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveOverlayPosition(final Overlay overlay)
|
||||
{
|
||||
final String key = overlay.getClass().getSimpleName() + OVERLAY_CONFIG_PREFERRED_POSITION;
|
||||
if (overlay.getPreferredPosition() != null)
|
||||
{
|
||||
configManager.setConfiguration(
|
||||
runeliteGroupName,
|
||||
key,
|
||||
overlay.getPreferredPosition());
|
||||
}
|
||||
else
|
||||
{
|
||||
configManager.unsetConfiguration(
|
||||
runeliteGroupName,
|
||||
key);
|
||||
}
|
||||
}
|
||||
|
||||
private Point loadOverlayLocation(final Overlay overlay)
|
||||
{
|
||||
final String key = overlay.getClass().getSimpleName() + OVERLAY_CONFIG_PREFERRED_LOCATION;
|
||||
return configManager.getConfiguration(runeliteGroupName, key, Point.class);
|
||||
}
|
||||
|
||||
private OverlayPosition loadOverlayPosition(final Overlay overlay)
|
||||
{
|
||||
final String locationKey = overlay.getClass().getSimpleName() + OVERLAY_CONFIG_PREFERRED_POSITION;
|
||||
return configManager.getConfiguration(runeliteGroupName, locationKey, OverlayPosition.class);
|
||||
}
|
||||
|
||||
public void provideScreenshot(BufferedImage image)
|
||||
{
|
||||
Consumer<BufferedImage> consumer;
|
||||
|
||||
@@ -137,6 +137,35 @@ public class OverlayUtil
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
}
|
||||
|
||||
public static java.awt.Point padPosition(OverlayPosition position, Dimension dimension, final int padding)
|
||||
{
|
||||
final java.awt.Point result = new java.awt.Point();
|
||||
|
||||
switch (position)
|
||||
{
|
||||
case DYNAMIC:
|
||||
case TOOLTIP:
|
||||
break;
|
||||
case BOTTOM_LEFT:
|
||||
result.x += dimension.width + (dimension.width == 0 ? 0 : padding);
|
||||
break;
|
||||
case BOTTOM_RIGHT:
|
||||
result.x -= dimension.width + (dimension.width == 0 ? 0 : padding);
|
||||
break;
|
||||
case TOP_LEFT:
|
||||
result.y += dimension.height + (dimension.height == 0 ? 0 : padding);
|
||||
break;
|
||||
case TOP_RIGHT:
|
||||
result.y += dimension.height + (dimension.height == 0 ? 0 : padding);
|
||||
break;
|
||||
case ABOVE_CHATBOX_RIGHT:
|
||||
result.y -= dimension.height + (dimension.height == 0 ? 0 : padding);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static java.awt.Point transformPosition(OverlayPosition position, Dimension dimension)
|
||||
{
|
||||
final java.awt.Point result = new java.awt.Point();
|
||||
@@ -144,6 +173,7 @@ public class OverlayUtil
|
||||
switch (position)
|
||||
{
|
||||
case DYNAMIC:
|
||||
case TOOLTIP:
|
||||
case TOP_LEFT:
|
||||
break;
|
||||
case BOTTOM_LEFT:
|
||||
|
||||
Reference in New Issue
Block a user