api: make MenuEntry an interface

This adds a new createMenuEntry api method to make MenuEntries instead.
Menu entries now have an associated callback called when they are
clicked on, avoiding most plugins from having to hook separately to
detect the menu click. Additionally get/set type has changed to take a
MenuAction.
This commit is contained in:
Adam
2021-12-04 15:28:27 -05:00
parent 479de04b73
commit 409d0dda76
38 changed files with 665 additions and 966 deletions

View File

@@ -24,9 +24,12 @@
*/
package net.runelite.client.menus;
import com.google.common.collect.Lists;
import com.google.inject.Guice;
import com.google.inject.testing.fieldbinder.Bind;
import com.google.inject.testing.fieldbinder.BoundFieldModule;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.MenuAction;
@@ -38,24 +41,18 @@ import static net.runelite.api.widgets.WidgetInfo.MINIMAP_WORLDMAP_OPTIONS;
import net.runelite.client.util.Text;
import static org.junit.Assert.assertArrayEquals;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import static org.mockito.ArgumentMatchers.anyInt;
import org.mockito.Mock;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
@RunWith(MockitoJUnitRunner.class)
public class MenuManagerTest
{
private static final MenuEntry CANCEL = new MenuEntry();
@Inject
private MenuManager menuManager;
@@ -63,14 +60,18 @@ public class MenuManagerTest
@Bind
private Client client;
private MenuEntry[] clientMenuEntries = {CANCEL};
private final MenuEntry CANCEL = createMenuEntry("Cancel", "", MenuAction.CANCEL, MINIMAP_WORLDMAP_OPTIONS.getPackedId());
@BeforeClass
public static void beforeClass()
private final List<MenuEntry> createdMenuEntries = new ArrayList<>();
private static MenuEntry createMenuEntry(String option, String target, MenuAction type, int param1)
{
CANCEL.setOption("Cancel");
CANCEL.setType(MenuAction.CANCEL.getId());
CANCEL.setParam1(MINIMAP_WORLDMAP_OPTIONS.getPackedId());
MenuEntry menuEntry = new TestMenuEntry();
menuEntry.setOption(option);
menuEntry.setTarget(target);
menuEntry.setType(type);
menuEntry.setParam1(param1);
return menuEntry;
}
@Before
@@ -78,35 +79,25 @@ public class MenuManagerTest
{
Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this);
doAnswer((Answer<Void>) invocationOnMock ->
{
clientMenuEntries = invocationOnMock.getArgument(0, MenuEntry[].class);
return null;
}).when(client).setMenuEntries(ArgumentMatchers.any(MenuEntry[].class));
when(client.getMenuEntries()).thenAnswer((Answer<MenuEntry[]>) invocationMock -> clientMenuEntries);
when(client.createMenuEntry(anyInt()))
.thenAnswer(a ->
{
MenuEntry e = new TestMenuEntry();
createdMenuEntries.add(e);
return e;
});
when(client.getMenuEntries()).thenReturn(new MenuEntry[]{CANCEL});
}
@Test
public void testManagedMenuOrder()
{
final MenuEntry first = new MenuEntry();
final MenuEntry second = new MenuEntry();
final MenuEntry third = new MenuEntry();
first.setOption("Test");
first.setTarget("First Entry");
first.setParam1(MINIMAP_WORLDMAP_OPTIONS.getPackedId());
first.setType(RUNELITE.getId());
second.setOption("Test");
second.setTarget("Second Entry");
second.setParam1(MINIMAP_WORLDMAP_OPTIONS.getPackedId());
second.setType(RUNELITE.getId());
third.setOption("Test");
third.setTarget("Third Entry");
third.setParam1(MINIMAP_WORLDMAP_OPTIONS.getPackedId());
third.setType(RUNELITE.getId());
menuManager.addManagedCustomMenu(new WidgetMenuOption(first.getOption(), first.getTarget(), MINIMAP_WORLDMAP_OPTIONS));
menuManager.addManagedCustomMenu(new WidgetMenuOption(second.getOption(), second.getTarget(), MINIMAP_WORLDMAP_OPTIONS));
menuManager.addManagedCustomMenu(new WidgetMenuOption(third.getOption(), third.getTarget(), MINIMAP_WORLDMAP_OPTIONS));
final MenuEntry first = createMenuEntry("Test", "First Entry", RUNELITE, MINIMAP_WORLDMAP_OPTIONS.getPackedId());
final MenuEntry second = createMenuEntry("Test", "Second Entry", RUNELITE, MINIMAP_WORLDMAP_OPTIONS.getPackedId());
final MenuEntry third = createMenuEntry("Test", "Third Entry", RUNELITE, MINIMAP_WORLDMAP_OPTIONS.getPackedId());
menuManager.addManagedCustomMenu(new WidgetMenuOption(first.getOption(), first.getTarget(), MINIMAP_WORLDMAP_OPTIONS), null);
menuManager.addManagedCustomMenu(new WidgetMenuOption(second.getOption(), second.getTarget(), MINIMAP_WORLDMAP_OPTIONS), null);
menuManager.addManagedCustomMenu(new WidgetMenuOption(third.getOption(), third.getTarget(), MINIMAP_WORLDMAP_OPTIONS), null);
menuManager.onMenuEntryAdded(new MenuEntryAdded(
CANCEL.getOption(),
@@ -116,12 +107,10 @@ public class MenuManagerTest
CANCEL.getParam0(),
CANCEL.getParam1()));
ArgumentCaptor<MenuEntry[]> captor = ArgumentCaptor.forClass(MenuEntry[].class);
verify(client, atLeastOnce()).setMenuEntries(captor.capture());
verify(client, times(3)).createMenuEntry(anyInt());
final MenuEntry[] resultMenuEntries = captor.getValue();
// Strip color tags from menu options before array comparison
for (MenuEntry resultEntry : resultMenuEntries)
for (MenuEntry resultEntry : createdMenuEntries)
{
final String resultTarget = resultEntry.getTarget();
if (resultTarget != null)
@@ -130,6 +119,7 @@ public class MenuManagerTest
}
}
assertArrayEquals(new MenuEntry[]{CANCEL, third, second, first}, resultMenuEntries);
assertArrayEquals(new MenuEntry[]{third, second, first},
Lists.reverse(createdMenuEntries).toArray(new MenuEntry[0]));
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (c) 2021, 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.menus;
import java.util.function.Consumer;
import lombok.EqualsAndHashCode;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
@EqualsAndHashCode
public class TestMenuEntry implements MenuEntry
{
private String option;
private String target;
private int identifier;
private int type;
private int param0;
private int param1;
private boolean forceLeftClick;
@Override
public String getOption()
{
return option;
}
@Override
public MenuEntry setOption(String option)
{
this.option = option;
return this;
}
@Override
public String getTarget()
{
return target;
}
@Override
public MenuEntry setTarget(String target)
{
this.target = target;
return this;
}
@Override
public int getIdentifier()
{
return this.identifier;
}
@Override
public MenuEntry setIdentifier(int identifier)
{
this.identifier = identifier;
return this;
}
@Override
public MenuAction getType()
{
return MenuAction.of(this.type);
}
@Override
public MenuEntry setType(MenuAction type)
{
this.type = type.getId();
return this;
}
@Override
public int getParam0()
{
return this.param0;
}
@Override
public MenuEntry setParam0(int param0)
{
this.param0 = param0;
return this;
}
@Override
public int getParam1()
{
return this.param1;
}
@Override
public MenuEntry setParam1(int param1)
{
this.param1 = param1;
return this;
}
@Override
public boolean isForceLeftClick()
{
return this.forceLeftClick;
}
@Override
public MenuEntry setForceLeftClick(boolean forceLeftClick)
{
this.forceLeftClick = forceLeftClick;
return this;
}
@Override
public boolean isDeprioritized()
{
return type >= MenuAction.MENU_ACTION_DEPRIORITIZE_OFFSET;
}
@Override
public void setDeprioritized(boolean deprioritized)
{
if (deprioritized)
{
if (type < MenuAction.MENU_ACTION_DEPRIORITIZE_OFFSET)
{
type += MenuAction.MENU_ACTION_DEPRIORITIZE_OFFSET;
}
}
else
{
if (type >= MenuAction.MENU_ACTION_DEPRIORITIZE_OFFSET)
{
type -= MenuAction.MENU_ACTION_DEPRIORITIZE_OFFSET;
}
}
}
@Override
public MenuEntry onClick(Consumer<MenuEntry> callback)
{
return this;
}
}

