package gminers.glasspane.ease; import java.awt.Color; import java.io.Closeable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import lombok.Getter; import lombok.NonNull; import lombok.Setter; import lombok.val; import com.gameminers.glasspane.internal.GlassPaneMod; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; /** * Implements a class that can ease values in any object.<br/> * <br/> * "But Aesen, why not use Trident?"<br/> * Trident is too damn complicated, so I implemented my own, super easy to use, reflection-based easer built for use in Forge.<br/> * <br/> * Implements Closeable because it adds itself to an external map for ticking. * * @author Aesen Vismea * */ // We use raw types because generics are fake anyway // Yes, this code is a big hack. Does it work? Yes. @SuppressWarnings("rawtypes") public class PaneEaser implements Closeable { protected final Object toEase; protected Map<String, Byte> byteTargets = Maps.newHashMap(); protected Map<String, Short> shortTargets = Maps.newHashMap(); protected Map<String, Integer> integerTargets = Maps.newHashMap(); protected Map<String, Long> longTargets = Maps.newHashMap(); protected Map<String, Integer> colorTargets = Maps.newHashMap(); protected Map<String, Float> floatTargets = Maps.newHashMap(); protected Map<String, Double> doubleTargets = Maps.newHashMap(); protected Map<String, FieldAccessor> accessors = Maps.newHashMap(); @Getter @Setter protected double speed = 4D; @Getter @Setter protected boolean autoClose; protected boolean closed = false; protected List<Runnable> closeListeners = Lists.newArrayList(); public PaneEaser(@NonNull Object toEase) { this.toEase = toEase; GlassPaneMod.easers.put(toEase, this); } @SuppressWarnings("unchecked") private <T extends Number> void ease(Iterator<Entry<String, T>> iter, Class<?> primitive) { while (iter.hasNext()) { Entry<String, T> en = iter.next(); FieldAccessor<T> accessor = getAccessor(en.getKey(), primitive); T current = accessor.get(); T val = (T) numerfy(adjust(current.doubleValue(), en.getValue().doubleValue()), current.getClass()); if (current.equals(val)) { iter.remove(); } accessor.set(val); } } private Number numerfy(double val, Class<? extends Number> clazz) { if (clazz == Byte.class) return Byte.valueOf((byte) val); else if (clazz == Short.class) return Short.valueOf((short) val); else if (clazz == Integer.class) return Integer.valueOf((int) val); else if (clazz == Long.class) return Long.valueOf((long) val); else if (clazz == Float.class) return Float.valueOf((float) val); else if (clazz == Double.class) return Double.valueOf(val); return null; } @SuppressWarnings("unchecked") public void onTick(Phase phase) { if (closed) return; if (phase == Phase.START) { ease(byteTargets.entrySet().iterator(), byte.class); ease(shortTargets.entrySet().iterator(), short.class); ease(integerTargets.entrySet().iterator(), int.class); ease(longTargets.entrySet().iterator(), long.class); ease(floatTargets.entrySet().iterator(), float.class); ease(doubleTargets.entrySet().iterator(), double.class); { Iterator<Entry<String, Integer>> iter = colorTargets.entrySet().iterator(); while (iter.hasNext()) { val en = iter.next(); FieldAccessor<Integer> accessor = getAccessor(en.getKey(), int.class); val current = accessor.get(); if (current.equals(en.getValue())) { iter.remove(); } else { Color targetCol = new Color(en.getValue(), true); Color col = new Color(current, true); float r = (float) Math.min(255, adjust(col.getRed(), targetCol.getRed())); float g = (float) Math.min(255, adjust(col.getGreen(), targetCol.getGreen())); float b = (float) Math.min(255, adjust(col.getBlue(), targetCol.getBlue())); float a = (float) Math.min(255, adjust(col.getAlpha(), targetCol.getAlpha())); accessor.set(new Color(r / 255f, g / 255f, b / 255f, a / 255f).getRGB()); } } } } else { if (autoClose && byteTargets.isEmpty() && shortTargets.isEmpty() && integerTargets.isEmpty() && longTargets.isEmpty() && floatTargets.isEmpty() && doubleTargets.isEmpty() && colorTargets.isEmpty()) { close(); } } } protected double adjust(double current, double target) { double adjustment = target - current; if (adjustment > 0.05) { adjustment = Math.max(0.05, adjustment / speed); } else if (adjustment < -0.05) { adjustment = Math.min(-0.05, adjustment / speed); } return current + adjustment; } @SuppressWarnings("unchecked") protected FieldAccessor getAccessor(String key, Class<?> setterClass) { if (!accessors.containsKey(key)) { accessors.put(key, new FriendlyFieldAccessor(toEase, toEase.getClass(), setterClass, key)); } return accessors.get(key); } public void setAccessor(String value, FieldAccessor<?> accessor) { accessors.put(value, accessor); } public void easeByte(String value, byte target) { byteTargets.put(value, target); } public void easeShort(String value, short target) { shortTargets.put(value, target); } public void easeInteger(String value, int target) { integerTargets.put(value, target); } public void easeLong(String value, long target) { longTargets.put(value, target); } public void easeColorInt(String value, int target) { colorTargets.put(value, target); } public void easeFloat(String value, float target) { floatTargets.put(value, target); } public void easeDouble(String value, double target) { doubleTargets.put(value, target); } public void cancelEase(String value) { byteTargets.remove(value); shortTargets.remove(value); integerTargets.remove(value); longTargets.remove(value); floatTargets.remove(value); doubleTargets.remove(value); colorTargets.remove(value); } public void registerCloseListener(Runnable r) { closeListeners.add(r); } public void unregisterCloseListener(Runnable r) { closeListeners.remove(r); } @Override public void close() { if (closed) return; GlassPaneMod.easers.remove(toEase); for (Runnable r : closeListeners) { r.run(); } closeListeners = null; byteTargets = null; shortTargets = null; integerTargets = null; longTargets = null; floatTargets = null; doubleTargets = null; closed = true; } }