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 62a67f510b..f2b1f60a00 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -331,6 +331,8 @@ public interface Client extends GameEngine Dimension getStretchedDimensions(); + Dimension getRealDimensions(); + /** * Changes world. Works only on login screen * @param world world diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index 9b7062121d..f9ec3b978b 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -24,10 +24,12 @@ */ package net.runelite.client.config; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.eventbus.EventBus; import java.awt.Color; import java.awt.Dimension; +import java.awt.Point; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -244,6 +246,16 @@ public class ConfigManager return properties.getProperty(groupName + "." + key); } + public T getConfiguration(String groupName, String key, Class 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) { log.debug("Setting configuration value for {}.{} to {}", groupName, key, value); @@ -289,6 +301,11 @@ public class ConfigManager eventBus.post(configChanged); } + public void setConfiguration(String groupName, String key, Object value) + { + setConfiguration(groupName, key, objectToString(value)); + } + public void unsetConfiguration(String groupName, String key) { log.debug("Unsetting configuration value for {}.{}", groupName, key); @@ -420,6 +437,13 @@ public class ConfigManager int height = Integer.parseInt(splitStr[1]); 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()) { return Enum.valueOf((Class) type, str); @@ -442,6 +466,11 @@ public class ConfigManager Dimension d = (Dimension) object; return d.width + "x" + d.height; } + if (object instanceof Point) + { + Point p = (Point) object; + return p.x + ":" + p.y; + } return object.toString(); } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java index cfc25313b9..4b6c7b9eca 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java @@ -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; diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayBounds.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayBounds.java new file mode 100644 index 0000000000..e81b1807fd --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayBounds.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018, 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.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 getBounds() + { + return Arrays.asList(topLeft, topRight, bottomLeft, bottomRight, aboveChatboxRight); + } +} 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 c4c55dfdb1..0923d2dbab 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 @@ -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 clientProvider; + private final InfoBoxOverlay infoBoxOverlay; + private final ConfigManager configManager; + private final TooltipOverlay tooltipOverlay; + private final List allOverlays = new CopyOnWriteArrayList<>(); + private final List overlaysAboveScene = new CopyOnWriteArrayList<>(), + overlaysUnderWidgets = new CopyOnWriteArrayList<>(), + overlaysAboveWidgets = new CopyOnWriteArrayList<>(), + overlaysOnTop = new CopyOnWriteArrayList<>(); + private final ConcurrentLinkedQueue> 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 clientProvider; - - @Inject - InfoBoxOverlay infoBoxOverlay; - - @Inject - TooltipOverlay tooltipOverlay; - - private final List overlays = new CopyOnWriteArrayList<>(); - private BufferedImage surface; - private Graphics2D surfaceGraphics; - - private ConcurrentLinkedQueue> screenshotRequests = new ConcurrentLinkedQueue<>(); - - @Subscribe - public void onResizableChanged(ResizeableChanged event) + private OverlayRenderer( + final Provider 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 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 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 overlayLayer = getOverlaysForLayer(layer); + overlayLayer.add(overlay); + } } static void sortOverlays(List 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 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); + } + } + } + } + + @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; } - 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()); + 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 consumer; diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java index 199b4c54d2..356a6aeed6 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java @@ -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: diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/StretchedFixedModeMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/StretchedFixedModeMixin.java index 3550defaa6..9729ef676d 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/StretchedFixedModeMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/StretchedFixedModeMixin.java @@ -85,6 +85,18 @@ public abstract class StretchedFixedModeMixin implements RSClient cachedStretchedDimensions = null; } + @Inject + @Override + public Dimension getRealDimensions() + { + if (isStretchedEnabled() && !isResized()) + { + return Constants.GAME_FIXED_SIZE; + } + + return getCanvas().getSize(); + } + @Inject @Override public Dimension getStretchedDimensions()