View File

@@ -28,6 +28,7 @@ import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.testing.fieldbinder.Bind;
import com.google.inject.testing.fieldbinder.BoundFieldModule;
import java.util.Arrays;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.KeyCode;
@@ -37,6 +38,7 @@ import net.runelite.api.events.ClientTick;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager;
import net.runelite.client.menus.TestMenuEntry;
import static org.junit.Assert.assertArrayEquals;
import org.junit.Before;
import org.junit.Test;
@@ -88,9 +90,7 @@ public class MenuEntrySwapperPluginTest
{
// The menu implementation returns a copy of the array, which causes swap() to not
// modify the same array being iterated in onClientTick
MenuEntry[] copy = new MenuEntry[entries.length];
System.arraycopy(entries, 0, copy, 0, entries.length);
return copy;
return Arrays.copyOf(entries, entries.length);
});
doAnswer((Answer<Void>) invocationOnMock ->
{
@@ -109,10 +109,10 @@ public class MenuEntrySwapperPluginTest
private static MenuEntry menu(String option, String target, MenuAction menuAction, int identifier)
{
MenuEntry menuEntry = new MenuEntry();
MenuEntry menuEntry = new TestMenuEntry();
menuEntry.setOption(option);
menuEntry.setTarget(target);
menuEntry.setType(menuAction.getId());
menuEntry.setType(menuAction);
menuEntry.setIdentifier(identifier);
return menuEntry;
}

View File

@@ -39,6 +39,7 @@ import net.runelite.api.NPC;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.NpcChanged;
import net.runelite.api.events.NpcSpawned;
import net.runelite.client.menus.TestMenuEntry;
import net.runelite.client.ui.overlay.OverlayManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -48,7 +49,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.MockitoJUnitRunner;
@@ -109,13 +109,12 @@ public class NpcIndicatorsPluginTest
when(client.getCachedNPCs()).thenReturn(new NPC[]{npc}); // id 0
when(client.getMenuEntries()).thenReturn(new MenuEntry[]{new MenuEntry()});
MenuEntry entry = new TestMenuEntry();
when(client.getMenuEntries()).thenReturn(new MenuEntry[]{entry});
MenuEntryAdded menuEntryAdded = new MenuEntryAdded("", "Goblin", MenuAction.NPC_FIRST_OPTION.getId(), 0, -1, -1);
npcIndicatorsPlugin.onMenuEntryAdded(menuEntryAdded);
MenuEntry target = new MenuEntry();
target.setTarget("<col=ff0000>Goblin"); // red
verify(client).setMenuEntries(new MenuEntry[]{target});
assertEquals("<col=ff0000>Goblin", entry.getTarget()); // red
}
@Test
@@ -133,13 +132,12 @@ public class NpcIndicatorsPluginTest
when(client.getCachedNPCs()).thenReturn(new NPC[]{npc}); // id 0
when(client.getMenuEntries()).thenReturn(new MenuEntry[]{new MenuEntry()});
MenuEntry entry = new TestMenuEntry();
when(client.getMenuEntries()).thenReturn(new MenuEntry[]{entry});
MenuEntryAdded menuEntryAdded = new MenuEntryAdded("", "Goblin", MenuAction.NPC_FIRST_OPTION.getId(), 0, -1, -1);
npcIndicatorsPlugin.onMenuEntryAdded(menuEntryAdded);
MenuEntry target = new MenuEntry();
target.setTarget("<col=0000ff>Goblin"); // blue
verify(client).setMenuEntries(new MenuEntry[]{target});
assertEquals("<col=0000ff>Goblin", entry.getTarget()); // blue
}
@Test