diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 29d7fc32c0..7418be6c5e 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -79,7 +79,6 @@ import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayRenderer; import net.runelite.client.ui.overlay.WidgetOverlay; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; -import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay; import net.runelite.client.ui.overlay.tooltip.TooltipOverlay; import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay; import net.runelite.client.ws.PartyService; @@ -134,7 +133,7 @@ public class RuneLite private ClientUI clientUI; @Inject - private InfoBoxManager infoBoxManager; + private Provider infoBoxManager; @Inject private OverlayManager overlayManager; @@ -160,9 +159,6 @@ public class RuneLite @Inject private Provider commandManager; - @Inject - private Provider infoBoxOverlay; - @Inject private Provider tooltipOverlay; @@ -367,7 +363,6 @@ public class RuneLite eventBus.register(externalPluginManager); eventBus.register(overlayManager); eventBus.register(drawManager); - eventBus.register(infoBoxManager); eventBus.register(configManager); eventBus.register(discordService); @@ -376,6 +371,7 @@ public class RuneLite // Initialize chat colors chatMessageManager.get().loadColors(); + eventBus.register(infoBoxManager.get()); eventBus.register(partyService.get()); eventBus.register(overlayRenderer.get()); eventBus.register(friendsChatManager.get()); @@ -386,11 +382,9 @@ public class RuneLite eventBus.register(lootManager.get()); eventBus.register(chatboxPanelManager.get()); eventBus.register(hooks.get()); - eventBus.register(infoBoxOverlay.get()); // Add core overlays WidgetOverlay.createOverlays(client).forEach(overlayManager::add); - overlayManager.add(infoBoxOverlay.get()); overlayManager.add(worldMapOverlay.get()); overlayManager.add(tooltipOverlay.get()); } diff --git a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java index f0f652aea8..4b6a960cbf 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java @@ -334,7 +334,8 @@ public interface RuneLiteConfig extends Config name = "Display infoboxes vertically", description = "Toggles the infoboxes to display vertically", position = 40, - section = overlaySettings + section = overlaySettings, + hidden = true ) default boolean infoBoxVertical() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostIndicator.java b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostIndicator.java index 802c05eac7..001e8ae5c8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostIndicator.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/boosts/BoostIndicator.java @@ -89,4 +89,10 @@ public class BoostIndicator extends InfoBox { return config.displayInfoboxes() && plugin.canShowBoosts() && plugin.getSkillsToDisplay().contains(getSkill()); } + + @Override + public String getName() + { + return "Boost " + skill.getName(); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimerTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimerTimer.java index 8a30de2ade..45728a4120 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimerTimer.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimerTimer.java @@ -45,4 +45,10 @@ class TimerTimer extends Timer { return timer; } + + @Override + public String getName() + { + return timer.name(); + } } 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 771278185d..a440108944 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 @@ -30,6 +30,7 @@ import java.awt.Rectangle; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import net.runelite.client.plugins.Plugin; @@ -52,6 +53,13 @@ public abstract class Overlay implements LayoutableRenderableEntity private boolean resizable; private boolean resettable = true; + /** + * Whether this overlay can be dragged onto other overlays & have + * other overlays dragged onto it. + */ + @Setter(AccessLevel.PROTECTED) + private boolean dragTargetable; + protected Overlay() { plugin = null; @@ -64,6 +72,7 @@ public abstract class Overlay implements LayoutableRenderableEntity /** * Overlay name, used for saving the overlay, needs to be unique + * * @return overlay name */ public String getName() @@ -74,4 +83,17 @@ public abstract class Overlay implements LayoutableRenderableEntity public void onMouseOver() { } + + /** + * Called when an overlay is dragged onto this, if dragTargetable is true. + * Return true to consume the mouse event and prevent the other + * overlay from being moved + * + * @param other the overlay being dragged + * @return + */ + public boolean onDrag(Overlay other) + { + return false; + } } 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 888c6431ae..7a4d7f08c1 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 @@ -77,6 +77,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener 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_TARGET_COLOR = Color.RED; private static final Color MOVING_OVERLAY_RESIZING_COLOR = new Color(255, 0, 255, 200); private final Client client; private final OverlayManager overlayManager; @@ -87,6 +88,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener private final Point overlayOffset = new Point(); private final Point mousePosition = new Point(); private Overlay currentManagedOverlay; + private Overlay dragTargetOverlay; private Rectangle currentManagedBounds; private boolean inOverlayManagingMode; private boolean inOverlayResizingMode; @@ -292,15 +294,28 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener { if (inOverlayManagingMode) { + Color boundsColor; if (inOverlayResizingMode && currentManagedOverlay == overlay) { - graphics.setColor(MOVING_OVERLAY_RESIZING_COLOR); + boundsColor = MOVING_OVERLAY_RESIZING_COLOR; + } + else if (inOverlayDraggingMode && currentManagedOverlay == overlay) + { + boundsColor = MOVING_OVERLAY_ACTIVE_COLOR; + } + else if (inOverlayDraggingMode && overlay.isDragTargetable() && currentManagedOverlay.isDragTargetable() + && currentManagedOverlay.getBounds().intersects(bounds)) + { + boundsColor = MOVING_OVERLAY_TARGET_COLOR; + assert currentManagedOverlay != overlay; + dragTargetOverlay = overlay; } else { - graphics.setColor(inOverlayDraggingMode && currentManagedOverlay == overlay ? MOVING_OVERLAY_ACTIVE_COLOR : MOVING_OVERLAY_COLOR); + boundsColor = MOVING_OVERLAY_COLOR; } + graphics.setColor(boundsColor); graphics.draw(bounds); graphics.setPaint(paint); } @@ -457,6 +472,12 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener return mouseEvent; } + if (dragTargetOverlay != null && !currentManagedOverlay.getBounds().intersects(dragTargetOverlay.getBounds())) + { + // No longer over drag target + dragTargetOverlay = null; + } + final Rectangle canvasRect = new Rectangle(client.getRealDimensions()); if (!canvasRect.contains(p)) @@ -584,7 +605,17 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener mousePosition.setLocation(-1, -1); - // do not snapcorner detached overlays + if (dragTargetOverlay != null) + { + if (dragTargetOverlay.onDrag(currentManagedOverlay)) + { + mouseEvent.consume(); + resetOverlayManagementMode(); + return mouseEvent; + } + } + + // Check if the overlay is over a snapcorner and move it if so, unless it is a detached overlay if (currentManagedOverlay.getPosition() != OverlayPosition.DETACHED && inOverlayDraggingMode) { final OverlayBounds snapCorners = this.snapCorners.translated(-SNAP_CORNER_SIZE.width, -SNAP_CORNER_SIZE.height); @@ -720,6 +751,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener inOverlayResizingMode = false; inOverlayDraggingMode = false; currentManagedOverlay = null; + dragTargetOverlay = null; currentManagedBounds = null; clientUI.setCursor(clientUI.getDefaultCursor()); } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java index 364979cf36..58459dff23 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java @@ -81,4 +81,11 @@ public abstract class InfoBox { return false; } + + public String getName() + { + // Use a combination of plugin name and infobox implementation name to try and make each infobox as unique + // as possible by default + return plugin.getClass().getSimpleName() + "_" + getClass().getSimpleName(); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java index efda12a992..66a6d7d913 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java @@ -25,33 +25,76 @@ package net.runelite.client.ui.overlay.infobox; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.ComparisonChain; import java.awt.Graphics; import java.awt.image.BufferedImage; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.MenuAction; +import net.runelite.client.config.ConfigManager; import net.runelite.client.config.RuneLiteConfig; +import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.InfoBoxMenuClicked; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.components.ComponentOrientation; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; import net.runelite.client.util.AsyncBufferedImage; @Singleton @Slf4j public class InfoBoxManager { - private final List infoBoxes = new CopyOnWriteArrayList<>(); + private static final String INFOBOXLAYER_KEY = "infoboxlayer"; + private static final String INFOBOXOVERLAY_KEY = "infoboxoverlay"; + private static final String INFOBOXOVERLAY_ORIENTATION_PREFIX = "orient_"; + private static final String DEFAULT_LAYER = "InfoBoxOverlay"; + + private static final String DETACH = "Detach"; + private static final String FLIP = "Flip"; + private static final String DELETE = "Delete"; + + private static final OverlayMenuEntry DETACH_ME = new OverlayMenuEntry(MenuAction.RUNELITE_INFOBOX, DETACH, "InfoBox"); + private static final OverlayMenuEntry FLIP_ME = new OverlayMenuEntry(MenuAction.RUNELITE_INFOBOX, FLIP, "InfoBox Group"); + private static final OverlayMenuEntry DELETE_ME = new OverlayMenuEntry(MenuAction.RUNELITE_INFOBOX, DELETE, "InfoBox Group"); + + private final Map layers = new ConcurrentHashMap<>(); + private final RuneLiteConfig runeLiteConfig; + private final TooltipManager tooltipManager; + private final Client client; + private final EventBus eventBus; + private final OverlayManager overlayManager; + private final ConfigManager configManager; @Inject - private InfoBoxManager(final RuneLiteConfig runeLiteConfig) + private InfoBoxManager( + final RuneLiteConfig runeLiteConfig, + final TooltipManager tooltipManager, + final Client client, + final EventBus eventBus, + final OverlayManager overlayManager, + final ConfigManager configManager) { this.runeLiteConfig = runeLiteConfig; + this.tooltipManager = tooltipManager; + this.client = client; + this.eventBus = eventBus; + this.overlayManager = overlayManager; + this.configManager = configManager; } @Subscribe @@ -59,7 +102,33 @@ public class InfoBoxManager { if (event.getGroup().equals("runelite") && event.getKey().equals("infoBoxSize")) { - infoBoxes.forEach(this::updateInfoBoxImage); + layers.values().forEach(l -> l.getInfoBoxes().forEach(this::updateInfoBoxImage)); + } + } + + @Subscribe + public void onInfoBoxMenuClicked(InfoBoxMenuClicked event) + { + if (DETACH.equals(event.getEntry().getOption())) + { + // The layer name doesn't matter as long as it is unique + splitInfobox(event.getInfoBox().getName() + "_" + System.currentTimeMillis(), event.getInfoBox()); + } + else if (FLIP.equals(event.getEntry().getOption())) + { + InfoBoxOverlay infoBoxOverlay = layers.get(getLayer(event.getInfoBox())); + ComponentOrientation newOrientation = infoBoxOverlay.flip(); + setOrientation(infoBoxOverlay.getName(), newOrientation); + } + else if (DELETE.equals(event.getEntry().getOption())) + { + // This is just a merge into the default layer + InfoBoxOverlay source = layers.get(getLayer(event.getInfoBox())); + InfoBoxOverlay dest = layers.computeIfAbsent(DEFAULT_LAYER, this::makeOverlay); + if (source != dest) + { + mergeInfoBoxes(source, dest); + } } } @@ -70,14 +139,25 @@ public class InfoBoxManager updateInfoBoxImage(infoBox); + String layerName = getLayer(infoBox); + InfoBoxOverlay overlay = layers.computeIfAbsent(layerName, this::makeOverlay); + List menuEntries = infoBox.getMenuEntries(); + menuEntries.add(DETACH_ME); + menuEntries.add(FLIP_ME); + if (!layerName.equals(DEFAULT_LAYER)) + { + // Non default-group infoboxes have a delete option to delete the group + menuEntries.add(DELETE_ME); + } + synchronized (this) { - int idx = findInsertionIndex(infoBoxes, infoBox, (b1, b2) -> ComparisonChain + int idx = findInsertionIndex(overlay.getInfoBoxes(), infoBox, (b1, b2) -> ComparisonChain .start() .compare(b1.getPriority(), b2.getPriority()) .compare(b1.getPlugin().getName(), b2.getPlugin().getName()) .result()); - infoBoxes.add(idx, infoBox); + overlay.getInfoBoxes().add(idx, infoBox); } BufferedImage image = infoBox.getImage(); @@ -91,28 +171,40 @@ public class InfoBoxManager public synchronized void removeInfoBox(InfoBox infoBox) { - if (infoBoxes.remove(infoBox)) + if (infoBox == null) + { + return; + } + + if (layers.get(getLayer(infoBox)).getInfoBoxes().remove(infoBox)) { log.debug("Removed InfoBox {}", infoBox); } + + infoBox.getMenuEntries().remove(DETACH_ME); + infoBox.getMenuEntries().remove(FLIP_ME); + infoBox.getMenuEntries().remove(DELETE_ME); } public synchronized void removeIf(Predicate filter) { - if (infoBoxes.removeIf(filter)) + for (InfoBoxOverlay overlay : layers.values()) { - log.debug("Removed InfoBoxes for filter {}", filter); + if (overlay.getInfoBoxes().removeIf(filter)) + { + log.debug("Removed InfoBoxes for filter {} from {}", filter, overlay); + } } } public List getInfoBoxes() { - return Collections.unmodifiableList(infoBoxes); + return layers.values().stream().map(InfoBoxOverlay::getInfoBoxes).flatMap(Collection::stream).collect(Collectors.toList()); } public synchronized void cull() { - infoBoxes.removeIf(InfoBox::cull); + layers.values().forEach(l -> l.getInfoBoxes().removeIf(InfoBox::cull)); } public void updateInfoBoxImage(final InfoBox infoBox) @@ -152,6 +244,140 @@ public class InfoBoxManager infoBox.setScaledImage(resultImage); } + private InfoBoxOverlay makeOverlay(String name) + { + ComponentOrientation orientation = getOrientation(name); + if (orientation == null) + { + if (name.equals(DEFAULT_LAYER)) + { + // Fall back to old orientation config option + orientation = runeLiteConfig.infoBoxVertical() ? ComponentOrientation.VERTICAL : ComponentOrientation.HORIZONTAL; + setOrientation(name, orientation); + } + else + { + // Default infobox orientation + orientation = ComponentOrientation.HORIZONTAL; + } + } + + InfoBoxOverlay infoBoxOverlay = new InfoBoxOverlay( + this, + tooltipManager, + client, + runeLiteConfig, + eventBus, + name, + orientation); + overlayManager.add(infoBoxOverlay); + eventBus.register(infoBoxOverlay); + return infoBoxOverlay; + } + + private void removeOverlay(InfoBoxOverlay overlay) + { + unsetOrientation(overlay.getName()); + eventBus.unregister(overlay); + overlayManager.remove(overlay); + layers.remove(overlay.getName()); + } + + private synchronized void splitInfobox(String newLayer, InfoBox infoBox) + { + String layer = getLayer(infoBox); + InfoBoxOverlay oldOverlay = layers.get(layer); + // Find all infoboxes with the same name, as they are all within the same group and so move at once. + Collection filtered = oldOverlay.getInfoBoxes().stream() + .filter(i -> i.getName().equals(infoBox.getName())).collect(Collectors.toList()); + + oldOverlay.getInfoBoxes().removeAll(filtered); + if (oldOverlay.getInfoBoxes().isEmpty()) + { + log.debug("Deleted layer: {}", oldOverlay.getName()); + removeOverlay(oldOverlay); + } + + InfoBoxOverlay newOverlay = layers.computeIfAbsent(newLayer, this::makeOverlay); + newOverlay.getInfoBoxes().addAll(filtered); + + // Adjust config for new infoboxes + for (InfoBox i : filtered) + { + setLayer(i, newLayer); + + if (!i.getMenuEntries().contains(DELETE_ME)) + { + i.getMenuEntries().add(DELETE_ME); + } + } + + log.debug("Moving infobox named {} (layer {}) to layer {}: {} boxes", infoBox.getName(), layer, newLayer, filtered.size()); + } + + public synchronized void mergeInfoBoxes(InfoBoxOverlay source, InfoBoxOverlay dest) + { + Collection infoBoxesToMove = source.getInfoBoxes(); + boolean isDefault = dest.getName().equals(DEFAULT_LAYER); + + log.debug("Merging InfoBoxes from {} into {} ({} boxes)", source.getName(), dest.getName(), infoBoxesToMove.size()); + + for (InfoBox infoBox : infoBoxesToMove) + { + setLayer(infoBox, dest.getName()); + + if (isDefault) + { + infoBox.getMenuEntries().remove(DELETE_ME); + } + } + + dest.getInfoBoxes().addAll(infoBoxesToMove); + source.getInfoBoxes().clear(); + + // remove source + removeOverlay(source); + log.debug("Deleted layer: {}", source.getName()); + } + + private String getLayer(InfoBox infoBox) + { + String name = configManager.getConfiguration(INFOBOXLAYER_KEY, infoBox.getName()); + if (Strings.isNullOrEmpty(name)) + { + return DEFAULT_LAYER; + } + + return name; + } + + private void setLayer(InfoBox infoBox, String layer) + { + if (layer.equals(DEFAULT_LAYER)) + { + configManager.unsetConfiguration(INFOBOXLAYER_KEY, infoBox.getName()); + } + else + { + configManager.setConfiguration(INFOBOXLAYER_KEY, infoBox.getName(), layer); + } + } + + ComponentOrientation getOrientation(String name) + { + return configManager.getConfiguration(INFOBOXOVERLAY_KEY, INFOBOXOVERLAY_ORIENTATION_PREFIX + name, ComponentOrientation.class); + } + + void setOrientation(String name, ComponentOrientation orientation) + { + configManager.setConfiguration(INFOBOXOVERLAY_KEY, INFOBOXOVERLAY_ORIENTATION_PREFIX + name, orientation); + } + + void unsetOrientation(String name) + { + configManager.unsetConfiguration(INFOBOXOVERLAY_KEY, INFOBOXOVERLAY_ORIENTATION_PREFIX + name); + } + /** * Find insertion point for the given key into the given sorted list. If key already exists in the list, * return the index after the last occurrence. diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java index 387eda76ab..f3abeb6627 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java @@ -33,8 +33,9 @@ import java.awt.Point; import java.awt.Rectangle; import java.util.Collections; import java.util.List; -import javax.inject.Inject; -import javax.inject.Singleton; +import java.util.concurrent.CopyOnWriteArrayList; +import lombok.Getter; +import lombok.NonNull; import net.runelite.api.Client; import net.runelite.api.MenuAction; import net.runelite.api.events.MenuOptionClicked; @@ -42,6 +43,7 @@ import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.InfoBoxMenuClicked; +import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -51,7 +53,6 @@ import net.runelite.client.ui.overlay.components.LayoutableRenderableEntity; import net.runelite.client.ui.overlay.tooltip.Tooltip; import net.runelite.client.ui.overlay.tooltip.TooltipManager; -@Singleton public class InfoBoxOverlay extends OverlayPanel { private static final int GAP = 1; @@ -62,24 +63,33 @@ public class InfoBoxOverlay extends OverlayPanel private final Client client; private final RuneLiteConfig config; private final EventBus eventBus; + private final String name; + private ComponentOrientation orientation; + + @Getter + private final List infoBoxes = new CopyOnWriteArrayList<>(); private InfoBoxComponent hoveredComponent; - @Inject - private InfoBoxOverlay( + InfoBoxOverlay( InfoBoxManager infoboxManager, TooltipManager tooltipManager, Client client, RuneLiteConfig config, - EventBus eventBus) + EventBus eventBus, + String name, + @NonNull ComponentOrientation orientation) { this.tooltipManager = tooltipManager; this.infoboxManager = infoboxManager; this.client = client; this.config = config; this.eventBus = eventBus; + this.name = name; + this.orientation = orientation; setPosition(OverlayPosition.TOP_LEFT); setClearChildren(false); + setDragTargetable(true); panelComponent.setWrap(true); panelComponent.setBackgroundColor(null); @@ -87,11 +97,15 @@ public class InfoBoxOverlay extends OverlayPanel panelComponent.setGap(new Point(GAP, GAP)); } + @Override + public String getName() + { + return this.name; + } + @Override public Dimension render(Graphics2D graphics) { - final List infoBoxes = infoboxManager.getInfoBoxes(); - final boolean menuOpen = client.isMenuOpen(); if (!menuOpen) { @@ -106,9 +120,7 @@ public class InfoBoxOverlay extends OverlayPanel // Set preferred size to the size of DEFAULT_WRAP_COUNT infoboxes, including the padding - which is applied // to the last infobox prior to wrapping too. panelComponent.setPreferredSize(new Dimension(DEFAULT_WRAP_COUNT * (config.infoBoxSize() + GAP), DEFAULT_WRAP_COUNT * (config.infoBoxSize() + GAP))); - panelComponent.setOrientation(config.infoBoxVertical() - ? ComponentOrientation.VERTICAL - : ComponentOrientation.HORIZONTAL); + panelComponent.setOrientation(orientation); for (InfoBox box : infoBoxes) { @@ -177,7 +189,7 @@ public class InfoBoxOverlay extends OverlayPanel @Subscribe public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) { - if (menuOptionClicked.getMenuAction() != MenuAction.RUNELITE_INFOBOX) + if (menuOptionClicked.getMenuAction() != MenuAction.RUNELITE_INFOBOX || hoveredComponent == null) { return; } @@ -192,4 +204,21 @@ public class InfoBoxOverlay extends OverlayPanel eventBus.post(new InfoBoxMenuClicked(overlayMenuEntry, infoBox)); } } + + @Override + public boolean onDrag(Overlay source) + { + if (!(source instanceof InfoBoxOverlay)) + { + return false; + } + + infoboxManager.mergeInfoBoxes((InfoBoxOverlay) source, this); + return true; + } + + ComponentOrientation flip() + { + return orientation = orientation == ComponentOrientation.HORIZONTAL ? ComponentOrientation.VERTICAL : ComponentOrientation.HORIZONTAL; + } } diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/itemcharges/ItemChargePluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemcharges/ItemChargePluginTest.java index 6534225980..44694dfce8 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/itemcharges/ItemChargePluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemcharges/ItemChargePluginTest.java @@ -38,6 +38,7 @@ import net.runelite.api.events.ChatMessage; import net.runelite.client.Notifier; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -91,6 +92,10 @@ public class ItemChargePluginTest @Bind private Notifier notifier; + @Mock + @Bind + private InfoBoxManager infoBoxManager; + @Mock @Bind private ItemChargeConfig config; diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/raids/RaidsPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/raids/RaidsPluginTest.java index d687d98e5b..ca13ae0c88 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/raids/RaidsPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/raids/RaidsPluginTest.java @@ -34,6 +34,7 @@ import net.runelite.client.Notifier; import net.runelite.client.config.ChatColorConfig; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.util.ImageCapture; import net.runelite.client.ws.PartyService; import net.runelite.http.api.chat.ChatClient; @@ -85,6 +86,10 @@ public class RaidsPluginTest @Inject RaidsPlugin raidsPlugin; + @Mock + @Bind + private InfoBoxManager infoBoxManager; + @Mock @Bind private PartyService partyService; diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java index 1aaaa3e23f..8b3a136ea8 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java @@ -45,6 +45,7 @@ import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.DrawManager; import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; @@ -105,6 +106,10 @@ public class ScreenshotPluginTest @Bind private OverlayManager overlayManager; + @Mock + @Bind + private InfoBoxManager infoBoxManager; + @Before public void before() { diff --git a/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java b/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java index 49c9c2c484..a54af2ab8a 100644 --- a/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java +++ b/runelite-client/src/test/java/net/runelite/client/ui/overlay/infobox/InfoBoxManagerTest.java @@ -32,6 +32,8 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.client.config.ConfigManager; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.plugins.Plugin; import static org.junit.Assert.assertEquals; @@ -53,6 +55,14 @@ public class InfoBoxManagerTest @Bind private RuneLiteConfig runeLiteConfig; + @Mock + @Bind + private ConfigManager configManager; + + @Mock + @Bind + private Client client; + @Before public void before() {