Skip to content

Commit

Permalink
More work on guis
Browse files Browse the repository at this point in the history
  • Loading branch information
fullwall committed Jan 24, 2021
1 parent e415663 commit 933e7b0
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 57 deletions.
186 changes: 135 additions & 51 deletions src/main/java/net/citizensnpcs/api/gui/InventoryMenu.java
@@ -1,30 +1,86 @@
package net.citizensnpcs.api.gui;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.WeakHashMap;

import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;

import com.google.common.collect.Lists;
import com.google.common.collect.Queues;

public class InventoryMenu {
private final MenuContext currentContext;
private InventoryMenuPage page;
private final InventoryMenuPattern[] patterns;
private final InventoryMenuTransition[] transitions;
public class InventoryMenu implements Listener {
private PageContext page;
private final Queue<PageContext> stack = Queues.newArrayDeque();
private final Collection<InventoryView> views = Lists.newArrayList();

public InventoryMenu(InventoryMenuInfo info) {
transition(info);
}

@EventHandler(ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
if (!event.getInventory().equals(page.ctx.getInventory()) || event.getAction() == InventoryAction.NOTHING)
return;
InventoryMenuSlot slot = page.ctx.getSlot(event.getSlot());
page.page.onClick(slot, event);
slot.onClick(event);
for (InventoryMenuTransition transition : page.transitions) {
Class<? extends InventoryMenuPage> next = transition.accept(slot);
if (next != null) {
transition(next);
break;
}
}
}

@EventHandler(ignoreCancelled = true)
public void onInventoryClose(InventoryCloseEvent event) {
if (!event.getInventory().equals(page.ctx.getInventory()))
return;
page.page.onClose(event.getPlayer());
page = stack.poll();
}

private int posToIndex(int[] dim, int[] pos) {
return pos[0] * dim[1] + pos[1];
}

public void present(Player player) {
InventoryView view = player.openInventory(page.ctx.getInventory());
views.add(view);
}

public void transition(Class<? extends InventoryMenuPage> clazz) {
if (!CACHED_INFOS.containsKey(clazz)) {
cacheInfo(clazz);
}
transition(CACHED_INFOS.get(clazz));
}

private void transition(InventoryMenuInfo info) {
if (page != null) {
stack.add(page);
}
page = new PageContext();
int[] dim = info.menuAnnotation.dimensions();
int size = dim[0] * dim[1];
Inventory inventory;
Expand All @@ -34,73 +90,51 @@ public InventoryMenu(InventoryMenuInfo info) {
inventory = Bukkit.createInventory(null, info.menuAnnotation.type(), info.menuAnnotation.title());
}
InventoryMenuSlot[] slots = new InventoryMenuSlot[inventory.getSize()];
this.patterns = new InventoryMenuPattern[info.patterns.length];
this.transitions = new InventoryMenuTransition[info.transitions.length];
page.patterns = new InventoryMenuPattern[info.patterns.length];
page.transitions = new InventoryMenuTransition[info.transitions.length];
try {
this.page = info.constructor.newInstance();
page.page = info.constructor.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
currentContext = new MenuContext(this, slots, inventory);
page.ctx = new MenuContext(this, slots, inventory);
for (int i = 0; i < info.slots.length; i++) {
Bindable<MenuSlot> slotInfo = info.slots[i];
int pos = posToIndex(dim, slotInfo.data.value());
InventoryMenuSlot slot = currentContext.getSlot(pos);
InventoryMenuSlot slot = page.ctx.getSlot(pos);
slot.initialise(slotInfo.data);
slotInfo.bind(slot);
}
for (int i = 0; i < info.patterns.length; i++) {
Bindable<MenuPattern> patternInfo = info.patterns[i];
InventoryMenuPattern pat = new InventoryMenuPattern(currentContext, patternInfo.data);
InventoryMenuPattern pat = new InventoryMenuPattern(page.ctx, patternInfo.data);
patternInfo.bind(pat);
this.patterns[i] = pat;
page.patterns[i] = pat;
}
for (int i = 0; i < info.transitions.length; i++) {
Bindable<MenuTransition> transitionInfo = info.transitions[i];
int pos = posToIndex(dim, transitionInfo.data.pos());
InventoryMenuSlot slot = currentContext.getSlot(pos);
InventoryMenuSlot slot = page.ctx.getSlot(pos);
InventoryMenuTransition transition = new InventoryMenuTransition(this, slot, transitionInfo.data.value());
transitionInfo.bind(transition);
this.transitions[i] = transition;
page.transitions[i] = transition;
}
}

@EventHandler(ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
if (!event.getInventory().equals(currentContext.getInventory()))
return;
InventoryMenuSlot slot = currentContext.getSlot(event.getSlot());
page.onClick(slot, event);
slot.onClick(event);
// TODO: check for transitions
}

@EventHandler(ignoreCancelled = true)
public void onInventoryClose(InventoryCloseEvent event) {
if (!event.getInventory().equals(currentContext.getInventory()))
return;
page.onClose(event.getPlayer());
// TODO: transition upwards if required
}

private int posToIndex(int[] dim, int[] pos) {
return pos[0] * dim[1] + pos[1];
}

public void present(Player player) {
InventoryView view = player.openInventory(currentContext.getInventory());
views.add(view);
}

private static class Bindable<T> {
MethodHandle bind;
T data;

public void bind(Object slot) {
public Bindable(MethodHandle bind, T data) {
this.bind = bind;
this.data = data;
}

public void bind(Object instance) {
if (bind == null)
return;
try {
bind.invoke(slot);
bind.invoke(instance);
} catch (Throwable e) {
e.printStackTrace();
}
Expand All @@ -113,18 +147,60 @@ private static class InventoryMenuInfo {
Bindable<MenuPattern>[] patterns;
Bindable<MenuSlot>[] slots;
Bindable<MenuTransition>[] transitions;
}

public static InventoryMenu create(Class<? extends InventoryMenuPage> clazz) {
if (!CACHED_INFOS.containsKey(clazz)) {
create0(clazz);
public InventoryMenuInfo(Class<?> clazz) {
patterns = getBindables(clazz, MenuPattern.class, InventoryMenuPattern.class);
slots = getBindables(clazz, MenuSlot.class, InventoryMenuSlot.class);
transitions = getBindables(clazz, MenuTransition.class, InventoryMenuTransition.class);
}
InventoryMenuInfo info = CACHED_INFOS.get(clazz);
return new InventoryMenu(info);

@SuppressWarnings({ "unchecked" })
private <T extends Annotation> Bindable<T>[] getBindables(Class<?> clazz, Class<T> annotationType,
Class<?> concreteType) {
List<Bindable<T>> bindables = Lists.newArrayList();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
T[] annotations = field.getAnnotationsByType(annotationType);
MethodHandle bind = null;
if (field.getType() == concreteType) {
try {
bind = LOOKUP.unreflectSetter(field);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
for (T t : annotations) {
bindables.add(new Bindable<T>(bind, t));
}
}

List<AccessibleObject> reflect = Lists.newArrayList();
reflect.addAll(Arrays.asList(clazz.getDeclaredConstructors()));
reflect.addAll(Arrays.asList(clazz.getDeclaredMethods()));
for (AccessibleObject object : reflect) {
object.setAccessible(true);
for (T t : object.getAnnotationsByType(annotationType)) {
bindables.add(new Bindable<T>(null, t));
}
}
for (T t : clazz.getAnnotationsByType(annotationType)) {
bindables.add(new Bindable<T>(null, t));
}
return bindables.toArray(new Bindable[bindables.size()]);
}

private static MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
}

private static void create0(Class<? extends InventoryMenuPage> clazz) {
InventoryMenuInfo info = new InventoryMenuInfo();
private static class PageContext {
private MenuContext ctx;
private InventoryMenuPage page;
private InventoryMenuPattern[] patterns;
private InventoryMenuTransition[] transitions;
}

private static void cacheInfo(Class<? extends InventoryMenuPage> clazz) {
InventoryMenuInfo info = new InventoryMenuInfo(clazz);
info.menuAnnotation = clazz.getAnnotation(Menu.class);
if (info.menuAnnotation == null) {
throw new IllegalArgumentException("Missing menu annotation");
Expand All @@ -140,5 +216,13 @@ private static void create0(Class<? extends InventoryMenuPage> clazz) {
CACHED_INFOS.put(clazz, info);
}

public static InventoryMenu create(Class<? extends InventoryMenuPage> clazz) {
if (!CACHED_INFOS.containsKey(clazz)) {
cacheInfo(clazz);
}
InventoryMenuInfo info = CACHED_INFOS.get(clazz);
return new InventoryMenu(info);
}

private static Map<Class<? extends InventoryMenuPage>, InventoryMenuInfo> CACHED_INFOS = new WeakHashMap<Class<? extends InventoryMenuPage>, InventoryMenuInfo>();
}
6 changes: 4 additions & 2 deletions src/main/java/net/citizensnpcs/api/gui/InventoryMenuPage.java
Expand Up @@ -6,7 +6,9 @@
public abstract class InventoryMenuPage {
public abstract void create(MenuContext ctx);

public abstract void onClick(InventoryMenuSlot slot, InventoryClickEvent event);
public void onClick(InventoryMenuSlot slot, InventoryClickEvent event) {
}

public abstract void onClose(HumanEntity player);
public void onClose(HumanEntity player) {
}
}
Expand Up @@ -62,6 +62,6 @@ public void onClick(InventoryClickEvent event) {
}

public void setClickFilter(Collection<ClickType> filter) {
this.clickFilter = EnumSet.copyOf(filter);
this.clickFilter = filter == null || filter.isEmpty() ? EnumSet.allOf(ClickType.class) : EnumSet.copyOf(filter);
}
}
Expand Up @@ -3,11 +3,16 @@
public class InventoryMenuTransition {
private final InventoryMenu menu;
private final InventoryMenuSlot slot;
private final Class<?> transition;
private final Class<? extends InventoryMenuPage> transition;

public InventoryMenuTransition(InventoryMenu menu, InventoryMenuSlot slot, Class<?> transition) {
public InventoryMenuTransition(InventoryMenu menu, InventoryMenuSlot slot,
Class<? extends InventoryMenuPage> transition) {
this.menu = menu;
this.slot = slot;
this.transition = transition;
}

public Class<? extends InventoryMenuPage> accept(InventoryMenuSlot accept) {
return accept.equals(slot) ? transition : null;
}
}
3 changes: 3 additions & 0 deletions src/main/java/net/citizensnpcs/api/gui/MenuContext.java
Expand Up @@ -41,6 +41,9 @@ public InventoryMenu getParent() {

@Override
public InventoryMenuSlot getSlot(int i) {
if (slots[i] == null) {
return slots[i] = new InventoryMenuSlot(this, i);
}
return slots[i];
}
}
2 changes: 1 addition & 1 deletion src/main/java/net/citizensnpcs/api/gui/MenuTransition.java
Expand Up @@ -27,5 +27,5 @@
/**
* The next sub-menu class to transition to.
*/
Class<?> value();
Class<? extends InventoryMenuPage> value();
}

0 comments on commit 933e7b0

Please sign in to comment.