Merge pull request #989 from deathbeam/movable-overlays

Add support for movable overlays
This commit is contained in:
Adam
2018-03-22 21:22:18 -04:00
committed by GitHub
7 changed files with 633 additions and 135 deletions

View File

@@ -331,6 +331,8 @@ public interface Client extends GameEngine
Dimension getStretchedDimensions(); Dimension getStretchedDimensions();
Dimension getRealDimensions();
/** /**
* Changes world. Works only on login screen * Changes world. Works only on login screen
* @param world world * @param world world

View File

@@ -24,10 +24,12 @@
*/ */
package net.runelite.client.config; package net.runelite.client.config;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Point;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -244,6 +246,16 @@ public class ConfigManager
return properties.getProperty(groupName + "." + key); return properties.getProperty(groupName + "." + key);
} }
public <T> T getConfiguration(String groupName, String key, Class<T> clazz)
{
String value = getConfiguration(groupName, key);
if (!Strings.isNullOrEmpty(value))
{
return (T) stringToObject(value, clazz);
}
return null;
}
public void setConfiguration(String groupName, String key, String value) public void setConfiguration(String groupName, String key, String value)
{ {
log.debug("Setting configuration value for {}.{} to {}", groupName, key, value); log.debug("Setting configuration value for {}.{} to {}", groupName, key, value);
@@ -289,6 +301,11 @@ public class ConfigManager
eventBus.post(configChanged); eventBus.post(configChanged);
} }
public void setConfiguration(String groupName, String key, Object value)
{
setConfiguration(groupName, key, objectToString(value));
}
public void unsetConfiguration(String groupName, String key) public void unsetConfiguration(String groupName, String key)
{ {
log.debug("Unsetting configuration value for {}.{}", groupName, key); log.debug("Unsetting configuration value for {}.{}", groupName, key);
@@ -420,6 +437,13 @@ public class ConfigManager
int height = Integer.parseInt(splitStr[1]); int height = Integer.parseInt(splitStr[1]);
return new Dimension(width, height); return new Dimension(width, height);
} }
if (type == Point.class)
{
String[] splitStr = str.split(":");
int width = Integer.parseInt(splitStr[0]);
int height = Integer.parseInt(splitStr[1]);
return new Point(width, height);
}
if (type.isEnum()) if (type.isEnum())
{ {
return Enum.valueOf((Class<? extends Enum>) type, str); return Enum.valueOf((Class<? extends Enum>) type, str);
@@ -442,6 +466,11 @@ public class ConfigManager
Dimension d = (Dimension) object; Dimension d = (Dimension) object;
return d.width + "x" + d.height; return d.width + "x" + d.height;
} }
if (object instanceof Point)
{
Point p = (Point) object;
return p.x + ":" + p.y;
}
return object.toString(); return object.toString();
} }
} }

View File

@@ -24,11 +24,16 @@
*/ */
package net.runelite.client.ui.overlay; package net.runelite.client.ui.overlay;
import java.awt.Point;
import java.awt.Rectangle;
import lombok.Data; import lombok.Data;
@Data @Data
public abstract class Overlay implements RenderableEntity public abstract class Overlay implements RenderableEntity
{ {
private Point preferredLocation;
private OverlayPosition preferredPosition;
private Rectangle bounds = new Rectangle();
private OverlayPosition position = OverlayPosition.TOP_LEFT; private OverlayPosition position = OverlayPosition.TOP_LEFT;
private OverlayPriority priority = OverlayPriority.NONE; private OverlayPriority priority = OverlayPriority.NONE;
private OverlayLayer layer = OverlayLayer.UNDER_WIDGETS; private OverlayLayer layer = OverlayLayer.UNDER_WIDGETS;

View File

@@ -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);
}
}

View File

