diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerOverlay.java index 8635378738..e5f110c0c2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerOverlay.java @@ -48,6 +48,7 @@ public class ScreenMarkerOverlay extends Overlay setPosition(OverlayPosition.DETACHED); setLayer(OverlayLayer.ALWAYS_ON_TOP); setPriority(OverlayPriority.HIGH); + setResizable(true); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java index 41760fda8d..9820dc645e 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java @@ -139,6 +139,7 @@ public class ClientUI private NavigationButton sidebarNavigationButton; private JButton sidebarNavigationJButton; private Dimension lastClientSize; + private Cursor defaultCursor; @Inject private ClientUI( @@ -663,6 +664,24 @@ public class ClientUI giveClientFocus(); } + /** + * Returns current cursor set on game container + * @return awt cursor + */ + public Cursor getCurrentCursor() + { + return container.getCursor(); + } + + /** + * Returns current custom cursor or default system cursor if cursor is not set + * @return awt cursor + */ + public Cursor getDefaultCursor() + { + return defaultCursor != null ? defaultCursor : Cursor.getDefaultCursor(); + } + /** * Changes cursor for client window. Requires ${@link ClientUI#init()} to be called first. * FIXME: This is working properly only on Windows, Linux and Mac are displaying cursor incorrectly @@ -678,7 +697,17 @@ public class ClientUI final java.awt.Point hotspot = new java.awt.Point(0, 0); final Cursor cursorAwt = Toolkit.getDefaultToolkit().createCustomCursor(image, hotspot, name); - container.setCursor(cursorAwt); + defaultCursor = cursorAwt; + setCursor(cursorAwt); + } + + /** + * Changes cursor for client window. Requires ${@link ClientUI#init()} to be called first. + * @param cursor awt cursor + */ + public void setCursor(final Cursor cursor) + { + container.setCursor(cursor); } /** @@ -692,6 +721,7 @@ public class ClientUI return; } + defaultCursor = null; container.setCursor(Cursor.getDefaultCursor()); } 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 abd2fc3f48..57d5164a8d 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 @@ -49,6 +49,7 @@ public abstract class Overlay implements LayoutableRenderableEntity private OverlayPriority priority = OverlayPriority.NONE; private OverlayLayer layer = OverlayLayer.UNDER_WIDGETS; private final List menuEntries = new ArrayList<>(); + private boolean resizable; protected Overlay() { diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java index 2af17900b3..39e08b3964 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java @@ -263,7 +263,7 @@ public class OverlayManager saveOverlay(overlay); } - private synchronized void rebuildOverlayLayers() + synchronized void rebuildOverlayLayers() { for (OverlayLayer l : OverlayLayer.values()) { 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 34b7715491..21d6993871 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -28,6 +28,7 @@ import com.google.common.base.MoreObjects; import com.google.common.primitives.Ints; import java.awt.Color; import java.awt.Composite; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Paint; @@ -58,6 +59,7 @@ import net.runelite.client.input.KeyListener; import net.runelite.client.input.KeyManager; import net.runelite.client.input.MouseAdapter; import net.runelite.client.input.MouseManager; +import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.JagexColors; import net.runelite.client.util.ColorUtil; @@ -68,21 +70,29 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener private static final int BORDER = 5; private static final int BORDER_TOP = BORDER + 15; private static final int PADDING = 2; + private static final int MIN_OVERLAY_SIZE = 32; + private static final int OVERLAY_RESIZE_TOLERANCE = 5; 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 Color MOVING_OVERLAY_RESIZING_COLOR = new Color(255, 0, 255, 200); private final Client client; private final OverlayManager overlayManager; private final RuneLiteConfig runeLiteConfig; + private final ClientUI clientUI; // Overlay movement variables private final Point overlayOffset = new Point(); private final Point mousePosition = new Point(); - private Overlay movedOverlay; + private Overlay currentManagedOverlay; + private Rectangle currentManagedBounds; + private boolean inOverlayManagingMode; + private boolean inOverlayResizingMode; private boolean inOverlayDraggingMode; private boolean inMenuEntryMode; + private boolean startedMovingOverlay; private MenuEntry[] menuEntries; // Overlay state validation @@ -99,11 +109,13 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener final OverlayManager overlayManager, final RuneLiteConfig runeLiteConfig, final MouseManager mouseManager, - final KeyManager keyManager) + final KeyManager keyManager, + final ClientUI clientUI) { this.client = client; this.overlayManager = overlayManager; this.runeLiteConfig = runeLiteConfig; + this.clientUI = clientUI; keyManager.registerKeyListener(this); mouseManager.registerMouseListener(this); } @@ -113,7 +125,8 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener { if (!event.isFocused()) { - inOverlayDraggingMode = false; + inOverlayManagingMode = false; + resetOverlayManagementMode(); inMenuEntryMode = false; menuEntries = null; } @@ -182,7 +195,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener OverlayUtil.setGraphicProperties(graphics); // Draw snap corners - if (layer == OverlayLayer.UNDER_WIDGETS && movedOverlay != null && movedOverlay.getPosition() != OverlayPosition.DETACHED) + if (inOverlayDraggingMode && layer == OverlayLayer.UNDER_WIDGETS && currentManagedOverlay != null && currentManagedOverlay.getPosition() != OverlayPosition.DETACHED) { final OverlayBounds translatedSnapCorners = snapCorners.translated( -SNAP_CORNER_SIZE.width, @@ -274,12 +287,19 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener if (!bounds.isEmpty()) { - if (inOverlayDraggingMode) + if (inOverlayManagingMode) { - final Color previous = graphics.getColor(); - graphics.setColor(movedOverlay == overlay ? MOVING_OVERLAY_ACTIVE_COLOR : MOVING_OVERLAY_COLOR); + if (inOverlayResizingMode && currentManagedOverlay == overlay) + { + graphics.setColor(MOVING_OVERLAY_RESIZING_COLOR); + } + else + { + graphics.setColor(inOverlayDraggingMode && currentManagedOverlay == overlay ? MOVING_OVERLAY_ACTIVE_COLOR : MOVING_OVERLAY_COLOR); + } + graphics.draw(bounds); - graphics.setColor(previous); + graphics.setPaint(paint); } if (!client.isMenuOpen() && !client.getSpellSelected() && bounds.contains(mouse)) @@ -299,7 +319,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener @Override public MouseEvent mousePressed(MouseEvent mouseEvent) { - if (!inOverlayDraggingMode) + if (!inOverlayManagingMode) { return mouseEvent; } @@ -307,112 +327,248 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener final Point mousePoint = mouseEvent.getPoint(); mousePosition.setLocation(mousePoint); - synchronized (overlayManager) + if (currentManagedOverlay == null) { - for (Overlay overlay : overlayManager.getOverlays()) + return mouseEvent; + } + + if (SwingUtilities.isRightMouseButton(mouseEvent)) + { + overlayManager.resetOverlay(currentManagedOverlay); + } + else if (SwingUtilities.isLeftMouseButton(mouseEvent)) + { + final Point offset = new Point(mousePoint.x, mousePoint.y); + offset.translate(-currentManagedOverlay.getBounds().x, -currentManagedOverlay.getBounds().y); + overlayOffset.setLocation(offset); + + inOverlayResizingMode = currentManagedOverlay != null && currentManagedOverlay.isResizable() && clientUI.getCurrentCursor() != clientUI.getDefaultCursor(); + inOverlayDraggingMode = !inOverlayResizingMode; + startedMovingOverlay = true; + currentManagedBounds = new Rectangle(currentManagedOverlay.getBounds()); + } + else + { + return mouseEvent; + } + + mouseEvent.consume(); + return mouseEvent; + } + + @Override + public MouseEvent mouseMoved(MouseEvent mouseEvent) + { + if (!inOverlayManagingMode) + { + return mouseEvent; + } + + final Point mousePoint = mouseEvent.getPoint(); + mousePosition.setLocation(mousePoint); + + if (!inOverlayResizingMode && !inOverlayDraggingMode) + { + currentManagedOverlay = null; + + synchronized (overlayManager) { - final OverlayPosition overlayPosition = getCorrectedOverlayPosition(overlay); - - if (overlayPosition == OverlayPosition.DYNAMIC || overlayPosition == OverlayPosition.TOOLTIP) + for (Overlay overlay : overlayManager.getOverlays()) { - continue; - } - - if (overlay.getBounds().contains(mousePoint)) - { - if (SwingUtilities.isRightMouseButton(mouseEvent)) + final Rectangle bounds = overlay.getBounds(); + if (bounds.contains(mousePoint)) { - overlayManager.resetOverlay(overlay); + currentManagedOverlay = overlay; + break; } - else - { - final Point offset = new Point(mousePoint.x, mousePoint.y); - offset.translate(-overlay.getBounds().x, -overlay.getBounds().y); - overlayOffset.setLocation(offset); - - mousePoint.translate(-offset.x, -offset.y); - movedOverlay = overlay; - movedOverlay.setPreferredPosition(null); - movedOverlay.setPreferredLocation(mousePoint); - overlayManager.saveOverlay(movedOverlay); - } - - mouseEvent.consume(); - break; } } } + if (currentManagedOverlay == null || !currentManagedOverlay.isResizable()) + { + clientUI.setCursor(clientUI.getDefaultCursor()); + return mouseEvent; + } + + final Rectangle toleranceRect = new Rectangle(currentManagedOverlay.getBounds()); + toleranceRect.grow(-OVERLAY_RESIZE_TOLERANCE, -OVERLAY_RESIZE_TOLERANCE); + final int outcode = toleranceRect.outcode(mouseEvent.getPoint()); + + switch (outcode) + { + case Rectangle.OUT_TOP: + clientUI.setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)); + break; + case Rectangle.OUT_TOP | Rectangle.OUT_LEFT: + clientUI.setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR)); + break; + case Rectangle.OUT_LEFT: + clientUI.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)); + break; + case Rectangle.OUT_LEFT | Rectangle.OUT_BOTTOM: + clientUI.setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR)); + break; + case Rectangle.OUT_BOTTOM: + clientUI.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); + break; + case Rectangle.OUT_BOTTOM | Rectangle.OUT_RIGHT: + clientUI.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)); + break; + case Rectangle.OUT_RIGHT: + clientUI.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); + break; + case Rectangle.OUT_RIGHT | Rectangle.OUT_TOP: + clientUI.setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)); + break; + default: + // center + clientUI.setCursor(clientUI.getDefaultCursor()); + } + return mouseEvent; } @Override public MouseEvent mouseDragged(MouseEvent mouseEvent) { - if (!inOverlayDraggingMode) + if (!inOverlayManagingMode) + { + return mouseEvent; + } + + final Point p = mouseEvent.getPoint(); + mousePosition.setLocation(p); + + if (currentManagedOverlay == null) { return mouseEvent; } - final Point mousePoint = mouseEvent.getPoint(); - mousePosition.setLocation(mousePoint); final Rectangle canvasRect = new Rectangle(client.getRealDimensions()); - if (!canvasRect.contains(mousePoint)) + if (!canvasRect.contains(p)) { return mouseEvent; } - if (movedOverlay != null) + if (inOverlayResizingMode) + { + final Rectangle r = currentManagedBounds; + + final int dx = p.x - r.x; + final int dy = p.y - r.y; + switch (clientUI.getCurrentCursor().getType()) + { + case Cursor.N_RESIZE_CURSOR: + int height = r.height - dy; + r.setRect(r.x, r.y + dy, r.width, height); + break; + case Cursor.NW_RESIZE_CURSOR: + int width = r.width - dx; + height = r.height - dy; + r.setRect(r.x + dx, r.y + dy, width, height); + break; + case Cursor.W_RESIZE_CURSOR: + width = r.width - dx; + r.setRect(r.x + dx, r.y, width, r.height); + break; + case Cursor.SW_RESIZE_CURSOR: + width = r.width - dx; + height = dy; + r.setRect(r.x + dx, r.y, width, height); + break; + case Cursor.S_RESIZE_CURSOR: + height = dy; + r.setRect(r.x, r.y, r.width, height); + break; + case Cursor.SE_RESIZE_CURSOR: + width = dx; + height = dy; + r.setRect(r.x, r.y, width, height); + break; + case Cursor.E_RESIZE_CURSOR: + width = dx; + r.setRect(r.x, r.y, width, r.height); + break; + case Cursor.NE_RESIZE_CURSOR: + width = dx; + height = r.height - dy; + r.setRect(r.x, r.y + dy, width, height); + break; + default: + // center + } + + currentManagedOverlay.setPreferredSize(new Dimension(Math.max(MIN_OVERLAY_SIZE, currentManagedBounds.width), Math.max(MIN_OVERLAY_SIZE, currentManagedBounds.height))); + + if (currentManagedOverlay.getPreferredLocation() != null) + { + currentManagedOverlay.setPreferredLocation(currentManagedBounds.getLocation()); + } + } + else if (inOverlayDraggingMode) { final Dimension realDimension = client.getRealDimensions(); - mousePoint.translate(-overlayOffset.x, -overlayOffset.y); - mousePoint.x = Ints.constrainToRange(mousePoint.x, 0, Math.max(0, realDimension.width - movedOverlay.getBounds().width)); - mousePoint.y = Ints.constrainToRange(mousePoint.y, 0, Math.max(0, realDimension.height - movedOverlay.getBounds().height)); - movedOverlay.setPreferredPosition(null); - movedOverlay.setPreferredLocation(mousePoint); - mouseEvent.consume(); + p.translate(-overlayOffset.x, -overlayOffset.y); + p.x = Ints.constrainToRange(p.x, 0, Math.max(0, realDimension.width - currentManagedOverlay.getBounds().width)); + p.y = Ints.constrainToRange(p.y, 0, Math.max(0, realDimension.height - currentManagedOverlay.getBounds().height)); + currentManagedOverlay.setPreferredPosition(null); + currentManagedOverlay.setPreferredLocation(p); + } + else + { + return mouseEvent; } + if (startedMovingOverlay) + { + // Move currently moved overlay to correct layer + overlayManager.rebuildOverlayLayers(); + startedMovingOverlay = false; + } + + mouseEvent.consume(); return mouseEvent; } @Override public MouseEvent mouseReleased(MouseEvent mouseEvent) { - if (movedOverlay != null) + if (!inOverlayManagingMode || currentManagedOverlay == null || (!inOverlayDraggingMode && !inOverlayResizingMode)) { - mousePosition.setLocation(-1, -1); - - // do not snapcorner detached overlays - if (movedOverlay.getPosition() != OverlayPosition.DETACHED) - { - final OverlayBounds snapCorners = this.snapCorners.translated(-SNAP_CORNER_SIZE.width, -SNAP_CORNER_SIZE.height); - - 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; - } - } - } - - overlayManager.saveOverlay(movedOverlay); - movedOverlay = null; - mouseEvent.consume(); + return mouseEvent; } + mousePosition.setLocation(-1, -1); + + // do not snapcorner detached overlays + if (currentManagedOverlay.getPosition() != OverlayPosition.DETACHED && inOverlayDraggingMode) + { + final OverlayBounds snapCorners = this.snapCorners.translated(-SNAP_CORNER_SIZE.width, -SNAP_CORNER_SIZE.height); + + for (Rectangle snapCorner : snapCorners.getBounds()) + { + if (snapCorner.contains(mouseEvent.getPoint())) + { + OverlayPosition position = snapCorners.fromBounds(snapCorner); + + if (position == currentManagedOverlay.getPosition()) + { + // overlay moves back to default position + position = null; + } + + currentManagedOverlay.setPreferredPosition(position); + currentManagedOverlay.setPreferredLocation(null); // from dragging + break; + } + } + } + + overlayManager.saveOverlay(currentManagedOverlay); + resetOverlayManagementMode(); + mouseEvent.consume(); return mouseEvent; } @@ -426,7 +582,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener { if (e.isAltDown()) { - inOverlayDraggingMode = true; + inOverlayManagingMode = true; } if (e.isShiftDown() && runeLiteConfig.menuEntryShift()) @@ -440,7 +596,8 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener { if (!e.isAltDown()) { - inOverlayDraggingMode = false; + inOverlayManagingMode = false; + resetOverlayManagementMode(); } if (!e.isShiftDown()) @@ -526,6 +683,15 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener return overlayPosition; } + private void resetOverlayManagementMode() + { + inOverlayResizingMode = false; + inOverlayDraggingMode = false; + currentManagedOverlay = null; + currentManagedBounds = null; + clientUI.setCursor(clientUI.getDefaultCursor()); + } + private boolean shouldInvalidateBounds() { final Widget chatbox = client.getWidget(WidgetInfo.CHATBOX);