@@ -31,6 +31,8 @@ import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -42,21 +44,28 @@ import java.util.stream.Stream;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.swing.SwingUtilities;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.ResizeableChanged;
import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo; 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.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.plugins.PluginManager;
import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay; import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay; import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
@Singleton @Singleton
@Slf4j @Slf4j
public class OverlayRenderer public class OverlayRenderer extends MouseListener implements KeyListener
{ {
private static final int BORDER_LEFT_RESIZABLE = 5; private static final int BORDER_LEFT_RESIZABLE = 5;
private static final int BORDER_TOP_RESIZABLE = 20; 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_RIGHT = 2;
private static final int BORDER_BOTTOM = 2; private static final int BORDER_BOTTOM = 2;
private static final int PADDING = 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 @Inject
PluginManager pluginManager; private OverlayRenderer(
final Provider<Client> clientProvider,
@Inject final PluginManager pluginManager,
Provider<Client> clientProvider; final MouseManager mouseManager,
final KeyManager keyManager,
@Inject final TooltipOverlay tooltipOverlay,
InfoBoxOverlay infoBoxOverlay; final InfoBoxOverlay infoBoxOverlay,
final ConfigManager configManager)
@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)
{ {
updateSurface(); this.clientProvider = clientProvider;
this.pluginManager = pluginManager;
this.tooltipOverlay = tooltipOverlay;
this.infoBoxOverlay = infoBoxOverlay;
this.configManager = configManager;
keyManager.registerKeyListener(this);
mouseManager.registerMouseListener(this);
} }
@Subscribe @Subscribe
@@ -101,32 +137,101 @@ public class OverlayRenderer
return; return;
} }
if (event.getGameState().equals(GameState.LOGIN_SCREEN) || event.getGameState().equals(GameState.LOGGED_IN)) if (event.getGameState().equals(GameState.LOGGED_IN))
{ {
refreshPlugins(); rebuildOverlays();
updateSurface();
} }
} }
@Subscribe @Subscribe
public void onPluginChanged(PluginChanged event) public void onPluginChanged(PluginChanged event)
{ {
refreshPlugins(); rebuildOverlays();
} }
private void refreshPlugins() private List<Overlay> getOverlaysForLayer(OverlayLayer layer)
{ {
overlays.clear(); switch (layer)
overlays.addAll(Stream {
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( .concat(
pluginManager.getPlugins() pluginManager.getPlugins()
.stream() .stream()
.filter(plugin -> pluginManager.isPluginEnabled(plugin)) .filter(pluginManager::isPluginEnabled)
.flatMap(plugin -> plugin.getOverlays().stream()), .flatMap(plugin -> plugin.getOverlays().stream()),
Stream.of(infoBoxOverlay, tooltipOverlay)) Stream.of(infoBoxOverlay, tooltipOverlay))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList())); .collect(Collectors.toList());
sortOverlays(overlays); 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) 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(); 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; return;
} }
final Dimension size = client.getCanvas().getSize(); if (shouldInvalidateOverlays())
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)
{ {
surfaceGraphics.dispose(); snapCorners = buildSnapCorners();
} }
surfaceGraphics = subGraphics; // Create copy of snap corners because overlays will modify them
} OverlayBounds snapCorners = new OverlayBounds(this.snapCorners);
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);
OverlayUtil.setGraphicProperties(graphics); 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 // Draw snap corners
if (chatbox != null && isResizeable && chatbox.isHidden()) if (layer == OverlayLayer.UNDER_WIDGETS && movedOverlay != null)
{ {
rightChatboxPoint.y += chatboxBounds.height; final OverlayBounds translatedSnapCorners = translateSnapCorners(snapCorners);
bottomLeftPoint.y += chatboxBounds.height; 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) 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()) if (overlayPosition == OverlayPosition.ABOVE_CHATBOX_RIGHT && !client.isResized())
{ {
// On fixed mode, ABOVE_CHATBOX_RIGHT is in the same location as // On fixed mode, ABOVE_CHATBOX_RIGHT is in the same location as
@@ -241,25 +311,6 @@ public class OverlayRenderer
// drawing over each other. // drawing over each other.
overlayPosition = OverlayPosition.BOTTOM_RIGHT; 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) if (overlayPosition == OverlayPosition.DYNAMIC || overlayPosition == OverlayPosition.TOOLTIP)
{ {
@@ -267,48 +318,313 @@ public class OverlayRenderer
} }
else else
{ {
final Dimension dimension = MoreObjects.firstNonNull(safeRender(overlay, surfaceGraphics, subPosition), new Dimension()); final Point location = overlay.getBounds().getLocation();
if (dimension.width == 0 && dimension.height == 0) 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; continue;
} }
final BufferedImage clippedImage = surface.getSubimage(0, 0, dimension.width, dimension.height); if (inOverlayDraggingMode)
switch (overlayPosition)
{ {
case BOTTOM_LEFT: final Color previous = graphics.getColor();
bottomLeftPoint.x += dimension.width + (dimension.width == 0 ? 0 : PADDING); graphics.setColor(movedOverlay == overlay ? MOVING_OVERLAY_ACTIVE_COLOR : MOVING_OVERLAY_COLOR);
break; graphics.drawRect(location.x, location.y, dimension.width - 1, dimension.height - 1);
case BOTTOM_RIGHT: graphics.setColor(previous);
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: @Override
topRightPoint.y += dimension.height + (dimension.height == 0 ? 0 : PADDING); public MouseEvent mousePressed(MouseEvent mouseEvent)
break; {
case ABOVE_CHATBOX_RIGHT: if (!inOverlayDraggingMode)
rightChatboxPoint.y -= dimension.height + (dimension.height == 0 ? 0 : PADDING); {
break; 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;
} }
final Point transformed = OverlayUtil.transformPosition(overlayPosition, dimension); mouseEvent.consume();
graphics.drawImage(clippedImage, subPosition.x + transformed.x, subPosition.y + transformed.y, null); break;
surfaceGraphics.clearRect(0, 0, (int) dimension.getWidth(), (int) dimension.getHeight());
} }
} }
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) private Dimension safeRender(RenderableEntity entity, Graphics2D graphics, Point point)
{ {
final Graphics2D subGraphics = (Graphics2D) graphics.create(); final Graphics2D subGraphics = (Graphics2D) graphics.create();
subGraphics.translate(point.x, point.y);
final Dimension dimension = entity.render(subGraphics, point); final Dimension dimension = entity.render(subGraphics, point);
subGraphics.dispose(); subGraphics.dispose();
return dimension; 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) public void provideScreenshot(BufferedImage image)
{ {
Consumer<BufferedImage> consumer; Consumer<BufferedImage> consumer;

View File

@@ -137,6 +137,35 @@ public class OverlayUtil
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 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) public static java.awt.Point transformPosition(OverlayPosition position, Dimension dimension)
{ {
final java.awt.Point result = new java.awt.Point(); final java.awt.Point result = new java.awt.Point();
@@ -144,6 +173,7 @@ public class OverlayUtil
switch (position) switch (position)
{ {
case DYNAMIC: case DYNAMIC:
case TOOLTIP:
case TOP_LEFT: case TOP_LEFT:
break; break;
case BOTTOM_LEFT: case BOTTOM_LEFT:

View File

@@ -85,6 +85,18 @@ public abstract class StretchedFixedModeMixin implements RSClient
cachedStretchedDimensions = null; cachedStretchedDimensions = null;
} }
@Inject
@Override
public Dimension getRealDimensions()
{
if (isStretchedEnabled() && !isResized())
{
return Constants.GAME_FIXED_SIZE;
}
return getCanvas().getSize();
}
@Inject @Inject
@Override @Override
public Dimension getStretchedDimensions() public Dimension getStretchedDimensions()