diff --git a/TODO.md b/TODO.md index 57d9a3592..2e25a6a9b 100644 --- a/TODO.md +++ b/TODO.md @@ -8,8 +8,9 @@ * [ ] Pie menu ## High Priority + - [ ] merge animations with NEA -- [ ] check JEI click interactions +- [x] check JEI click interactions ## Medium Priority diff --git a/dependencies.gradle b/dependencies.gradle index 7e4efb298..fb254407f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -25,4 +25,6 @@ dependencies { embed 'org.mariuszgromada.math:MathParser.org-mXparser:6.1.0' implementation(rfg.deobf("curse.maven:baubles-227083:2518667")) + implementation rfg.deobf("curse.maven:neverenoughanimation-1062347:6533650-sources-6533651") + //implementation("com.cleanroommc:neverenoughanimations:1.0.6") { transitive false } } diff --git a/src/main/java/com/cleanroommc/modularui/ClientProxy.java b/src/main/java/com/cleanroommc/modularui/ClientProxy.java index 90b0479d7..de7729037 100644 --- a/src/main/java/com/cleanroommc/modularui/ClientProxy.java +++ b/src/main/java/com/cleanroommc/modularui/ClientProxy.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui; +import com.cleanroommc.modularui.animation.AnimatorManager; import com.cleanroommc.modularui.drawable.DrawableSerialization; import com.cleanroommc.modularui.factory.GuiFactories; import com.cleanroommc.modularui.factory.inventory.InventoryTypes; @@ -44,8 +45,8 @@ void preInit(FMLPreInitializationEvent event) { super.preInit(event); MinecraftForge.EVENT_BUS.register(ClientScreenHandler.class); - MinecraftForge.EVENT_BUS.register(OverlayManager.class); MinecraftForge.EVENT_BUS.register(KeyBindHandler.class); + AnimatorManager.init(); if (ModularUIConfig.enableTestGuis) { MinecraftForge.EVENT_BUS.register(EventHandler.class); @@ -75,7 +76,7 @@ void postInit(FMLPostInitializationEvent event) { @SubscribeEvent public void onKeyboard(InputEvent.KeyInputEvent event) { - if (testKey.isPressed() && ModularUI.isBaubleLoaded()) { + if (ModularUIConfig.enableTestGuis && testKey.isPressed() && ModularUI.Mods.BAUBLES.isLoaded()) { InventoryTypes.BAUBLES.visitAll(Minecraft.getMinecraft().player, (type, index, stack) -> { if (stack.getItem() instanceof TestItem) { GuiFactories.playerInventory().openFromBaublesClient(index); diff --git a/src/main/java/com/cleanroommc/modularui/ModularUI.java b/src/main/java/com/cleanroommc/modularui/ModularUI.java index 02aff81d7..c08090087 100644 --- a/src/main/java/com/cleanroommc/modularui/ModularUI.java +++ b/src/main/java/com/cleanroommc/modularui/ModularUI.java @@ -2,6 +2,7 @@ import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.ModContainer; import net.minecraftforge.fml.common.SidedProxy; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; @@ -9,14 +10,18 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import org.mariuszgromada.math.mxparser.License; +import java.util.function.Predicate; + @Mod(modid = ModularUI.ID, name = ModularUI.NAME, version = ModularUI.VERSION, acceptedMinecraftVersions = "[1.12,)", dependencies = "required-after:mixinbooter@[8.0,);" + - "after:bogorter@[1.4.0,);") + "after:bogorter@[1.4.0,);" + + "after-client:neverenoughanimations@[1.0.6,)") public class ModularUI { public static final String ID = MuiTags.MODID; @@ -35,11 +40,6 @@ public class ModularUI { @Mod.Instance public static ModularUI INSTANCE; - private static boolean blurLoaded = false; - private static boolean sorterLoaded = false; - private static boolean jeiLoaded = false; - private static boolean baubleLoaded = false; - static { // confirm mXparser license License.iConfirmNonCommercialUse("CleanroomMC"); @@ -47,10 +47,6 @@ public class ModularUI { @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { - blurLoaded = Loader.isModLoaded("blur"); - sorterLoaded = Loader.isModLoaded(BOGO_SORT); - jeiLoaded = Loader.isModLoaded("jei"); - baubleLoaded = Loader.isModLoaded("baubles"); proxy.preInit(event); } @@ -64,19 +60,46 @@ public void onServerLoad(FMLServerStartingEvent event) { proxy.onServerLoad(event); } - public static boolean isBlurLoaded() { - return blurLoaded; + public enum Mods { + + BAUBLES(ModIds.BAUBLES), + BLUR(ModIds.BLUR), + BOGOSORTER(ModIds.BOGOSORTER), + JEI(ModIds.JEI), + NEA(ModIds.NEA); + + public final String id; + private boolean loaded = false; + private boolean initialized = false; + private final Predicate extraLoadedCheck; + + Mods(String id) { + this(id, null); + } + + Mods(String id, @Nullable Predicate extraLoadedCheck) { + this.id = id; + this.extraLoadedCheck = extraLoadedCheck; + } + + public boolean isLoaded() { + if (!this.initialized) { + this.loaded = Loader.isModLoaded(this.id); + if (this.loaded && this.extraLoadedCheck != null) { + this.loaded = this.extraLoadedCheck.test(Loader.instance().getIndexedModList().get(this.id)); + } + this.initialized = true; + } + return this.loaded; + } } - public static boolean isSortModLoaded() { - return sorterLoaded; - } - - public static boolean isJeiLoaded() { - return jeiLoaded; - } + public static class ModIds { - public static boolean isBaubleLoaded() { - return baubleLoaded; + public static final String BLUR = "blur"; + public static final String BOGOSORTER = "bogosorter"; + public static final String JEI = "jei"; + public static final String NEA = "neverenoughanimations"; + public static final String BAUBLES = "baubles"; } } diff --git a/src/main/java/com/cleanroommc/modularui/animation/Animator.java b/src/main/java/com/cleanroommc/modularui/animation/Animator.java new file mode 100644 index 000000000..870133c29 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/Animator.java @@ -0,0 +1,242 @@ +package com.cleanroommc.modularui.animation; + +import com.cleanroommc.modularui.api.drawable.IInterpolation; +import com.cleanroommc.modularui.utils.Interpolation; + +import java.util.function.DoubleConsumer; +import java.util.function.DoublePredicate; + +public class Animator extends BaseAnimator implements IAnimator { + + private float min = 0.0f; + private float max = 1.0f; + private int duration = 250; + private IInterpolation curve = Interpolation.LINEAR; + private boolean reverseOnFinish = false; + private int repeats = 0; + private DoublePredicate onUpdate; + private Runnable onFinish; + + private int progress = 0; + private boolean startedReverse = false; + private int repeated = 0; + + @Override + public void reset(boolean atEnd) { + this.progress = atEnd ? this.duration : 0; + this.startedReverse = atEnd; + this.repeated = 0; + } + + @Override + public void stop(boolean force) { + if (isAnimating() && !force) { + if (this.reverseOnFinish && this.startedReverse == isAnimatingReverse()) { + onAnimationFinished(false, false); + // started reverse -> bounce back and animate forward + animate(isAnimatingForward()); + return; + } + if (repeats != 0 && (repeated < repeats || repeats < 0)) { + onAnimationFinished(true, false); + // started forward -> full cycle finished -> try repeating + boolean reverse = !this.reverseOnFinish == isAnimatingReverse(); + animate(reverse); + repeated++; + return; + } + } + super.stop(force); + } + + @Override + public int advance(int elapsedTime) { + if (!isAnimating()) return elapsedTime; + while (elapsedTime > 0) { + int max = isAnimatingForward() ? this.duration - this.progress : this.progress; + int prog = Math.min(max, elapsedTime); + this.progress += prog * getDirection(); + elapsedTime -= prog; + if (onUpdate()) { + stop(true); + break; + } + if ((isAnimatingForward() && this.progress >= this.duration) || (isAnimatingReverse() && this.progress <= 0)) { + stop(false); + if (!isAnimating()) { + onAnimationFinished(true, true); + break; + } + } + } + return elapsedTime; + } + + protected boolean onUpdate() { + return this.onUpdate != null && this.onUpdate.test(getRawValue()); + } + + protected void onAnimationFinished(boolean finishedOneCycle, boolean finishedAllRepeats) { + if (this.onFinish != null) { + this.onFinish.run(); + } + } + + public boolean isAtEnd() { + return this.progress >= this.duration; + } + + public boolean isAtStart() { + return this.progress <= 0; + } + + public float getMin() { + return min; + } + + public float getMax() { + return max; + } + + public int getDuration() { + return duration; + } + + public IInterpolation getCurve() { + return curve; + } + + protected float getRawValue() { + return this.curve.interpolate(this.min, this.max, (float) this.progress / this.duration); + } + + public float getValue() { + //advance(); + return getRawValue(); + } + + @Override + public boolean hasProgressed() { + if (!isAnimating()) return false; + return isAnimatingForward() ? this.progress > 0 : this.progress < this.duration; + } + + /** + * Sets the min bound of the value that will be interpolated. + * + * @param min min value + * @return this + */ + public Animator min(float min) { + this.min = min; + return this; + } + + /** + * Sets the max bound of the value that will be interpolated. + * + * @param max max value + * @return this + */ + public Animator max(float max) { + this.max = max; + return this; + } + + /** + * Sets the bounds of the value that will be interpolated. + * + * @param min min value + * @param max max value + * @return this + */ + public Animator bounds(float min, float max) { + this.min = min; + this.max = max; + return this; + } + + /** + * The duration of this animation in milliseconds. Note this is not 100% accurate. + * Usually it's plus minus 2ms, but can rarely be more. + * + * @param duration duration in milliseconds + * @return this + */ + public Animator duration(int duration) { + this.duration = duration; + return this; + } + + /** + * Sets the interpolation curve, which is used to interpolate between the bounds. + * + * @param curve curve to interpolate on + * @return this + */ + public Animator curve(IInterpolation curve) { + this.curve = curve; + return this; + } + + /** + * Sets if the animation should reverse animate once after it finished. + * If the animation started in reverse it will animate forward on finish. + * + * @param reverseOnFinish if animation should bounce back on finish + * @return this + */ + public Animator reverseOnFinish(boolean reverseOnFinish) { + this.reverseOnFinish = reverseOnFinish; + return this; + } + + /** + * Sets how often the animation should repeat. If {@link #reverseOnFinish(boolean)} is set to true, it will repeat the whole cycle. + * If the number of repeats is negative, it will repeat infinitely. + * + * @param repeats how often the animation should repeat. + * @return this + */ + public Animator repeatsOnFinish(int repeats) { + this.repeats = repeats; + return this; + } + + /** + * Sets a function which is executed everytime the progress updates, that is on every frame. + * The argument of the function is the interpolated value. + * + * @param onUpdate update function + * @return this + */ + public Animator onUpdate(DoublePredicate onUpdate) { + this.onUpdate = onUpdate; + return this; + } + + /** + * Sets a function which is executed everytime the progress updates, that is on every frame. + * The argument of the function is the interpolated value. + * + * @param onUpdate update function + * @return this + */ + public Animator onUpdate(DoubleConsumer onUpdate) { + return onUpdate(val -> { + onUpdate.accept(val); + return false; + }); + } + + /** + * Sets a function which is executed everytime, on animation, cycle or all repeats is finished. + * + * @param onFinish finish function + * @return this + */ + public Animator onFinish(Runnable onFinish) { + this.onFinish = onFinish; + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/animation/AnimatorManager.java b/src/main/java/com/cleanroommc/modularui/animation/AnimatorManager.java new file mode 100644 index 000000000..be16859ff --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/AnimatorManager.java @@ -0,0 +1,56 @@ +package com.cleanroommc.modularui.animation; + +import net.minecraft.client.Minecraft; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.ArrayList; +import java.util.List; + +public class AnimatorManager { + + private static final List animators = new ArrayList<>(16); + private static final List queuedAnimators = new ArrayList<>(8); + private static long lastTime = 0; + + static void startAnimation(IAnimator animator) { + if (!animators.contains(animator) && !queuedAnimators.contains(animator)) { + queuedAnimators.add(animator); + } + } + + private AnimatorManager() {} + + public static void init() { + MinecraftForge.EVENT_BUS.register(new AnimatorManager()); + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onDraw(GuiScreenEvent.DrawScreenEvent.Pre event) { + long time = Minecraft.getSystemTime(); + int elapsedTime = IAnimator.getTimeDiff(lastTime, time); + if (lastTime > 0 && !animators.isEmpty()) { + animators.removeIf(animator -> { + if (animator == null) return true; + if (animator.isPaused()) return false; + animator.advance(elapsedTime); + return !animator.isAnimating(); + }); + } + lastTime = time; + animators.addAll(queuedAnimators); + queuedAnimators.clear(); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onDraw(GuiOpenEvent event) { + if (event.getGui() == null) { + // stop and yeet all animators on gui close + animators.forEach(iAnimator -> iAnimator.stop(false)); + animators.clear(); + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/animation/BaseAnimator.java b/src/main/java/com/cleanroommc/modularui/animation/BaseAnimator.java new file mode 100644 index 000000000..7b9c93e20 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/BaseAnimator.java @@ -0,0 +1,69 @@ +package com.cleanroommc.modularui.animation; + +import org.jetbrains.annotations.Nullable; + +public abstract class BaseAnimator implements IAnimator { + + private IAnimator parent; + + private byte direction = 0; + private boolean paused = false; + + void setParent(IAnimator parent) { + this.parent = parent; + } + + @Nullable + public final IAnimator getParent() { + return parent; + } + + @Override + public void stop(boolean force) { + this.direction = 0; + } + + @Override + public void pause() { + this.paused = true; + } + + @Override + public void resume(boolean reverse) { + this.paused = false; + this.direction = (byte) (reverse ? -1 : 1); + if (this.parent == null) AnimatorManager.startAnimation(this); + } + + @Override + public boolean isPaused() { + return paused; + } + + @Override + public boolean isAnimating() { + return this.direction != 0; + } + + @Override + public boolean isAnimatingReverse() { + return this.direction < 0; + } + + @Override + public boolean isAnimatingForward() { + return this.direction > 0; + } + + public final byte getDirection() { + return direction; + } + + public SequentialAnimator followedBy(IAnimator animator) { + return new SequentialAnimator(this, animator); + } + + public ParallelAnimator inParallelWith(IAnimator animator) { + return new ParallelAnimator(this, animator); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/animation/IAnimatable.java b/src/main/java/com/cleanroommc/modularui/animation/IAnimatable.java new file mode 100644 index 000000000..98a5fed88 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/IAnimatable.java @@ -0,0 +1,62 @@ +package com.cleanroommc.modularui.animation; + +import com.cleanroommc.modularui.api.drawable.IInterpolation; + +public interface IAnimatable> { + + T interpolate(T start, T end, float t); + + T copyOrImmutable(); + + default boolean shouldAnimate(T target) { + return !equals(target); + } + + default MutableObjectAnimator animator(T target) { + T self = (T) this; + return new MutableObjectAnimator<>(self, self.copyOrImmutable(), target); + } + + default void animate(T target) { + animate(target, false); + } + + default void animate(T target, boolean reverse) { + if (shouldAnimate(target)) { + animator(target).animate(reverse); + } + } + + default void animate(T target, boolean reverse, boolean reverseOnFinish, int repeatsOnFinish) { + if (shouldAnimate(target)) { + animator(target).reverseOnFinish(reverseOnFinish).repeatsOnFinish(repeatsOnFinish).animate(reverse); + } + } + + default void animate(T target, int durationMs, boolean reverse) { + if (shouldAnimate(target)) { + animator(target).duration(durationMs).animate(reverse); + } + } + + default void animate(T target, IInterpolation curve, boolean reverse) { + if (shouldAnimate(target)) { + animator(target).curve(curve).animate(reverse); + } + } + + default void animate(T target, IInterpolation curve, int durationMs, boolean reverse) { + animate(target, curve, durationMs, reverse, false, 0); + } + + default void animate(T target, IInterpolation curve, int durationMs, boolean reverse, boolean reverseOnFinish, int repeatsOnFinish) { + if (shouldAnimate(target)) { + animator(target) + .curve(curve) + .duration(durationMs) + .reverseOnFinish(reverseOnFinish) + .repeatsOnFinish(repeatsOnFinish) + .animate(reverse); + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/animation/IAnimator.java b/src/main/java/com/cleanroommc/modularui/animation/IAnimator.java new file mode 100644 index 000000000..c9178e879 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/IAnimator.java @@ -0,0 +1,62 @@ +package com.cleanroommc.modularui.animation; + +import net.minecraft.client.Minecraft; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public interface IAnimator { + + @Nullable IAnimator getParent(); + + default void animate(boolean reverse) { + reset(reverse); + resume(reverse); + } + + default void animate() { + animate(false); + } + + void stop(boolean force); + + void pause(); + + void resume(boolean reverse); + + void reset(boolean atEnd); + + default void reset() { + reset(false); + } + + /** + * Advances the animation by a given duration. + * + * @param elapsedTime elapsed time in ms + * @return remaining time (elapsed time - consumed time) + */ + @ApiStatus.OverrideOnly + int advance(int elapsedTime); + + boolean isPaused(); + + boolean isAnimating(); + + boolean isAnimatingReverse(); + + boolean hasProgressed(); + + default boolean isAnimatingForward() { + return isAnimating() && !isAnimatingReverse(); + } + + static int getTimeDiff(long startTime) { + return getTimeDiff(startTime, Minecraft.getSystemTime()); + } + + static int getTimeDiff(long startTime, long currentTime) { + long elapsedTime = Math.abs(currentTime - startTime); + return (int) Math.min(Integer.MAX_VALUE, elapsedTime); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/animation/MutableObjectAnimator.java b/src/main/java/com/cleanroommc/modularui/animation/MutableObjectAnimator.java new file mode 100644 index 000000000..a338bb8ca --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/MutableObjectAnimator.java @@ -0,0 +1,38 @@ +package com.cleanroommc.modularui.animation; + +import java.util.function.Consumer; + +public class MutableObjectAnimator> extends Animator { + + private final T from; + private final T to; + private final T animatable; + private Consumer intermediateConsumer; + + public MutableObjectAnimator(T animatable, T from, T to) { + this.from = from; + this.to = to; + this.animatable = animatable; + bounds(0f, 1f); + } + + @Override + public void resume(boolean reverse) { + super.resume(reverse); + this.animatable.interpolate(this.from, this.to, getRawValue()); + } + + @Override + protected boolean onUpdate() { + T intermediate = this.animatable.interpolate(this.from, this.to, getRawValue()); + if (this.intermediateConsumer != null) { + this.intermediateConsumer.accept(intermediate); + } + return super.onUpdate(); + } + + public MutableObjectAnimator intermediateConsumer(Consumer consumer) { + this.intermediateConsumer = consumer; + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/animation/ParallelAnimator.java b/src/main/java/com/cleanroommc/modularui/animation/ParallelAnimator.java new file mode 100644 index 000000000..f8bd2dbaf --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/ParallelAnimator.java @@ -0,0 +1,116 @@ +package com.cleanroommc.modularui.animation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ParallelAnimator extends BaseAnimator implements IAnimator { + + private final List animators; + private int waitTimeBetweenAnimators; + + private int startedAnimating = 0; + private int finishedAnimating = 0; + private int waitTime = 0; + + public ParallelAnimator(List animators) { + this.animators = new ArrayList<>(animators); + this.animators.forEach(animator -> { + if (animator instanceof BaseAnimator baseAnimator) { + baseAnimator.setParent(this); + } + }); + } + + public ParallelAnimator(IAnimator... animators) { + this.animators = new ArrayList<>(); + Collections.addAll(this.animators, animators); + this.animators.forEach(animator -> { + if (animator instanceof BaseAnimator baseAnimator) { + baseAnimator.setParent(this); + } + }); + } + + @Override + public void animate(boolean reverse) { + super.animate(reverse); + if (this.waitTimeBetweenAnimators <= 0) { + for (IAnimator animator : animators) { + animator.animate(reverse); + } + this.startedAnimating = this.animators.size(); + } else { + this.animators.get(this.startedAnimating).animate(reverse); + } + } + + @Override + public void stop(boolean force) { + super.stop(force); + for (IAnimator animator : animators) { + animator.stop(force); + } + } + + @Override + public void reset(boolean atEnd) { + this.startedAnimating = 0; + this.finishedAnimating = 0; + for (IAnimator animator : animators) { + animator.reset(atEnd); + } + } + + @Override + public int advance(int elapsedTime) { + int remainingTime = 0; + for (int i = 0; i < this.startedAnimating; i++) { + IAnimator animator = this.animators.get(i); + if (!animator.isAnimating()) continue; + remainingTime = Math.max(remainingTime, animator.advance(elapsedTime)); + if (!animator.isAnimating()) { + this.finishedAnimating++; + if (isFinished()) { + stop(false); + return remainingTime; + } + } + } + while (elapsedTime > 0 && this.startedAnimating < this.animators.size()) { + int prog = Math.min(elapsedTime, this.waitTimeBetweenAnimators - this.waitTime); + this.waitTime += prog; + elapsedTime -= prog; + if (this.waitTime >= this.waitTimeBetweenAnimators) { + this.animators.get(this.startedAnimating).animate(isAnimatingReverse()); + this.waitTime -= this.waitTimeBetweenAnimators; + this.startedAnimating++; + } + } + return Math.min(elapsedTime, remainingTime); + } + + public boolean isFinished() { + return this.finishedAnimating == this.animators.size(); + } + + @Override + public boolean hasProgressed() { + return isAnimating() && this.startedAnimating > 0; + } + + public ParallelAnimator waitTimeBetweenAnimators(int waitTime) { + this.waitTimeBetweenAnimators = waitTime; + return this; + } + + @Override + public ParallelAnimator inParallelWith(IAnimator animator) { + if (isAnimating()) { + throw new IllegalStateException("Can't add animators while animating"); + } + reset(); + this.animators.add(animator); + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/animation/SequentialAnimator.java b/src/main/java/com/cleanroommc/modularui/animation/SequentialAnimator.java new file mode 100644 index 000000000..41b44bf7d --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/SequentialAnimator.java @@ -0,0 +1,83 @@ +package com.cleanroommc.modularui.animation; + +import com.cleanroommc.modularui.ModularUI; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SequentialAnimator extends BaseAnimator implements IAnimator { + + private final List animators; + private int currentIndex = 0; + + public SequentialAnimator(List animators) { + this.animators = new ArrayList<>(animators); + this.animators.forEach(animator -> { + if (animator instanceof BaseAnimator baseAnimator) { + baseAnimator.setParent(this); + } + }); + } + + public SequentialAnimator(IAnimator... animators) { + this.animators = new ArrayList<>(); + Collections.addAll(this.animators, animators); + this.animators.forEach(animator -> { + if (animator instanceof BaseAnimator baseAnimator) { + baseAnimator.setParent(this); + } + }); + } + + @Override + public void animate(boolean reverse) { + if (this.animators.isEmpty()) return; + super.animate(reverse); + // start first animation + this.animators.get(this.currentIndex).animate(reverse); + } + + @Override + public void reset(boolean atEnd) { + this.currentIndex = atEnd ? this.animators.size() - 1 : 0; + this.animators.forEach(animator -> animator.reset(atEnd)); + } + + @Override + public int advance(int elapsedTime) { + while (isAnimating() && elapsedTime > 0) { + IAnimator animator = this.animators.get(currentIndex); + elapsedTime = animator.advance(elapsedTime); + if (!animator.isAnimating()) { + // animator has finished + this.currentIndex += getDirection(); + ModularUI.LOGGER.info("Finished {}th animator", this.currentIndex); + if (this.currentIndex >= this.animators.size() || this.currentIndex < 0) { + // whole sequence has finished + stop(false); + } else { + // start next animation + animator = this.animators.get(this.currentIndex); + animator.animate(isAnimatingReverse()); + } + } + } + return elapsedTime; + } + + @Override + public boolean hasProgressed() { + return !this.animators.isEmpty() && this.animators.get(0).hasProgressed(); + } + + @Override + public SequentialAnimator followedBy(IAnimator animator) { + if (isAnimating()) { + throw new IllegalStateException("Can't add animators while animating"); + } + reset(); + this.animators.add(animator); + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/animation/Wait.java b/src/main/java/com/cleanroommc/modularui/animation/Wait.java new file mode 100644 index 000000000..0dc8d65e2 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/animation/Wait.java @@ -0,0 +1,41 @@ +package com.cleanroommc.modularui.animation; + +public class Wait extends BaseAnimator { + + private int duration; + private int progress = 0; + + public Wait() { + this(250); + } + + public Wait(int duration) { + this.duration = duration; + } + + @Override + public void reset(boolean atEnd) { + this.progress = 0; + } + + @Override + public int advance(int elapsedTime) { + int max = this.duration - this.progress; + int prog = Math.min(max, elapsedTime); + this.progress += prog; + if (this.progress >= this.duration) { + stop(false); + } + return elapsedTime - prog; + } + + @Override + public boolean hasProgressed() { + return progress > 0 && isAnimating(); + } + + public Wait duration(int duration) { + this.duration = duration; + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java b/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java index 306e8639a..a9fd86215 100644 --- a/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java +++ b/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java @@ -1,14 +1,18 @@ package com.cleanroommc.modularui.api; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.core.mixin.GuiContainerAccessor; import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.neverenoughanimations.api.IAnimatedScreen; + import com.cleanroommc.modularui.utils.Platform; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.inventory.Slot; +import net.minecraftforge.fml.common.Optional; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -25,8 +29,9 @@ * See {@link com.cleanroommc.modularui.screen.GuiScreenWrapper GuiScreenWrapper} and {@link com.cleanroommc.modularui.screen.GuiContainerWrapper GuiContainerWrapper} * for default implementations. */ +@Optional.Interface(modid = ModularUI.ModIds.NEA, iface = "com.cleanroommc.neverenoughanimations.api.IAnimatedScreen") @SideOnly(Side.CLIENT) -public interface IMuiScreen { +public interface IMuiScreen extends IAnimatedScreen { /** * Returns the {@link ModularScreen} that is being wrapped. This should return a final instance field. @@ -108,4 +113,24 @@ default void setHoveredSlot(Slot slot) { default GuiScreen getGuiScreen() { return (GuiScreen) this; } + + @Override + default int nea$getX() { + return getScreen().getMainPanel().getArea().x; + } + + @Override + default int nea$getY() { + return getScreen().getMainPanel().getArea().y; + } + + @Override + default int nea$getWidth() { + return getScreen().getMainPanel().getArea().width; + } + + @Override + default int nea$getHeight() { + return getScreen().getMainPanel().getArea().height; + } } diff --git a/src/main/java/com/cleanroommc/modularui/api/IPanelHandler.java b/src/main/java/com/cleanroommc/modularui/api/IPanelHandler.java index abd4dc9ff..13c7c3431 100644 --- a/src/main/java/com/cleanroommc/modularui/api/IPanelHandler.java +++ b/src/main/java/com/cleanroommc/modularui/api/IPanelHandler.java @@ -12,7 +12,7 @@ * This class can handle opening and closing of a {@link ModularPanel}. It makes sure, that the same panel is not created multiple * times and instead reused. *

Using {@link #openPanel()} is the only way to open multiple panels.

- *

Panels can be closed with {@link #closePanel()}, but also with {@link ModularPanel#closeIfOpen(boolean)} and + *

Panels can be closed with {@link #closePanel()}, but also with {@link ModularPanel#closeIfOpen()} and * {@link ModularPanel#animateClose()}. With the difference, that the method from this interface also works on server side.

* Synced panels must be created with {@link PanelSyncManager#panel(String, PanelSyncHandler.IPanelBuilder, boolean)}. * If the panel does not contain any synced widgets, a simple panel handler using {@link #simple(ModularPanel, SecondaryPanel.IPanelBuilder, boolean)} diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IViewportStack.java b/src/main/java/com/cleanroommc/modularui/api/layout/IViewportStack.java index 11036b711..71dca552f 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IViewportStack.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IViewportStack.java @@ -1,6 +1,7 @@ package com.cleanroommc.modularui.api.layout; import com.cleanroommc.modularui.screen.viewport.TransformationMatrix; +import com.cleanroommc.modularui.utils.Matrix4f; import com.cleanroommc.modularui.utils.Vector3f; import com.cleanroommc.modularui.widget.sizer.Area; @@ -111,6 +112,8 @@ public interface IViewportStack { */ void scale(float x, float y); + void multiply(Matrix4f matrix); + /** * Resets the top matrix to the matrix below. */ diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java b/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java index e6a0067b5..9eca4cffe 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java @@ -108,4 +108,8 @@ default int getDefaultWidth() { default int getDefaultHeight() { return 18; } + + void scheduleResize(); + + boolean requiresResize(); } diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java b/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java index 671045b4e..83862a095 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java @@ -20,6 +20,10 @@ public interface IPositioned> { Area getArea(); + boolean requiresResize(); + + void scheduleResize(); + @SuppressWarnings("unchecked") default W getThis() { return (W) this; @@ -403,71 +407,85 @@ default W flex(Consumer flexConsumer) { default W padding(int left, int right, int top, int bottom) { getArea().getPadding().all(left, right, top, bottom); + scheduleResize(); return getThis(); } default W padding(int horizontal, int vertical) { getArea().getPadding().all(horizontal, vertical); + scheduleResize(); return getThis(); } default W padding(int all) { getArea().getPadding().all(all); + scheduleResize(); return getThis(); } default W paddingLeft(int val) { getArea().getPadding().left(val); + scheduleResize(); return getThis(); } default W paddingRight(int val) { getArea().getPadding().right(val); + scheduleResize(); return getThis(); } default W paddingTop(int val) { getArea().getPadding().top(val); + scheduleResize(); return getThis(); } default W paddingBottom(int val) { getArea().getPadding().bottom(val); + scheduleResize(); return getThis(); } default W margin(int left, int right, int top, int bottom) { getArea().getMargin().all(left, right, top, bottom); + scheduleResize(); return getThis(); } default W margin(int horizontal, int vertical) { getArea().getMargin().all(horizontal, vertical); + scheduleResize(); return getThis(); } default W margin(int all) { getArea().getMargin().all(all); + scheduleResize(); return getThis(); } default W marginLeft(int val) { getArea().getMargin().left(val); + scheduleResize(); return getThis(); } default W marginRight(int val) { getArea().getMargin().right(val); + scheduleResize(); return getThis(); } default W marginTop(int val) { getArea().getMargin().top(val); + scheduleResize(); return getThis(); } default W marginBottom(int val) { getArea().getMargin().bottom(val); + scheduleResize(); return getThis(); } } diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java index c160ef724..7cc97c20c 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -229,7 +229,7 @@ default IWidget flexBuilder(Consumer builder) { /** * Called before a widget is resized. */ - default void beforeResize() {} + default void beforeResize(boolean onOpen) {} /** * Called after a widget is fully resized. diff --git a/src/main/java/com/cleanroommc/modularui/drawable/Circle.java b/src/main/java/com/cleanroommc/modularui/drawable/Circle.java index ebff19512..5f0e667d9 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/Circle.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/Circle.java @@ -1,10 +1,12 @@ package com.cleanroommc.modularui.drawable; +import com.cleanroommc.modularui.animation.IAnimatable; import com.cleanroommc.modularui.api.IJsonSerializable; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.utils.Interpolations; import com.cleanroommc.modularui.utils.JsonHelper; import net.minecraftforge.fml.relauncher.Side; @@ -13,7 +15,7 @@ import com.google.gson.JsonObject; import org.jetbrains.annotations.Contract; -public class Circle implements IDrawable, IJsonSerializable { +public class Circle implements IDrawable, IJsonSerializable, IAnimatable { private int colorInner, colorOuter, segments; @@ -85,4 +87,19 @@ public boolean saveToJson(JsonObject json) { json.addProperty("segments", this.segments); return true; } + + @Override + public Circle interpolate(Circle start, Circle end, float t) { + this.colorInner = Color.interpolate(start.colorInner, end.colorInner, t); + this.colorOuter = Color.interpolate(start.colorOuter, end.colorOuter, t); + this.segments = Interpolations.lerp(start.segments, end.segments, t); + return this; + } + + @Override + public Circle copyOrImmutable() { + return new Circle() + .setColor(this.colorInner, this.colorOuter) + .setSegments(this.segments); + } } diff --git a/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java b/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java index ebced8b68..a92d535bd 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java @@ -348,6 +348,20 @@ public static void drawSprite(TextureMap textureMap, TextureAtlasSprite sprite, drawTexture(x0, y0, x0 + w, y0 + h, sprite.getMinU(), sprite.getMinV(), sprite.getMaxU(), sprite.getMaxV()); } + public static void drawTiledSprite(TextureAtlasSprite sprite, float x0, float y0, float w, float h) { + drawTiledSprite(Minecraft.getMinecraft().getTextureMapBlocks(), sprite, x0, y0, w, h); + } + + public static void drawTiledSprite(TextureMap textureMap, TextureAtlasSprite sprite, float x0, float y0, float w, float h) { + GlStateManager.disableAlpha(); + GlStateManager.enableBlend(); + GlStateManager.enableTexture2D(); + GlStateManager.bindTexture(textureMap.getGlTextureId()); + drawTiledTexture(x0, y0, x0 + w, y0 + h, sprite.getMinU(), sprite.getMinV(), sprite.getMaxU(), sprite.getMaxV(), sprite.getIconWidth(), sprite.getIconHeight(), 0); + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + } + public static void drawOutlineCenter(int x, int y, int offset, int color) { drawOutlineCenter(x, y, offset, color, 1); } diff --git a/src/main/java/com/cleanroommc/modularui/drawable/Rectangle.java b/src/main/java/com/cleanroommc/modularui/drawable/Rectangle.java index c883b6a22..710e6daff 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/Rectangle.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/Rectangle.java @@ -1,10 +1,12 @@ package com.cleanroommc.modularui.drawable; +import com.cleanroommc.modularui.animation.IAnimatable; import com.cleanroommc.modularui.api.IJsonSerializable; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.utils.Interpolations; import com.cleanroommc.modularui.utils.JsonHelper; import net.minecraftforge.fml.relauncher.Side; @@ -15,7 +17,7 @@ import java.util.function.IntConsumer; -public class Rectangle implements IDrawable, IJsonSerializable { +public class Rectangle implements IDrawable, IJsonSerializable, IAnimatable { public static final double PI_2 = Math.PI / 2; @@ -137,4 +139,24 @@ private void setColor(JsonObject json, IntConsumer color, String... keys) { color.accept(Color.ofJson(element)); } } + + @Override + public Rectangle interpolate(Rectangle start, Rectangle end, float t) { + this.cornerRadius = Interpolations.lerp(start.cornerRadius, end.cornerRadius, t); + this.cornerSegments = Interpolations.lerp(start.cornerSegments, end.cornerSegments, t); + this.colorTL = Color.interpolate(start.colorTL, end.colorTL, t); + this.colorTR = Color.interpolate(start.colorTR, end.colorTR, t); + this.colorBL = Color.interpolate(start.colorBL, end.colorBL, t); + this.colorBR = Color.interpolate(start.colorBR, end.colorBR, t); + return this; + } + + @Override + public Rectangle copyOrImmutable() { + return new Rectangle() + .setColor(this.colorTL, this.colorTR, this.colorBL, this.colorBR) + .setCornerRadius(this.cornerRadius) + .setCornerSegments(this.cornerSegments) + .setCanApplyTheme(this.canApplyTheme); + } } diff --git a/src/main/java/com/cleanroommc/modularui/factory/GuiManager.java b/src/main/java/com/cleanroommc/modularui/factory/GuiManager.java index 933644930..18d586d4f 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/GuiManager.java +++ b/src/main/java/com/cleanroommc/modularui/factory/GuiManager.java @@ -21,7 +21,6 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.network.PacketBuffer; -import net.minecraftforge.client.event.GuiOpenEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.event.entity.player.PlayerContainerEvent; @@ -45,7 +44,6 @@ public class GuiManager { private static final Object2ObjectMap> FACTORIES = new Object2ObjectOpenHashMap<>(16); - private static IMuiScreen lastMui; private static final List openedContainers = new ArrayList<>(4); public static void registerFactory(UIFactory factory) { @@ -146,28 +144,4 @@ public static void onTick(TickEvent.ServerTickEvent event) { openedContainers.clear(); } } - - @SideOnly(Side.CLIENT) - @SubscribeEvent - public static void onGuiOpen(GuiOpenEvent event) { - if (lastMui != null && event.getGui() == null) { - if (lastMui.getScreen().getPanelManager().isOpen()) { - lastMui.getScreen().getPanelManager().closeAll(); - } - lastMui.getScreen().getPanelManager().dispose(); - lastMui = null; - } else if (event.getGui() instanceof IMuiScreen screenWrapper) { - if (lastMui == null) { - lastMui = screenWrapper; - } else if (lastMui == event.getGui()) { - lastMui.getScreen().getPanelManager().reopen(); - } else { - if (lastMui.getScreen().getPanelManager().isOpen()) { - lastMui.getScreen().getPanelManager().closeAll(); - } - lastMui.getScreen().getPanelManager().dispose(); - lastMui = screenWrapper; - } - } - } } diff --git a/src/main/java/com/cleanroommc/modularui/factory/inventory/InventoryTypes.java b/src/main/java/com/cleanroommc/modularui/factory/inventory/InventoryTypes.java index 6175378f1..a66306948 100644 --- a/src/main/java/com/cleanroommc/modularui/factory/inventory/InventoryTypes.java +++ b/src/main/java/com/cleanroommc/modularui/factory/inventory/InventoryTypes.java @@ -25,7 +25,7 @@ public IInventory getInventory(EntityPlayer player) { @Override public boolean isActive() { - return ModularUI.isBaubleLoaded(); + return ModularUI.Mods.BAUBLES.isLoaded(); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java index 97ef1acbf..795929169 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java @@ -25,7 +25,6 @@ public static void register(OverlayHandler handler) { } } - @SubscribeEvent(priority = EventPriority.LOWEST) public static void onGuiOpen(GuiOpenEvent event) { if (event.getGui() != Minecraft.getMinecraft().currentScreen) { OverlayStack.closeAll(); diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index fadab7bdf..a0257914e 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -12,11 +12,11 @@ import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.drawable.Stencil; import com.cleanroommc.modularui.integration.jei.ModularUIJeiPlugin; +import com.cleanroommc.modularui.overlay.OverlayManager; import com.cleanroommc.modularui.overlay.OverlayStack; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.screen.viewport.LocatedWidget; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; -import com.cleanroommc.modularui.utils.Animator; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.utils.FpsCounter; import com.cleanroommc.modularui.utils.Platform; @@ -25,6 +25,7 @@ import com.cleanroommc.modularui.widgets.slot.ItemSlot; import com.cleanroommc.modularui.widgets.slot.ModularSlot; import com.cleanroommc.modularui.widgets.slot.SlotGroup; +import com.cleanroommc.neverenoughanimations.animations.OpeningAnimation; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; @@ -76,9 +77,32 @@ public class ClientScreenHandler { private static final FpsCounter fpsCounter = new FpsCounter(); private static long ticks = 0L; - @SubscribeEvent + private static IMuiScreen lastMui; + + // we need to know the actual gui and not some fake bs some other mod overwrites + @SubscribeEvent(priority = EventPriority.LOWEST) public static void onGuiOpen(GuiOpenEvent event) { defaultContext.reset(); + if (lastMui != null && event.getGui() == null) { + if (lastMui.getScreen().getPanelManager().isOpen()) { + lastMui.getScreen().getPanelManager().closeAll(); + } + lastMui.getScreen().getPanelManager().dispose(); + lastMui = null; + } else if (event.getGui() instanceof IMuiScreen screenWrapper) { + if (lastMui == null) { + lastMui = screenWrapper; + } else if (lastMui == event.getGui()) { + lastMui.getScreen().getPanelManager().reopen(); + } else { + if (lastMui.getScreen().getPanelManager().isOpen()) { + lastMui.getScreen().getPanelManager().closeAll(); + } + lastMui.getScreen().getPanelManager().dispose(); + lastMui = screenWrapper; + } + } + if (event.getGui() instanceof IMuiScreen muiScreen) { Objects.requireNonNull(muiScreen.getScreen(), "ModularScreen must not be null!"); if (currentScreen != muiScreen.getScreen()) { @@ -95,6 +119,8 @@ public static void onGuiOpen(GuiOpenEvent event) { currentScreen = null; lastChar = null; } + + OverlayManager.onGuiOpen(event); } @SubscribeEvent @@ -132,7 +158,7 @@ public static void onGuiInputHigh(GuiScreenEvent.MouseInputEvent.Pre event) thro defaultContext.updateEventState(); if (checkGui(event.getGui())) currentScreen.getContext().updateEventState(); if (handleMouseInput(Mouse.getEventButton(), currentScreen, event.getGui())) { - if (ModularUI.isJeiLoaded()) { + if (ModularUI.Mods.JEI.isLoaded()) { ((IngredientListOverlay) ModularUIJeiPlugin.getRuntime().getIngredientListOverlay()).setKeyboardFocus(false); } event.setCanceled(true); @@ -192,7 +218,6 @@ public static long getTicks() { public static void onFrameUpdate() { OverlayStack.foreach(ModularScreen::onFrameUpdate, true); if (currentScreen != null) currentScreen.onFrameUpdate(); - Animator.advance(); } private static boolean doAction(@Nullable ModularScreen muiScreen, Predicate action) { @@ -277,7 +302,7 @@ private static boolean keyTyped(GuiScreen screen, char typedChar, int keyCode) t if (currentScreen.getContext().hasDraggable()) { currentScreen.getContext().dropDraggable(); } else { - currentScreen.getPanelManager().closeTopPanel(true); + currentScreen.getPanelManager().closeTopPanel(); } return true; } @@ -320,17 +345,19 @@ public static void releaseSlot() { } public static boolean shouldDrawWorldBackground() { - return ModularUI.isBlurLoaded() || Minecraft.getMinecraft().world == null; + return ModularUI.Mods.BLUR.isLoaded() || Minecraft.getMinecraft().world == null; } public static void drawDarkBackground(GuiScreen screen, int tint) { if (hasScreen()) { - float alpha = currentScreen.getMainPanel().getAlpha(); + float alpha = ModularUI.Mods.NEA.isLoaded() ? OpeningAnimation.getValue(screen) : 1f; // vanilla color values as hex int color = 0x101010; - int startAlpha = 0xc0; - int endAlpha = 0xd0; - GuiDraw.drawVerticalGradientRect(0, 0, screen.width, screen.height, Color.withAlpha(color, (int) (startAlpha * alpha)), Color.withAlpha(color, (int) (endAlpha * alpha))); + int start = (int) (0xc0 * alpha); + int end = (int) (0xd0 * alpha); + start = Color.withAlpha(color, start); + end = Color.withAlpha(color, end); + GuiDraw.drawVerticalGradientRect(0, 0, screen.width, screen.height, start, end); } } @@ -343,6 +370,7 @@ public static void drawScreen(ModularScreen muiScreen, GuiScreen mcScreen, int m } public static void drawScreenInternal(ModularScreen muiScreen, GuiScreen mcScreen, int mouseX, int mouseY, float partialTicks) { + GlStateManager.pushMatrix(); // needed for open animation currently Stencil.reset(); Stencil.apply(muiScreen.getScreenArea(), null); Platform.setupDrawTex(); @@ -350,6 +378,7 @@ public static void drawScreenInternal(ModularScreen muiScreen, GuiScreen mcScree GlStateManager.enableDepth(); GlStateManager.enableRescaleNormal(); RenderHelper.enableStandardItemLighting(); + handleAnimationScale(mcScreen); muiScreen.drawScreen(); GlStateManager.disableLighting(); GlStateManager.disableDepth(); @@ -364,6 +393,7 @@ public static void drawScreenInternal(ModularScreen muiScreen, GuiScreen mcScree GlStateManager.enableRescaleNormal(); RenderHelper.enableStandardItemLighting(); Stencil.remove(); + GlStateManager.popMatrix(); } public static void drawContainer(ModularScreen muiScreen, GuiContainer mcScreen, int mouseX, int mouseY, float partialTicks) { @@ -376,6 +406,7 @@ public static void drawContainer(ModularScreen muiScreen, GuiContainer mcScreen, int x = mcScreen.getGuiLeft(); int y = mcScreen.getGuiTop(); + //handleAnimationScale(mcScreen); acc.invokeDrawGuiContainerBackgroundLayer(partialTicks, mouseX, mouseY); muiScreen.drawScreen(); @@ -426,7 +457,7 @@ public static void drawContainer(ModularScreen muiScreen, GuiContainer mcScreen, } } - drawItemStack(mcScreen, itemstack, mouseX - x - 8, mouseY - y - k2, s); + drawItemStack(mcScreen, NEAAnimationHandler.injectVirtualCursorStack(mcScreen, itemstack), mouseX - x - 8, mouseY - y - k2, s); } if (!acc.getReturningStack().isEmpty()) { @@ -443,6 +474,8 @@ public static void drawContainer(ModularScreen muiScreen, GuiContainer mcScreen, int i2 = acc.getTouchUpY() + (int) ((float) i3 * f); drawItemStack(mcScreen, acc.getReturningStack(), l1, i2, null); } + + NEAAnimationHandler.drawItemAnimation(mcScreen); GlStateManager.popMatrix(); GlStateManager.enableLighting(); GlStateManager.enableDepth(); @@ -622,4 +655,10 @@ public boolean isLate() { return this == LATE; } } + + public static void handleAnimationScale(GuiScreen screen) { + if (ModularUI.Mods.NEA.isLoaded()) { + OpeningAnimation.handleScale(screen, true); + } + } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java b/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java index c95c98ddc..329f3c54e 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java +++ b/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java @@ -24,7 +24,7 @@ public DraggablePanelWrapper(ModularPanel panel) { public void drawMovingState(ModularGuiContext context, float partialTicks) { context.pushMatrix(); transform(context); - WidgetTree.drawTree(this.panel, context, true); + WidgetTree.drawTree(this.panel, context, true, true); context.popMatrix(); } @@ -52,7 +52,7 @@ public void onDragEnd(boolean successful) { this.panel.flex().relativeToScreen(); this.panel.flex().topRelAnchor(y, y) .leftRelAnchor(x, x); - WidgetTree.resize(this.panel); + this.panel.scheduleResize(); } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularContainer.java b/src/main/java/com/cleanroommc/modularui/screen/ModularContainer.java index 39b5d5887..da9942f93 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularContainer.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularContainer.java @@ -29,7 +29,7 @@ import java.util.*; -@Interface(modid = ModularUI.BOGO_SORT, iface = "com.cleanroommc.bogosorter.api.ISortableContainer") +@Interface(modid = ModularUI.ModIds.BOGOSORTER, iface = "com.cleanroommc.bogosorter.api.ISortableContainer") public class ModularContainer extends Container implements ISortableContainer { public static ModularContainer getCurrent(EntityPlayer player) { @@ -128,12 +128,15 @@ public void setAll(@NotNull List items) { @ApiStatus.Internal public void registerSlot(String panelName, ModularSlot slot) { - if (this.inventorySlots.contains(slot)) { - throw new IllegalArgumentException("Tried to register slot which already exists!"); - } if (slot.isPhantom()) { + if (this.phantomSlots.contains(slot)) { + throw new IllegalArgumentException("Tried to register slot which already exists!"); + } this.phantomSlots.add(slot); } else { + if (this.inventorySlots.contains(slot)) { + throw new IllegalArgumentException("Tried to register slot which already exists!"); + } addSlotToContainer(slot); } if (slot.getSlotGroupName() != null) { @@ -229,18 +232,7 @@ public boolean canInteractWith(@NotNull EntityPlayer playerIn) { if ((clickTypeIn == ClickType.PICKUP || clickTypeIn == ClickType.QUICK_MOVE) && (mouseButton == LEFT_MOUSE || mouseButton == RIGHT_MOUSE)) { if (slotId == DROP_TO_WORLD) { - // no dif - if (!inventoryplayer.getItemStack().isEmpty()) { - if (mouseButton == LEFT_MOUSE) { - player.dropItem(inventoryplayer.getItemStack(), true); - inventoryplayer.setItemStack(Platform.EMPTY_STACK); - } - - if (mouseButton == RIGHT_MOUSE) { - player.dropItem(inventoryplayer.getItemStack().splitStack(1), true); - } - } - return inventoryplayer.getItemStack(); + return superSlotClick(slotId, mouseButton, clickTypeIn, player); } // early return @@ -252,12 +244,12 @@ public boolean canInteractWith(@NotNull EntityPlayer playerIn) { if (!fromSlot.canTakeStack(player)) { return Platform.EMPTY_STACK; } - // looping so that crafting works properly - ItemStack remainder; - do { - remainder = transferStackInSlot(player, slotId); - returnable = Platform.copyStack(remainder); - } while (!Platform.isStackEmpty(remainder) && ItemHandlerHelper.canItemStacksStack(fromSlot.getStack(), remainder)); + + if (NEAAnimationHandler.shouldHandleNEA(this)) { + returnable = NEAAnimationHandler.injectQuickMove(this, player, slotId, fromSlot); + } else { + returnable = handleQuickMove(player, slotId, fromSlot); + } } else { Slot clickedSlot = getSlot(slotId); @@ -322,49 +314,6 @@ public boolean canInteractWith(@NotNull EntityPlayer playerIn) { } detectAndSendChanges(); return returnable; - } else if (clickTypeIn == ClickType.PICKUP_ALL && slotId >= 0) { - Slot slot = inventorySlots.get(slotId); - ItemStack itemstack1 = inventoryplayer.getItemStack(); - - if (!itemstack1.isEmpty() && (slot == null || !slot.getHasStack() || !slot.canTakeStack(player))) { - int i = mouseButton == 0 ? 0 : inventorySlots.size() - 1; - int j = mouseButton == 0 ? 1 : -1; - - for (int k = 0; k < 2; ++k) { - for (int l = i; l >= 0 && l < inventorySlots.size() && itemstack1.getCount() < itemstack1.getMaxStackSize(); l += j) { - Slot slot1 = inventorySlots.get(l); - if (slot1 instanceof ModularSlot modularSlot && modularSlot.isPhantom()) continue; - - if (slot1.getHasStack() && Container.canAddItemToSlot(slot1, itemstack1, true) && slot1.canTakeStack(player) && canMergeSlot(itemstack1, slot1)) { - ItemStack itemstack2 = slot1.getStack(); - - if (k != 0 || itemstack2.getCount() != itemstack2.getMaxStackSize()) { - int i1 = Math.min(itemstack1.getMaxStackSize() - itemstack1.getCount(), itemstack2.getCount()); - ItemStack itemstack3 = slot1.decrStackSize(i1); - itemstack1.grow(i1); - - if (itemstack3.isEmpty()) { - slot1.putStack(Platform.EMPTY_STACK); - } - - slot1.onTake(player, itemstack3); - } - } - } - } - } - - detectAndSendChanges(); - return returnable; - } else if (clickTypeIn == ClickType.SWAP && mouseButton >= 0 && mouseButton < 9) { - ModularSlot phantom = getModularSlot(slotId); - ItemStack hotbarStack = inventoryplayer.getStackInSlot(mouseButton); - if (phantom.isPhantom()) { - // insert stack from hotbar slot into phantom slot - phantom.putStack(hotbarStack.isEmpty() ? Platform.EMPTY_STACK : hotbarStack.copy()); - detectAndSendChanges(); - return returnable; - } } return superSlotClick(slotId, mouseButton, clickTypeIn, player); @@ -374,6 +323,17 @@ public boolean canInteractWith(@NotNull EntityPlayer playerIn) { return super.slotClick(slotId, mouseButton, clickTypeIn, player); } + public final ItemStack handleQuickMove(EntityPlayer player, int slotId, Slot fromSlot) { + // looping so that crafting works properly + ItemStack returnable; + ItemStack remainder; + do { + remainder = transferStackInSlot(player, slotId); + returnable = Platform.copyStack(remainder); + } while (!Platform.isStackEmpty(remainder) && ItemHandlerHelper.canItemStacksStack(fromSlot.getStack(), remainder)); + return returnable; + } + @Override public @NotNull ItemStack transferStackInSlot(@NotNull EntityPlayer playerIn, int index) { ModularSlot slot = getModularSlot(index); diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java index e8da3220a..15282511c 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java @@ -1,6 +1,7 @@ package com.cleanroommc.modularui.screen; import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.animation.Animator; import com.cleanroommc.modularui.api.IPanelHandler; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IDrawable; @@ -15,9 +16,9 @@ import com.cleanroommc.modularui.screen.viewport.LocatedWidget; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; -import com.cleanroommc.modularui.utils.Animator; import com.cleanroommc.modularui.utils.HoveredWidgetList; import com.cleanroommc.modularui.utils.Interpolation; +import com.cleanroommc.modularui.utils.Interpolations; import com.cleanroommc.modularui.utils.ObjectList; import com.cleanroommc.modularui.value.sync.PanelSyncHandler; import com.cleanroommc.modularui.value.sync.PanelSyncManager; @@ -26,6 +27,8 @@ import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widgets.SlotGroupWidget; +import com.cleanroommc.neverenoughanimations.NEAConfig; + import net.minecraft.client.Minecraft; import mezz.jei.gui.ghost.GhostIngredientDrag; @@ -71,9 +74,7 @@ public static ModularPanel defaultPanel(@NotNull String name, int width, int hei private final List clientSubPanels = new ArrayList<>(); private boolean invisible = false; - private Animator animator; - private float scale = 1f; - private float alpha = 1f; + private Animator animator;; public ModularPanel(@NotNull String name) { this.name = Objects.requireNonNull(name, "A panels name must not be null and should be unique!"); @@ -110,13 +111,16 @@ public boolean isOpen() { /** * If this panel is open it will be closed. * If animating is enabled and an animation is already playing this method will do nothing. - * - * @param animate true if the closing animation should play first. */ - public void closeIfOpen(boolean animate) { + public void closeIfOpen() { if (!isOpen()) return; closeSubPanels(); - if (!animate || !shouldAnimate()) { + if (isMainPanel()) { + // close screen and let NEA animation + Minecraft.getMinecraft().player.closeScreen(); + return; + } + if (!shouldAnimate()) { this.screen.getPanelManager().closePanel(this); return; } @@ -125,11 +129,13 @@ public void closeIfOpen(boolean animate) { // if this is the main panel, start closing animation for all panels for (ModularPanel panel : getScreen().getPanelManager().getOpenPanels()) { if (!panel.isMainPanel()) { - panel.closeIfOpen(true); + panel.closeIfOpen(); } } } - getAnimator().setEndCallback(val -> this.screen.getPanelManager().closePanel(this)).backward(); + getAnimator().onFinish(() -> this.screen.getPanelManager().closePanel(this)); + getAnimator().reset(true); + getAnimator().animate(true); } } @@ -140,7 +146,7 @@ protected void closeSubPanels() { } public void animateClose() { - closeIfOpen(true); + closeIfOpen(); } void setPanelHandler(IPanelHandler panelHandler) { @@ -212,16 +218,11 @@ public boolean canHover() { public void onOpen(ModularScreen screen) { this.screen = screen; getArea().z(1); - this.scale = 1f; - this.alpha = 1f; initialise(this); - if (shouldAnimate()) { - this.scale = 0.75f; - this.alpha = 0f; - getAnimator().setEndCallback(value -> { - this.scale = 1f; - this.alpha = 1f; - }).forward(); + if (!isMainPanel() && shouldAnimate()) { + getAnimator().onFinish(() -> {}); + getAnimator().reset(); + getAnimator().animate(); } this.state = State.OPEN; } @@ -350,7 +351,7 @@ public boolean onMousePressed(int mouseButton) { } private boolean checkJeiGhostIngredient(int mouseButton) { - if (ModularUI.isJeiLoaded() && ModularUIJeiPlugin.getGhostDrag() != null) { + if (ModularUI.Mods.JEI.isLoaded() && ModularUIJeiPlugin.getGhostDrag() != null) { // try inserting ghost ingredient GhostIngredientDrag drag = ModularUIJeiPlugin.getGhostDrag(); for (LocatedWidget widget : this.hovering) { @@ -627,10 +628,17 @@ public IWidget getTopHovering() { @Nullable public LocatedWidget getTopHoveringLocated(boolean debug) { - for (LocatedWidget widget : this.hovering) { + int i = 0; + while (i < this.hovering.size()) { + LocatedWidget widget = this.hovering.get(i); + if (!widget.getElement().isValid()) { + this.hovering.remove(i); + continue; + } if (debug || widget.getElement().canHover()) { return widget; } + i++; } return null; } @@ -653,19 +661,21 @@ final void setPanelGuiContext(@NotNull ModularGuiContext context) { } public boolean isOpening() { - return this.animator != null && this.animator.isRunningForwards(); + return this.animator != null && this.animator.isAnimatingForward(); } public boolean isClosing() { - return this.animator != null && this.animator.isRunningBackwards(); + return this.animator != null && this.animator.isAnimatingReverse(); } public float getScale() { - return this.scale; + if (!ModularUI.Mods.NEA.isLoaded() || NEAConfig.openingAnimationTime == 0) return 1f; + return Interpolations.lerp(NEAConfig.openingStartScale, 1f, getAnimator().getValue()); } public float getAlpha() { - return this.alpha; + if (!ModularUI.Mods.NEA.isLoaded() || NEAConfig.openingAnimationTime == 0) return 1f; + return getAnimator().getValue(); } public final boolean isMainPanel() { @@ -685,18 +695,23 @@ public void setSyncHandler(@Nullable SyncHandler syncHandler) { @NotNull protected Animator getAnimator() { if (this.animator == null) { - this.animator = new Animator(getScreen().getCurrentTheme().getOpenCloseAnimationOverride(), Interpolation.QUINT_OUT) - .setValueBounds(0.0f, 1.0f) - .setCallback(val -> { - this.alpha = (float) val; - this.scale = (float) val * 0.25f + 0.75f; - }); + this.animator = new Animator() + .bounds(0f, 1f) + .duration(NEAConfig.openingAnimationTime) + .curve(Interpolation.getForName(NEAConfig.openingAnimationCurve.getName())); + this.animator.reset(true); } return this.animator; } + /** + * This method determines if the panel should be animated. + * It is strongly discouraged to override this method. Users should have animations when they have NEA installed, and it's enabled in + * the config. Only override if the animation looks seriously wrong with your specific panel. + */ + @ApiStatus.Internal public boolean shouldAnimate() { - return !getScreen().isOverlay() && getScreen().getCurrentTheme().getOpenCloseAnimationOverride() > 0; + return !getScreen().isOverlay() && ModularUI.Mods.NEA.isLoaded() && NEAConfig.openingAnimationTime > 0; } void registerSubPanel(IPanelHandler handler) { diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index a86dbee4c..e1694b88b 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -172,7 +172,7 @@ public void onResize(int width, int height) { this.context.pushViewport(null, this.context.getScreenArea()); for (ModularPanel panel : this.panelManager.getReverseOpenPanels()) { - WidgetTree.resize(panel); + WidgetTree.resizeInternal(panel, true); } this.context.popViewport(null); @@ -224,7 +224,7 @@ public void close(boolean force) { MCHelper.closeScreen(); return; } - getMainPanel().closeIfOpen(true); + getMainPanel().closeIfOpen(); } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/NEAAnimationHandler.java b/src/main/java/com/cleanroommc/modularui/screen/NEAAnimationHandler.java new file mode 100644 index 000000000..ca0a6a0a4 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/screen/NEAAnimationHandler.java @@ -0,0 +1,124 @@ +package com.cleanroommc.modularui.screen; + +import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.core.mixin.GuiAccessor; +import com.cleanroommc.modularui.core.mixin.GuiScreenAccessor; +import com.cleanroommc.modularui.network.NetworkUtils; +import com.cleanroommc.modularui.widgets.slot.ModularSlot; +import com.cleanroommc.neverenoughanimations.NEA; +import com.cleanroommc.neverenoughanimations.NEAConfig; +import com.cleanroommc.neverenoughanimations.animations.ItemHoverAnimation; +import com.cleanroommc.neverenoughanimations.animations.ItemMoveAnimation; +import com.cleanroommc.neverenoughanimations.animations.ItemMovePacket; +import com.cleanroommc.neverenoughanimations.animations.ItemPickupThrowAnimation; +import com.cleanroommc.neverenoughanimations.api.IItemLocation; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.List; + +public class NEAAnimationHandler { + + public static boolean shouldHandleNEA(ModularContainer container) { + return ModularUI.Mods.NEA.isLoaded() && NetworkUtils.isClient(container.getPlayer()); + } + + public static ItemStack injectQuickMove(ModularContainer container, EntityPlayer player, int slotId, Slot slot) { + if (slot == null || !slot.getHasStack()) { + return ItemStack.EMPTY; + } + ItemStack oldStack = slot.getStack().copy(); + Pair, List> candidates = ItemMoveAnimation.getCandidates(slot, container.inventorySlots); + ItemStack returnable = container.handleQuickMove(player, slotId, slot); + if (candidates != null) ItemMoveAnimation.handleMove(slot, oldStack, candidates); + return returnable; + } + + public static ItemStack pickupAllPre(ModularContainer container) { + if (shouldHandleNEA(container) && NEAConfig.moveAnimationTime > 0) { + return container.getPlayer().inventory.getItemStack().copy(); + } + return null; + } + + public static void pickupAllMid(ModularContainer container, ItemStack instance, int quantity, Int2ObjectArrayMap packets, Slot slot) { + if (shouldHandleNEA(container) && NEAConfig.moveAnimationTime > 0) { + // handle animation + IItemLocation source = IItemLocation.of(slot); + ItemStack movingStack = instance.copy(); + movingStack.setCount(quantity); + packets.put(source.nea$getSlotNumber(), new ItemMovePacket(NEA.time(), source, IItemLocation.CURSOR, movingStack)); + } + // do the redirected action + instance.grow(quantity); + } + + public static void pickupAllPost(ModularContainer container, Int2ObjectArrayMap packets, ItemStack cursor) { + if (shouldHandleNEA(container) && NEAConfig.moveAnimationTime > 0 && !packets.isEmpty()) { + for (var iterator = packets.int2ObjectEntrySet().fastIterator(); iterator.hasNext(); ) { + var e = iterator.next(); + ItemMoveAnimation.queueAnimation(e.getIntKey(), (ItemMovePacket) e.getValue()); + ItemMoveAnimation.updateVirtualStack(-1, cursor, 1); + } + } + } + + public static ItemStack injectVirtualStack(GuiContainer guiContainer, ModularSlot slot) { + if (!slot.isPhantom() && ModularUI.Mods.NEA.isLoaded() && NEAConfig.moveAnimationTime > 0) { + return ItemMoveAnimation.getVirtualStack(guiContainer, slot); + } + return null; + } + + public static float injectHoverScale(GuiContainer guiContainer, ModularSlot slot) { + if (ModularUI.Mods.NEA.isLoaded() && NEAConfig.hoverAnimationTime > 0) { + GlStateManager.pushMatrix(); + float scale = ItemHoverAnimation.getRenderScale(guiContainer, slot); + if (scale > 1f) { + int x = 8; + int y = 8; + GlStateManager.translate(x, y, 0); + GlStateManager.scale(scale, scale, 1); + GlStateManager.translate(-x, -y, 0); + return scale; + } + } + return 1f; + } + + public static void endHoverScale() { + if (ModularUI.Mods.NEA.isLoaded() && NEAConfig.hoverAnimationTime > 0) { + GlStateManager.popMatrix(); + } + } + + public static void drawItemAnimation(GuiContainer container) { + if (ModularUI.Mods.NEA.isLoaded()) { + RenderItem itemRender = ((GuiScreenAccessor) container).getItemRender(); + FontRenderer fontRenderer = ((GuiScreenAccessor) container).getFontRenderer(); + ((GuiAccessor) container).setZLevel(200f); + itemRender.zLevel = 200f; + ItemPickupThrowAnimation.drawIndependentAnimations(container, itemRender, fontRenderer); + ItemMoveAnimation.drawAnimations(itemRender, fontRenderer); + itemRender.zLevel = 0f; + ((GuiAccessor) container).setZLevel(0f); + } + } + + public static ItemStack injectVirtualCursorStack(GuiContainer container, ItemStack stack) { + if (ModularUI.Mods.NEA.isLoaded() && NEAConfig.moveAnimationTime > 0) { + ItemStack virtual = ItemMoveAnimation.getVirtualStack(container, IItemLocation.CURSOR); + return virtual == null ? stack : virtual; + } + return stack; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java b/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java index a2aeb98c2..accf8b2fb 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java +++ b/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java @@ -81,7 +81,7 @@ private void openPanel(ModularPanel panel, boolean resize) { panel.getArea().setPanelLayer((byte) this.panels.size()); panel.onOpen(this.screen); if (resize) { - WidgetTree.resize(panel); + WidgetTree.resizeInternal(panel, true); } } @@ -168,8 +168,8 @@ public void closePanel(@NotNull ModularPanel panel) { } } - public void closeTopPanel(boolean animate) { - getTopMostPanel().closeIfOpen(animate); + public void closeTopPanel() { + getTopMostPanel().closeIfOpen(); } public boolean closeAll() { diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java index 02ef330e3..c2d249174 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java @@ -129,34 +129,47 @@ public void popUntilViewport(IViewport viewport) { updateViewport(true); } + @Override public void translate(float x, float y) { checkViewport(); this.top.getMatrix().translate(x, y); this.top.markDirty(); } + @Override public void translate(float x, float y, float z) { checkViewport(); this.top.getMatrix().translate(vec(x, y, z)); this.top.markDirty(); } + @Override public void rotate(float angle, float x, float y, float z) { checkViewport(); this.top.getMatrix().rotate(angle, vec(x, y, z)); this.top.markDirty(); } + @Override public void rotateZ(float angle) { rotate(angle, 0f, 0f, 1f); } + @Override public void scale(float x, float y) { checkViewport(); this.top.getMatrix().scale(vec(x, y, 1f)); this.top.markDirty(); } + @Override + public void multiply(Matrix4f matrix) { + checkViewport(); + Matrix4f.mul(this.top.getMatrix(), matrix, this.top.getMatrix()); + this.top.markDirty(); + } + + @Override public void resetCurrent() { checkViewport(); Matrix4f belowTop = this.viewportStack.size() > 1 ? this.viewportStack.get(this.viewportStack.size() - 2).getMatrix() : new Matrix4f(); diff --git a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java index b0e589343..2be8b5bf9 100644 --- a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java +++ b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java @@ -22,7 +22,7 @@ public static void onItemUse(PlayerInteractEvent.RightClickItem event) { .screenScale(0.5f) .open(new TestGui());*/ //ClientGUI.open(new ResizerTest()); - ClientGUI.open(new TestGuis()); + ClientGUI.open(new TestGui()); } } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGui.java b/src/main/java/com/cleanroommc/modularui/test/TestGui.java index 1c5480775..a6f41c700 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGui.java @@ -48,7 +48,8 @@ public void onClose() { } final Map> items = new Object2ObjectOpenHashMap<>(); for (String line : this.lines) { - items.put(line, new SortableListWidget.Item<>(line).child(item -> new Row() + items.put(line, new SortableListWidget.Item<>(line) + .child(item -> new Row() .child(new Widget<>() .addTooltipLine(line) .background(GuiTextures.BUTTON_CLEAN) diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java index f4c1ac31d..ac9119331 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java @@ -1,25 +1,33 @@ package com.cleanroommc.modularui.test; import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.animation.Animator; +import com.cleanroommc.modularui.animation.IAnimator; +import com.cleanroommc.modularui.animation.Wait; +import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.drawable.GuiDraw; +import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.drawable.ItemDrawable; import com.cleanroommc.modularui.drawable.SpriteDrawable; import com.cleanroommc.modularui.screen.CustomModularScreen; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; -import com.cleanroommc.modularui.utils.Alignment; -import com.cleanroommc.modularui.utils.GameObjectHelper; -import com.cleanroommc.modularui.utils.SpriteHelper; +import com.cleanroommc.modularui.utils.*; import com.cleanroommc.modularui.utils.fakeworld.ArraySchema; import com.cleanroommc.modularui.utils.fakeworld.ISchema; import com.cleanroommc.modularui.widget.DraggableWidget; import com.cleanroommc.modularui.widgets.RichTextWidget; import com.cleanroommc.modularui.widgets.SchemaWidget; import com.cleanroommc.modularui.widgets.SortableListWidget; - +import com.cleanroommc.modularui.widgets.TransformWidget; +import com.cleanroommc.modularui.widgets.layout.Column; +import com.cleanroommc.modularui.widgets.layout.Row; import com.cleanroommc.modularui.widgets.TextWidget; +import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.init.Blocks; import net.minecraft.init.Items; @@ -31,12 +39,84 @@ import java.util.ArrayList; import java.util.List; +import java.util.Random; +import java.util.stream.IntStream; public class TestGuis extends CustomModularScreen { @Override public @NotNull ModularPanel buildUI(ModularGuiContext context) { - return buildSpriteUI(context); + return buildPostTheLogAnimationUI(context); + } + + public @NotNull ModularPanel buildAnimationUI(ModularGuiContext context) { + IWidget widget = GuiTextures.MUI_LOGO.asWidget().size(20).pos(65, 65); + Animator animator = new Animator() + .bounds(0, 1) + .curve(Interpolation.SINE_INOUT) + .reverseOnFinish(true) + .repeatsOnFinish(-1) + .duration(1200); + + animator.reset(true); + animator.animate(true); + return ModularPanel.defaultPanel("main").size(150) + .child(new TransformWidget() + .child(widget) + .transform(stack -> { + float x = (float) (55 * Math.cos(animator.getValue() * 2 * Math.PI - Math.PI / 2)); + float y = (float) (55 * Math.sin(animator.getValue() * 2 * Math.PI - Math.PI / 2)); + stack.translate(x, y); + })); + } + + public @NotNull ModularPanel buildPostTheLogAnimationUI(ModularGuiContext context) { + Animator post = new Animator().curve(Interpolation.SINE_IN).duration(300).bounds(-35, 0); + Animator the = new Animator().curve(Interpolation.SINE_IN).duration(300).bounds(-20, 0); + Animator fucking = new Animator().curve(Interpolation.SINE_IN).duration(300).bounds(53, 0); + Animator log = new Animator().curve(Interpolation.SINE_IN).duration(300).bounds(20, 0); + Animator logGrow = new Animator().curve(Interpolation.LINEAR).duration(2500).bounds(0f, 1f); + IAnimator animator = new Wait(300) + .followedBy(post) + .followedBy(the) + .followedBy(fucking) + .followedBy(log) + .followedBy(logGrow); + animator.animate(); + Random rnd = new Random(); + TextureAtlasSprite[] sprites = IntStream.range(0, 10).mapToObj(SpriteHelper::getDestroyBlockSprite).toArray(TextureAtlasSprite[]::new); + IDrawable broken = ((context1, x, y, width, height, widgetTheme) -> { + if (logGrow.getValue() < 0.1f) return; + GlStateManager.color(1f, 1f, 1f, 0.75f); + GuiDraw.drawTiledSprite(sprites[(int) Math.min(9, logGrow.getValue() * 10)], x, y, width + 24, height + 24); + }); + return new ModularPanel("main") + .coverChildren() + .padding(12) + .overlay(broken) + .child(new Column() + .coverChildren() + .child(new Row() + .coverChildren() + .child(IKey.str("Post ").asWidget() + .transform((widget, stack) -> stack.translate(post.getValue(), 0))) + .child(IKey.str("the ").asWidget() + .transform((widget, stack) -> stack.translate(0, the.getValue()))) + .child(IKey.str("fucking ").asWidget() + .transform((widget, stack) -> stack.translate(fucking.getValue(), 0)))) + .child(IKey.str("LOOOOGG!!!! ").asWidget() + .paddingTop(4) + .transform((widget, stack) -> { + float logVal = log.getValue(); + float logGrowVal = logGrow.getValue(); + stack.translate(rnd.nextInt(5) * logGrowVal, logVal + rnd.nextInt(5) * logGrowVal); + int x0 = widget.getArea().width / 2, y0 = widget.getArea().height; + float scale = Interpolations.lerp(1f, 3f, logGrowVal); + stack.translate(x0, y0); + stack.scale(scale, scale); + stack.translate(-x0, -y0); + widget.color(Color.interpolate(0xFF040404, Color.RED.main, Math.min(1f, 1.2f * logGrowVal))); + }))); } public @NotNull ModularPanel buildSpriteUI(ModularGuiContext context) { diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index f232383a6..933895652 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -16,6 +16,7 @@ import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.utils.Interpolation; import com.cleanroommc.modularui.value.BoolValue; import com.cleanroommc.modularui.value.IntValue; import com.cleanroommc.modularui.value.StringValue; @@ -116,10 +117,35 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager, .child(new PageButton(1, tabController) .tab(GuiTextures.TAB_TOP, 0)) .child(new PageButton(2, tabController) - .tab(GuiTextures.TAB_TOP, 0)) - .child(new PageButton(3, tabController) - .tab(GuiTextures.TAB_TOP, 0) - .overlay(new ItemDrawable(Blocks.CRAFTING_TABLE).asIcon()))) + .tab(GuiTextures.TAB_TOP, 0))) + .child(new Expandable() + .debugName("expandable") + .top(0) + .leftRelOffset(1f, 1) + .background(GuiTextures.MC_BACKGROUND) + .excludeAreaInJei() + .stencilTransform((r, expanded) -> { + if (expanded) { + r.width -= 5; + r.height -= 5; + } + }) + .animationDuration(500) + .interpolation(Interpolation.BOUNCE_OUT) + .normalView(new ItemDrawable(Blocks.CRAFTING_TABLE).asIcon().asWidget().size(20).pos(0, 0)) + .expandedView(new ParentWidget<>() + .debugName("crafting tab") + .coverChildren() + .child(new ItemDrawable(Blocks.CRAFTING_TABLE).asIcon().asWidget().size(20).pos(0, 0)) + .child(SlotGroupWidget.builder() + .row("III D") + .row("III O") + .row("III ") + .key('I', i -> new ItemSlot().slot(new ModularSlot(this.craftingInventory, i))) + .key('O', new ItemSlot().slot(new ModularCraftingSlot(this.craftingInventory, 9))) + .key('D', new ItemDisplayWidget().syncHandler("display_item").displayAmount(true)) + .build() + .margin(5, 5, 20, 5)))) .child(Flow.column() .sizeRel(1f) .paddingBottom(7) @@ -342,19 +368,7 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager, .size(14, 14)) .child(IKey.lang("bogosort.gui.enabled").asWidget() .height(14))))) - .addPage(new ParentWidget<>() - .debugName("page 4 crafting") - .sizeRel(1f) - .child(SlotGroupWidget.builder() - .row("III D") - .row("III O") - .row("III ") - .key('I', i -> new ItemSlot().slot(new ModularSlot(this.craftingInventory, i))) - .key('O', new ItemSlot().slot(new ModularCraftingSlot(this.craftingInventory, 9))) - .key('D', new ItemDisplayWidget().syncHandler("display_item").displayAmount(true)) - .build() - .center()) - ))) + )) .child(SlotGroupWidget.playerInventory(false)) ); /*panel.child(new ButtonWidget<>() diff --git a/src/main/java/com/cleanroommc/modularui/utils/Animator.java b/src/main/java/com/cleanroommc/modularui/utils/Animator.java deleted file mode 100644 index 23d50b478..000000000 --- a/src/main/java/com/cleanroommc/modularui/utils/Animator.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.cleanroommc.modularui.utils; - -import com.cleanroommc.modularui.api.drawable.IInterpolation; - -import org.jetbrains.annotations.ApiStatus; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.DoubleConsumer; -import java.util.function.DoublePredicate; - -public class Animator { - - private static final List activeAnimators = new ArrayList<>(); - - @ApiStatus.Internal - public static void advance() { - activeAnimators.removeIf(Animator::tick); - } - - private final int duration; - private int progress; - private int dir = 0; - private float min = 0, max = 1; - private float value; - private final IInterpolation interpolation; - private DoublePredicate callback; - private DoubleConsumer endCallback; - - public Animator(int duration, IInterpolation interpolation) { - this.duration = duration; - this.interpolation = interpolation; - } - - public Animator setCallback(DoubleConsumer callback) { - this.callback = val -> { - callback.accept(val); - return false; - }; - return this; - } - - public Animator setCallback(DoublePredicate callback) { - this.callback = callback; - return this; - } - - public Animator setEndCallback(DoubleConsumer endCallback) { - this.endCallback = endCallback; - return this; - } - - public Animator setValueBounds(float min, float max) { - this.min = min; - this.max = max; - return this; - } - - public void forward() { - this.progress = 0; - this.dir = 1; - updateValue(); - if (!activeAnimators.contains(this)) { - activeAnimators.add(this); - } - } - - public void backward() { - this.progress = this.duration; - this.dir = -1; - updateValue(); - if (!activeAnimators.contains(this)) { - activeAnimators.add(this); - } - } - - public boolean isRunning() { - return this.dir != 0 && (this.dir > 0 ? this.progress < this.duration : this.progress > 0); - } - - public boolean isRunningForwards() { - return this.dir > 0 && this.progress < this.duration; - } - - public boolean isRunningBackwards() { - return this.dir < 0 && this.progress > 0; - } - - public double getValue() { - return this.value; - } - - public int getDuration() { - return this.duration; - } - - public int getProgress() { - return this.progress; - } - - public double getMin() { - return this.min; - } - - public double getMax() { - return this.max; - } - - private boolean tick() { - this.progress += this.dir; - updateValue(); - if (this.callback != null && this.callback.test(this.value)) { - this.dir = 0; // stop animation - } - if (!isRunning()) { - if (this.endCallback != null) this.endCallback.accept(this.value); - return true; - } - return false; - } - - private void updateValue() { - this.value = this.interpolation.interpolate(this.min, this.max, this.progress / (float) this.duration); - } -} diff --git a/src/main/java/com/cleanroommc/modularui/utils/Interpolation.java b/src/main/java/com/cleanroommc/modularui/utils/Interpolation.java index 46a3ca829..2b19e0f95 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/Interpolation.java +++ b/src/main/java/com/cleanroommc/modularui/utils/Interpolation.java @@ -314,4 +314,13 @@ public float interpolate(float a, float b, float x) { public @NotNull String getName() { return this.name; } + + public static Interpolation getForName(String name) { + for (Interpolation interpolation : values()) { + if (interpolation.name.equals(name)) { + return interpolation; + } + } + return null; + } } \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/utils/Interpolations.java b/src/main/java/com/cleanroommc/modularui/utils/Interpolations.java index 842232335..fa2327823 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/Interpolations.java +++ b/src/main/java/com/cleanroommc/modularui/utils/Interpolations.java @@ -20,6 +20,10 @@ public static float lerp(float a, float b, float position) { return a + (b - a) * position; } + public static int lerp(int a, int b, float position) { + return (int) (a + (b - a) * position); + } + /** * Special interpolation method for interpolating yaw. The problem with yaw, * is that it may go in the "wrong" direction when having, for example, diff --git a/src/main/java/com/cleanroommc/modularui/utils/ObjectList.java b/src/main/java/com/cleanroommc/modularui/utils/ObjectList.java index 09f3a3422..ba5db3bee 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/ObjectList.java +++ b/src/main/java/com/cleanroommc/modularui/utils/ObjectList.java @@ -79,14 +79,15 @@ static ObjectArrayList of(ObjectIterator i) { @NotNull V[] elements(); + void ensureCapacity(int minCapacity); + class ObjectArrayList extends it.unimi.dsi.fastutil.objects.ObjectArrayList implements ObjectList { public ObjectArrayList(int capacity) { super(capacity); } - public ObjectArrayList() { - } + public ObjectArrayList() {} public ObjectArrayList(Collection c) { super(c); diff --git a/src/main/java/com/cleanroommc/modularui/utils/SpriteHelper.java b/src/main/java/com/cleanroommc/modularui/utils/SpriteHelper.java index a8d685d09..0fbc23a03 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/SpriteHelper.java +++ b/src/main/java/com/cleanroommc/modularui/utils/SpriteHelper.java @@ -36,4 +36,8 @@ public static TextureAtlasSprite getSpriteOfItem(ItemStack item) { public static List getQuadsOfItem(ItemStack item) { return Minecraft.getMinecraft().getRenderItem().getItemModelWithOverrides(item, null, null).getQuads(null, null, 0); } + + public static TextureAtlasSprite getDestroyBlockSprite(int stage) { + return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite("minecraft:blocks/destroy_stage_" + stage); + } } diff --git a/src/main/java/com/cleanroommc/modularui/value/sync/PanelSyncHandler.java b/src/main/java/com/cleanroommc/modularui/value/sync/PanelSyncHandler.java index 81c2d4f04..61b3aae7c 100644 --- a/src/main/java/com/cleanroommc/modularui/value/sync/PanelSyncHandler.java +++ b/src/main/java/com/cleanroommc/modularui/value/sync/PanelSyncHandler.java @@ -91,7 +91,7 @@ private void openInModularSyncManager() { public void closePanel() { if (getSyncManager().isClient()) { if (this.openedPanel != null) { - this.openedPanel.closeIfOpen(true); + this.openedPanel.closeIfOpen(); } } else { syncToClient(2); diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java index ab8008995..55309d197 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java @@ -7,11 +7,10 @@ import com.cleanroommc.modularui.widgets.VoidWidget; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; import java.util.ArrayList; import java.util.List; -import java.util.function.BooleanSupplier; -import java.util.function.Supplier; /** * A widget which can hold any amount of children. @@ -23,12 +22,30 @@ public class AbstractParentWidget children = new ArrayList<>(); + /** + * A list of all children of this widget. The list is modifiable contrary to the annotation. + * This just means that you shouldn't carelessly modify the list. Adding to the list also requires initialising the new child. + * Removing requires disposing the old child. Calling {@link #scheduleResize()} may also be expected. + * + * @return a view of all children. + */ + @SuppressWarnings("unchecked") + @UnmodifiableView @NotNull @Override public List getChildren() { return (List) this.children; } + /** + * A list of all children of this widget with the given children type {@link I}. The list is modifiable contrary to the annotation. + * This just means that you shouldn't carelessly modify the list. Adding to the list also requires initialising the new child. + * Removing requires disposing the old child. Calling {@link #scheduleResize()} may also be expected. + * + * @return a view of all children. + */ + @UnmodifiableView + @NotNull public List getTypeChildren() { return children; } diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java index 1b1853179..c03fe0136 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java @@ -68,6 +68,7 @@ public void getWidgetsAt(IViewportStack stack, HoveredWidgetList widgets, int x, @Override public void onResized() { + super.onResized(); if (this.scroll.getScrollX() != null) { this.scroll.getScrollX().clamp(this.scroll); if (!this.keepScrollBarInArea) { diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java new file mode 100644 index 000000000..6c3e4cdb2 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java @@ -0,0 +1,23 @@ +package com.cleanroommc.modularui.widget; + +public class DelegatingSingleChildWidget> extends SingleChildWidget { + + @Override + public void onInit() { + super.onInit(); + if (hasChildren()) getChild().flex().relative(getParent()); + coverChildren(); + } + + @Override + public void postResize() { + super.postResize(); + if (hasChildren()) { + getArea().set(getChild().getArea()); + getArea().rx = getChild().getArea().rx; + getArea().ry = getChild().getArea().ry; + getChild().getArea().rx = 0; + getChild().getArea().ry = 0; + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java index bcadebb95..c754cc3b5 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java @@ -26,7 +26,7 @@ public DraggableWidget() { @Override public void drawMovingState(ModularGuiContext context, float partialTicks) { - WidgetTree.drawTree(this, context, true); + WidgetTree.drawTree(this, context, true, true); } @Override @@ -50,7 +50,7 @@ public void onDragEnd(boolean successful) { .left(getContext().getAbsMouseX() - this.relativeClickX); this.movingArea.x = getArea().x; this.movingArea.y = getArea().y; - WidgetTree.resize(this); + scheduleResize(); } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java index bdd484138..f1f5e73c7 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java @@ -17,6 +17,7 @@ public class EmptyWidget implements IWidget { private final Area area = new Area(); private final Flex flex = new Flex(this); + private boolean requiresResize = false; private IWidget parent; @Override @@ -40,24 +41,19 @@ public boolean isValid() { } @Override - public void drawBackground(ModularGuiContext context, WidgetTheme widgetTheme) { - } + public void drawBackground(ModularGuiContext context, WidgetTheme widgetTheme) {} @Override - public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { - } + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) {} @Override - public void drawOverlay(ModularGuiContext context, WidgetTheme widgetTheme) { - } + public void drawOverlay(ModularGuiContext context, WidgetTheme widgetTheme) {} @Override - public void drawForeground(ModularGuiContext context) { - } + public void drawForeground(ModularGuiContext context) {} @Override - public void onUpdate() { - } + public void onUpdate() {} @Override public Area getArea() { @@ -75,17 +71,30 @@ public boolean isEnabled() { } @Override - public void setEnabled(boolean enabled) { + public void scheduleResize() { + this.requiresResize = true; + } + + @Override + public boolean requiresResize() { + return this.requiresResize; } + @Override + public void onResized() { + this.requiresResize = false; + } + + @Override + public void setEnabled(boolean enabled) {} + @Override public boolean canBeSeen(IViewportStack stack) { return false; } @Override - public void markTooltipDirty() { - } + public void markTooltipDirty() {} @Override public @NotNull IWidget getParent() { @@ -108,8 +117,7 @@ public Flex flex() { } @Override - public void resizer(IResizeable resizer) { - } + public void resizer(IResizeable resizer) {} @Override public Flex getFlex() { diff --git a/src/main/java/com/cleanroommc/modularui/widget/SingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/SingleChildWidget.java index 41e11f9f1..a90d00af5 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/SingleChildWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/SingleChildWidget.java @@ -12,6 +12,10 @@ public class SingleChildWidget> extends Widget private IWidget child; private List list = Collections.emptyList(); + public IWidget getChild() { + return child; + } + @Override public @NotNull List getChildren() { return this.list; diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index 7376a7b1c..c5b17781a 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -4,6 +4,7 @@ import com.cleanroommc.modularui.api.IThemeApi; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.layout.IResizeable; +import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.value.IValue; import com.cleanroommc.modularui.api.widget.*; import com.cleanroommc.modularui.screen.ModularPanel; @@ -25,6 +26,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; @@ -43,6 +45,7 @@ public class Widget> implements IWidget, IPositioned, ITo // other @Nullable private String debugName; private boolean enabled = true; + private boolean excludeAreaInJei = false; // gui context private boolean valid = false; private IWidget parent = null; @@ -52,6 +55,8 @@ public class Widget> implements IWidget, IPositioned, ITo private final Area area = new Area(); private final Flex flex = new Flex(this); private IResizeable resizer = this.flex; + private BiConsumer transform; + private boolean requiresResize = false; // syncing @Nullable private IValue value; @Nullable private String syncKey; @@ -64,7 +69,7 @@ public class Widget> implements IWidget, IPositioned, ITo @Nullable private RichTooltip tooltip; @Nullable private String widgetThemeOverride = null; // listener - @Nullable private List guiActionListeners; + @Nullable private List guiActionListeners; // TODO replace with proper event system @Nullable private Consumer onUpdateListener; // ----------------- @@ -98,6 +103,9 @@ public final void initialise(@NotNull IWidget parent) { if (!getScreen().isClientOnly()) { initialiseSyncHandler(getScreen().getSyncManager()); } + if (isExcludeAreaInJei()) { + getContext().getJeiSettings().addJeiExclusionArea(this); + } onInit(); if (hasChildren()) { for (IWidget child : getChildren()) { @@ -106,6 +114,7 @@ public final void initialise(@NotNull IWidget parent) { } afterInit(); onUpdate(); + this.requiresResize = false; } /** @@ -152,6 +161,9 @@ public void dispose() { this.context.getScreen().removeGuiActionListener(action); } } + if (isExcludeAreaInJei()) { + getContext().getJeiSettings().removeJeiExclusionArea(this); + } } if (hasChildren()) { for (IWidget child : getChildren()) { @@ -558,6 +570,22 @@ public W setEnabledIf(Predicate condition) { // === Resizing === // ---------------- + @Override + public void scheduleResize() { + this.requiresResize = true; + } + + @Override + public boolean requiresResize() { + return this.requiresResize; + } + + @MustBeInvokedByOverriders + @Override + public void onResized() { + this.requiresResize = false; + } + /** * Returns the area of this widget. This contains information such as position, size, relative position to parent, padding and margin. * Even tho this is a mutable object, you should refrain from modifying the values. @@ -618,6 +646,19 @@ public void resizer(IResizeable resizer) { this.resizer = resizer != null ? resizer : IUnResizeable.INSTANCE; } + @Override + public void transform(IViewportStack stack) { + IWidget.super.transform(stack); + if (this.transform != null) { + this.transform.accept(getThis(), stack); + } + } + + public W transform(BiConsumer transform) { + this.transform = transform; + return getThis(); + } + // ------------------- // === Gui context === // ------------------- @@ -653,7 +694,7 @@ public ModularScreen getScreen() { @Override public @NotNull ModularPanel getPanel() { if (!isValid()) { - throw new IllegalStateException(getClass().getSimpleName() + " is not in a valid state!"); + throw new IllegalStateException(this + " is not in a valid state!"); } return this.panel; } @@ -667,7 +708,7 @@ public ModularScreen getScreen() { @Override public @NotNull IWidget getParent() { if (!isValid()) { - throw new IllegalStateException(getClass().getSimpleName() + " is not in a valid state!"); + throw new IllegalStateException(this + " is not in a valid state!"); } return this.parent; } @@ -681,7 +722,7 @@ public ModularScreen getScreen() { @Override public ModularGuiContext getContext() { if (!isValid()) { - throw new IllegalStateException(getClass().getSimpleName() + " is not in a valid state!"); + throw new IllegalStateException(this + " is not in a valid state!"); } return this.context; } @@ -788,6 +829,10 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } + public boolean isExcludeAreaInJei() { + return this.excludeAreaInJei; + } + /** * Disables the widget from start. Useful inside widget tree creation, where widget references are usually not stored. * @@ -798,6 +843,18 @@ public W disabled() { return getThis(); } + public W excludeAreaInJei() { + return excludeAreaInJei(true); + } + + public W excludeAreaInJei(boolean val) { + this.excludeAreaInJei = val; + if (isValid()) { + getContext().getJeiSettings().addJeiExclusionArea(this); + } + return getThis(); + } + /** * Sets a debug name. This is only used in {@link #toString()}, which is displayed in the mui debug info. Useful for identifying widgets * for debugging. This has no other effect. diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index 2af5a5c87..1ee700bfd 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -8,7 +8,6 @@ import com.cleanroommc.modularui.api.widget.IGuiElement; import com.cleanroommc.modularui.api.widget.ISynced; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.network.NetworkUtils; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; @@ -118,11 +117,14 @@ public static boolean foreachChildReverse(IWidget parent, Predicate con } public static void drawTree(IWidget parent, ModularGuiContext context) { - drawTree(parent, context, false); + drawTree(parent, context, false, true); } - public static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEnabled) { + public static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEnabled, boolean drawBackground) { if (!parent.isEnabled() && !ignoreEnabled) return; + if (parent.requiresResize()) { + resizeInternal(parent, false); + } float alpha = parent.getPanel().getAlpha(); IViewport viewport = parent instanceof IViewport ? (IViewport) parent : null; @@ -143,7 +145,7 @@ public static void drawTree(IWidget parent, ModularGuiContext context, boolean i GlStateManager.color(1f, 1f, 1f, alpha); GlStateManager.enableBlend(); WidgetTheme widgetTheme = parent.getWidgetTheme(context.getTheme()); - parent.drawBackground(context, widgetTheme); + if (drawBackground) parent.drawBackground(context, widgetTheme); parent.draw(context, widgetTheme); parent.drawOverlay(context, widgetTheme); } @@ -174,7 +176,15 @@ public static void drawTree(IWidget parent, ModularGuiContext context, boolean i // render all children if there are any List children = parent.getChildren(); if (!children.isEmpty()) { - children.forEach(widget -> drawTree(widget, context, false)); + boolean backgroundSeparate = children.size() > 1; + // draw all backgrounds first if we have more than 1 child + // the whole reason this exists is because of the hover animation of items with NEA + // on hover the item scales up slightly, this causes the amount text to overlap nearby slots, but since the whole slot is drawn + // at once the backgrounds might draw on top of the text + // for now we'll apply this always without checking for NEA as it might be useful for other things + // maybe proper layer customization in the future? + if (backgroundSeparate) children.forEach(widget -> drawBackground(widget, context, ignoreEnabled)); + children.forEach(widget -> drawTree(widget, context, false, !backgroundSeparate)); } if (viewport != null) { @@ -202,6 +212,36 @@ public static void drawTree(IWidget parent, ModularGuiContext context, boolean i context.popMatrix(); } + public static void drawBackground(IWidget parent, ModularGuiContext context, boolean ignoreEnabled) { + if (!parent.isEnabled() && !ignoreEnabled) return; + + float alpha = parent.getPanel().getAlpha(); + + // transform stack according to the widget + context.pushMatrix(); + parent.transform(context); + + boolean canBeSeen = parent.canBeSeen(context); + if (!canBeSeen) { + context.popMatrix(); + return; + } + + // apply transformations to opengl + GlStateManager.pushMatrix(); + context.applyToOpenGl(); + + // draw widget + GlStateManager.colorMask(true, true, true, true); + GlStateManager.color(1f, 1f, 1f, alpha); + GlStateManager.enableBlend(); + WidgetTheme widgetTheme = parent.getWidgetTheme(context.getTheme()); + parent.drawBackground(context, widgetTheme); + + GlStateManager.popMatrix(); + context.popMatrix(); + } + public static void drawTreeForeground(IWidget parent, ModularGuiContext context) { IViewport viewport = parent instanceof IViewport viewport1 ? viewport1 : null; context.pushMatrix(); @@ -231,11 +271,16 @@ public static void onUpdate(IWidget parent) { }, true); } + @Deprecated public static void resize(IWidget parent) { - if (!NetworkUtils.isClient()) return; + parent.scheduleResize(); + } + + @ApiStatus.Internal + public static void resizeInternal(IWidget parent, boolean onOpen) { // TODO check if widget has a parent which depends on its children // resize each widget and calculate their relative pos - if (!resizeWidget(parent, true) && !resizeWidget(parent, false)) { + if (!resizeWidget(parent, true, onOpen) && !resizeWidget(parent, false, onOpen)) { throw new IllegalStateException("Failed to resize widgets"); } // now apply the calculated pos @@ -246,12 +291,12 @@ public static void resize(IWidget parent) { }, true); } - private static boolean resizeWidget(IWidget widget, boolean init) { + private static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen) { boolean alreadyCalculated = false; // first try to resize this widget IResizeable resizer = widget.resizer(); if (init) { - widget.beforeResize(); + widget.beforeResize(onOpen); resizer.initResizing(); } else { // if this is not the first time check if this widget is already resized @@ -266,7 +311,7 @@ private static boolean resizeWidget(IWidget widget, boolean init) { anotherResize = new ArrayList<>(); for (IWidget child : widget.getChildren()) { if (init && expandAxis != null) child.flex().checkExpanded(expandAxis); - if (!resizeWidget(child, init)) { + if (!resizeWidget(child, init, onOpen)) { anotherResize.add(child); } } @@ -289,7 +334,7 @@ private static boolean resizeWidget(IWidget widget, boolean init) { // now fully resize all children which needs it if (!anotherResize.isEmpty()) { - anotherResize.removeIf(iWidget -> resizeWidget(iWidget, false)); + anotherResize.removeIf(iWidget -> resizeWidget(iWidget, false, onOpen)); } if (result && !alreadyCalculated) widget.onResized(); diff --git a/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollData.java b/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollData.java index 7ba8ebcd4..51508cdb6 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollData.java +++ b/src/main/java/com/cleanroommc/modularui/widget/scroll/ScrollData.java @@ -1,8 +1,8 @@ package com.cleanroommc.modularui.widget.scroll; +import com.cleanroommc.modularui.animation.Animator; import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.drawable.GuiDraw; -import com.cleanroommc.modularui.utils.Animator; import com.cleanroommc.modularui.utils.Interpolation; import net.minecraft.util.math.MathHelper; @@ -62,7 +62,9 @@ public static ScrollData of(GuiAxis axis, boolean axisStart, int thickness) { protected int clickOffset; private int animatingTo = 0; - private final Animator scrollAnimator = new Animator(30, Interpolation.QUAD_OUT); + private final Animator scrollAnimator = new Animator() + .duration(500) + .curve(Interpolation.QUAD_OUT); protected ScrollData(GuiAxis axis, boolean axisStart, int thickness) { this.axis = axis; @@ -199,11 +201,13 @@ public boolean scrollTo(ScrollArea area, int x) { } public void animateTo(ScrollArea area, int x) { - this.scrollAnimator.setCallback(value -> { - return scrollTo(area, (int) value); // stop animation once an edge is hit + this.scrollAnimator.bounds(this.scroll, x).onUpdate(value -> { + if (scrollTo(area, (int) value)) { + this.scrollAnimator.stop(false); // stop animation once an edge is hit + } }); - this.scrollAnimator.setValueBounds(this.scroll, x); - this.scrollAnimator.forward(); + this.scrollAnimator.reset(); + this.scrollAnimator.animate(); this.animatingTo = x; } @@ -236,7 +240,7 @@ public int getScrollBarLength(ScrollArea area) { public abstract boolean isInsideScrollbarArea(ScrollArea area, int x, int y); public boolean isAnimating() { - return this.scrollAnimator.isRunning(); + return this.scrollAnimator.isAnimating(); } public int getAnimationDirection() { diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java index 8294705ca..1326c04bd 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java @@ -1,18 +1,21 @@ package com.cleanroommc.modularui.widget.sizer; +import com.cleanroommc.modularui.animation.IAnimatable; import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.utils.Interpolations; import com.cleanroommc.modularui.utils.MathUtils; import java.awt.*; import java.awt.geom.Rectangle2D; +import java.util.Objects; /** * A rectangular widget area, composed of a position and a size. * Also has fields for a relative position, a layer and margin & padding. */ -public class Area extends Rectangle implements IUnResizeable { +public class Area extends Rectangle implements IUnResizeable, IAnimatable { public static boolean isInside(int x, int y, int w, int h, int px, int py) { SHARED.set(x, y, w, h); @@ -48,6 +51,16 @@ public Area(Rectangle rectangle) { super(rectangle); } + public Area(Area area) { + super(area); + this.rx = area.rx; + this.ry = area.ry; + this.panelLayer = area.panelLayer; + this.z = area.z; + this.margin.set(area.margin); + this.padding.set(area.padding); + } + public int x() { return this.x; } @@ -471,7 +484,7 @@ public Area getArea() { } /** - * This creates a copy, but it only copies position and size. + * This creates a copy with size, pos, margin padding and z layer. * * @return copy */ @@ -488,4 +501,44 @@ public String toString() { ", height=" + this.height + '}'; } + + @Override + public Area interpolate(Area start, Area end, float t) { + this.x = Interpolations.lerp(start.x, end.x, t); + this.y = Interpolations.lerp(start.y, end.y, t); + this.width = Interpolations.lerp(start.width, end.width, t); + this.height = Interpolations.lerp(start.height, end.height, t); + this.rx = Interpolations.lerp(start.rx, end.rx, t); + this.ry = Interpolations.lerp(start.ry, end.ry, t); + this.margin.interpolate(start.margin, end.margin, t); + this.padding.interpolate(start.padding, end.padding, t); + return this; + } + + @Override + public Area copyOrImmutable() { + return createCopy(); + } + + @Override + public boolean shouldAnimate(Area target) { + return x != target.x || y != target.y || width != target.width || height != target.height || + rx != target.rx || ry != target.ry || !margin.isEqual(target.margin) || !padding.isEqual(target.padding); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Area area = (Area) o; + return rx == area.rx && ry == area.ry && panelLayer == area.panelLayer && z == area.z && Objects.equals(margin, + area.margin) && Objects.equals( + padding, area.padding); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), rx, ry, panelLayer, z, margin, padding); + } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java index 125132777..7a86ba86c 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Box.java @@ -1,12 +1,16 @@ package com.cleanroommc.modularui.widget.sizer; +import com.cleanroommc.modularui.animation.IAnimatable; import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.utils.Interpolations; + +import java.util.Objects; /** * A box with four edges. * Used for margins and paddings. */ -public class Box { +public class Box implements IAnimatable { public static final Box SHARED = new Box(); @@ -77,6 +81,20 @@ public int getEnd(GuiAxis axis) { return axis.isHorizontal() ? this.right : this.bottom; } + @Override + public Box interpolate(Box start, Box end, float t) { + this.left = Interpolations.lerp(start.left, end.left, t); + this.top = Interpolations.lerp(start.top, end.top, t); + this.right = Interpolations.lerp(start.right, end.right, t); + this.bottom = Interpolations.lerp(start.bottom, end.bottom, t); + return this; + } + + @Override + public Box copyOrImmutable() { + return new Box().set(this); + } + @Override public String toString() { return "Box{" + @@ -86,4 +104,21 @@ public String toString() { ", bottom=" + bottom + '}'; } + + public boolean isEqual(Box box) { + return left == box.left && top == box.top && right == box.right && bottom == box.bottom; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + return isEqual((Box) o); + } + + + @Override + public int hashCode() { + return Objects.hash(left, top, right, bottom); + } } \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java index c5bb767e0..7c580a019 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java @@ -53,6 +53,16 @@ public Area getArea() { return this.parent.getArea(); } + @Override + public boolean requiresResize() { + return this.parent.requiresResize(); + } + + @Override + public void scheduleResize() { + this.parent.scheduleResize(); + } + @Override public boolean isXCalculated() { return this.x.isPosCalculated(); @@ -75,43 +85,51 @@ public boolean isHeightCalculated() { public Flex coverChildrenWidth() { this.x.setCoverChildren(true, this.parent); + scheduleResize(); return this; } public Flex coverChildrenHeight() { this.y.setCoverChildren(true, this.parent); + scheduleResize(); return this; } public Flex cancelMovementX() { this.x.setCancelAutoMovement(true); + scheduleResize(); return this; } public Flex cancelMovementY() { this.y.setCancelAutoMovement(true); + scheduleResize(); return this; } public Flex expanded() { this.expanded = true; + scheduleResize(); return this; } public Flex relative(Area guiElement) { this.relativeTo = guiElement; this.relativeToParent = false; + scheduleResize(); return this; } public Flex relativeToScreen() { this.relativeTo = null; this.relativeToParent = false; + scheduleResize(); return this; } public Flex relativeToParent() { this.relativeToParent = true; + scheduleResize(); return this; } @@ -161,6 +179,7 @@ private Flex unit(Unit u, float val, int offset, float anchor, Unit.Measure meas u.setOffset(offset); u.setAnchor(anchor); u.setAutoAnchor(autoAnchor); + scheduleResize(); return this; } @@ -170,6 +189,7 @@ private Flex unit(Unit u, DoubleSupplier val, int offset, float anchor, Unit.Mea u.setOffset(offset); u.setAnchor(anchor); u.setAutoAnchor(autoAnchor); + scheduleResize(); return this; } @@ -192,50 +212,54 @@ public Flex height(DoubleSupplier h, Unit.Measure measure) { private Flex unitSize(Unit u, float val, Unit.Measure measure) { u.setValue(val); u.setMeasure(measure); + scheduleResize(); return this; } private Flex unitSize(Unit u, DoubleSupplier val, Unit.Measure measure) { u.setValue(val); u.setMeasure(measure); + scheduleResize(); return this; } public Flex anchorLeft(float val) { getLeft().setAnchor(val); getLeft().setAutoAnchor(false); + scheduleResize(); return this; } public Flex anchorRight(float val) { getRight().setAnchor(1 - val); getRight().setAutoAnchor(false); + scheduleResize(); return this; } public Flex anchorTop(float val) { getTop().setAnchor(val); getTop().setAutoAnchor(false); + scheduleResize(); return this; } public Flex anchorBottom(float val) { getBottom().setAnchor(1 - val); getBottom().setAutoAnchor(false); + scheduleResize(); return this; } public Flex anchor(Alignment alignment) { - if (this.x.hasStart()) { + if (this.x.hasStart() || !this.x.hasEnd()) { anchorLeft(alignment.x); - } - if (this.x.hasEnd()) { + } else if (this.x.hasEnd()) { anchorRight(alignment.x); } - if (this.y.hasStart()) { + if (this.y.hasStart() || !this.y.hasEnd()) { anchorTop(alignment.y); - } - if (this.y.hasEnd()) { + } else if (this.y.hasEnd()) { anchorBottom(alignment.y); } return this; @@ -484,8 +508,11 @@ public void applyPos(IGuiElement parent) { if (parent instanceof IVanillaSlot vanillaSlot) { // special treatment for minecraft slots Slot slot = vanillaSlot.getVanillaSlot(); - slot.xPos = parent.getArea().x; - slot.yPos = parent.getArea().y; + Area mainArea = parent.getScreen().getMainPanel().getArea(); + // in vanilla uis the position is relative to the gui area and size is 16 x 16 + // since our slots are 18 x 18 we need to offset by 1 + slot.xPos = parent.getArea().x - mainArea.x + 1; + slot.yPos = parent.getArea().y - mainArea.y + 1; } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Unit.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Unit.java index e8bbaffb4..fde8b74eb 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Unit.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Unit.java @@ -42,8 +42,7 @@ public String getText(GuiAxis axis) { public State state = State.UNUSED; - public Unit() { - } + public Unit() {} public void reset() { this.state = State.UNUSED; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ButtonWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ButtonWidget.java index c09cc2ceb..59036bf42 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ButtonWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ButtonWidget.java @@ -20,7 +20,7 @@ public static ButtonWidget panelCloseButton() { .size(10).top(4).right(4) .onMousePressed(mouseButton -> { if (mouseButton == 0 || mouseButton == 1) { - buttonWidget.getPanel().closeIfOpen(true); + buttonWidget.getPanel().closeIfOpen(); return true; } return false; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/Expandable.java b/src/main/java/com/cleanroommc/modularui/widgets/Expandable.java new file mode 100644 index 000000000..4df6f48c5 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/Expandable.java @@ -0,0 +1,188 @@ +package com.cleanroommc.modularui.widgets; + +import com.cleanroommc.modularui.animation.Animator; +import com.cleanroommc.modularui.animation.MutableObjectAnimator; +import com.cleanroommc.modularui.api.drawable.IInterpolation; +import com.cleanroommc.modularui.api.layout.IViewport; +import com.cleanroommc.modularui.api.layout.IViewportStack; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.drawable.Stencil; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.utils.HoveredWidgetList; +import com.cleanroommc.modularui.utils.Interpolation; +import com.cleanroommc.modularui.widget.EmptyWidget; +import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widget.sizer.Area; + +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; + +public class Expandable extends Widget implements Interactable, IViewport { + + private IWidget normalView = new EmptyWidget(); + private IWidget expandedView = new EmptyWidget(); + private final List children = Arrays.asList(normalView, expandedView); + private List currentChildren = children; + private boolean expanded = false; + private Area areaSnapshot; + private Animator animator; + private BiConsumer stencilTransform; + private int animationDuration = 300; + private IInterpolation interpolation = Interpolation.SINE_OUT; + + public Expandable() { + coverChildren(); + } + + @Override + public void onInit() { + this.children.set(0, normalView); + this.children.set(1, expandedView); + this.normalView.setEnabled(!this.expanded); + this.expandedView.setEnabled(this.expanded); + } + + @Override + public void beforeResize(boolean onOpen) { + super.beforeResize(onOpen); + this.currentChildren = Collections.singletonList(this.expanded ? this.expandedView : this.normalView); + } + + @Override + public void onResized() { + super.onResized(); + currentChildren = children; + } + + @Override + public void postResize() { + super.postResize(); + if (this.animator != null) { + this.animator.stop(true); + this.animator = null; + } + if (this.areaSnapshot != null) { + if (this.animationDuration <= 0) { + if (!this.expanded) { + this.normalView.setEnabled(true); + this.expandedView.setEnabled(false); + } + } else { + this.animator = new MutableObjectAnimator<>(getArea(), this.areaSnapshot, getArea().copyOrImmutable()) + .duration(this.animationDuration) + .curve(this.interpolation) + .onFinish(() -> { + if (!this.expanded) { + this.normalView.setEnabled(true); + this.expandedView.setEnabled(false); + } + }); + this.animator.animate(); + } + this.areaSnapshot = null; + } + } + + @Override + @NotNull + public List getChildren() { + return currentChildren; + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + toggle(); + return Result.SUCCESS; + } + + public void toggle() { + expanded(!expanded); + } + + @Override + public void preDraw(ModularGuiContext context, boolean transformed) { + if (!transformed) { + Rectangle rect = new Rectangle(getArea()); + rect.x = 0; + rect.y = 0; + if (this.stencilTransform != null) { + this.stencilTransform.accept(rect, this.expanded); + } + Stencil.apply(rect, context); + } + } + + @Override + public void postDraw(ModularGuiContext context, boolean transformed) { + if (!transformed) { + Stencil.remove(); + } + } + + @Override + public void getSelfAt(IViewportStack stack, HoveredWidgetList widgets, int x, int y) { + if (isInside(stack, x, y)) { + widgets.add(this, stack.peek()); + } + } + + @Override + public void getWidgetsAt(IViewportStack stack, HoveredWidgetList widgets, int x, int y) { + if (hasChildren()) { + IViewport.getChildrenAt(this, stack, widgets, x, y); + } + } + + public Expandable expanded(boolean expanded) { + if (this.expanded == expanded) return this; + this.expanded = expanded; + if (expanded) { + this.normalView.setEnabled(false); + this.expandedView.setEnabled(true); + } + if (isValid()) { + this.areaSnapshot = getArea().copyOrImmutable(); + scheduleResize(); + } + return this; + } + + public Expandable normalView(IWidget normalView) { + this.normalView = normalView; + this.children.set(0, normalView); + if (isValid()) { + this.normalView.initialise(this); + } + return this; + } + + public Expandable expandedView(IWidget expandedView) { + this.expandedView = expandedView; + this.children.set(1, expandedView); + if (isValid()) { + this.expandedView.initialise(this); + } + return this; + } + + public Expandable stencilTransform(BiConsumer stencilTransform) { + this.stencilTransform = stencilTransform; + return this; + } + + public Expandable animationDuration(int animationDuration) { + this.animationDuration = animationDuration; + return this; + } + + public Expandable interpolation(IInterpolation interpolation) { + this.interpolation = interpolation; + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java index ae4088daf..f1497bec7 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java @@ -99,10 +99,21 @@ public boolean addChild(I child, int index) { return super.addChild(child, index); } + @Override + public boolean remove(I child) { + return super.remove(child); + } + + @Override + public boolean remove(int index) { + return super.remove(index); + } + @Override public void onChildAdd(I child) { super.onChildAdd(child); if (isValid()) { + scheduleResize(); this.scrollData.clamp(getScrollArea()); } } @@ -111,6 +122,7 @@ public void onChildAdd(I child) { public void onChildRemove(I child) { super.onChildRemove(child); if (isValid()) { + scheduleResize(); this.scrollData.clamp(getScrollArea()); } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ProgressWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ProgressWidget.java index 2461ba204..6ef932d3b 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ProgressWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ProgressWidget.java @@ -44,6 +44,7 @@ public boolean isValidSyncHandler(SyncHandler syncHandler) { @Override public void onResized() { + super.onResized(); if (this.imageSize < 0) { this.imageSize = getArea().width; } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java index c2ec27d16..cc0d1a333 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java @@ -95,6 +95,7 @@ public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { @Override public void onResized() { + super.onResized(); float sw = this.sliderWidth.getValue(); if (this.sliderWidth.isRelative()) sw *= getArea().width; float sh = this.sliderHeight.getValue(); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SortButtons.java b/src/main/java/com/cleanroommc/modularui/widgets/SortButtons.java index 04bad1072..ef9af5cb1 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SortButtons.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SortButtons.java @@ -58,7 +58,7 @@ public List getChildren() { @Override public boolean isEnabled() { - return super.isEnabled() && ModularUI.isSortModLoaded(); + return super.isEnabled() && ModularUI.Mods.BOGOSORTER.isLoaded(); } public String getSlotGroupName() { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java index fb43bb16f..786f241a2 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java @@ -1,15 +1,19 @@ package com.cleanroommc.modularui.widgets; import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.animation.Animator; import com.cleanroommc.modularui.api.widget.IGuiElement; import com.cleanroommc.modularui.api.widget.IValueWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.utils.ObjectList; import com.cleanroommc.modularui.widget.DraggableWidget; import com.cleanroommc.modularui.widget.WidgetTree; +import com.cleanroommc.modularui.widget.sizer.Area; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import java.util.Collections; import java.util.List; @@ -22,6 +26,9 @@ public class SortableListWidget extends ListValueWidget> onChange; private Consumer> onRemove; private int timeSinceLastMove = 0; + private boolean scheduleAnimation = false; + private final ObjectList widgetAreaSnapshots = ObjectList.create(); + private final ObjectList animators = ObjectList.create(); public SortableListWidget() { super(Item::getWidgetValue); @@ -40,6 +47,39 @@ public void onUpdate() { this.timeSinceLastMove++; } + @Override + public void beforeResize(boolean onOpen) { + super.beforeResize(onOpen); + if (this.scheduleAnimation) { + this.widgetAreaSnapshots.clear(); + this.widgetAreaSnapshots.size(getTypeChildren().size()); + this.animators.size(getTypeChildren().size()); + @UnmodifiableView @NotNull List> typeChildren = getTypeChildren(); + for (int i = 0; i < typeChildren.size(); i++) { + Item item = typeChildren.get(i); + this.widgetAreaSnapshots.set(i, item.getArea().copyOrImmutable()); + } + } + } + + @Override + public void postResize() { + if (this.scheduleAnimation && !this.widgetAreaSnapshots.isEmpty()) { + @UnmodifiableView @NotNull List> typeChildren = getTypeChildren(); + for (int i = 0; i < typeChildren.size(); i++) { + Item item = typeChildren.get(i); + Animator current = this.animators.get(i); + if ((current != null && current.isAnimating()) || item.getArea().shouldAnimate(this.widgetAreaSnapshots.get(i))) { + if (current != null) current.stop(true); + Animator animator = item.getArea().animator(this.widgetAreaSnapshots.get(i)).duration(150); + this.animators.set(i, animator); + animator.animate(true); + } + } + } + this.scheduleAnimation = false; + } + @Override public int getDefaultWidth() { return 80; @@ -53,9 +93,10 @@ public void moveTo(int from, int to) { } SortableListWidget.Item child = getTypeChildren().remove(from); getChildren().add(to, child); - assignIndexes(); if (isValid()) { - WidgetTree.resize(this); + assignIndexes(); + this.scheduleAnimation = true; + scheduleResize(); } if (this.onChange != null) { this.onChange.accept(getValues()); @@ -67,11 +108,10 @@ public void moveTo(int from, int to) { public boolean remove(int index) { Item widget = getTypeChildren().remove(index); if (widget != null) { - onChildRemove(widget); + widget.dispose(); assignIndexes(); - if (isValid()) { - WidgetTree.resize(this); - } + this.scheduleAnimation = true; + onChildRemove(widget); if (this.onChange != null) { this.onChange.accept(getValues()); } @@ -87,8 +127,9 @@ public boolean remove(int index) { public void onChildAdd(Item child) { if (isValid()) { assignIndexes(); + this.scheduleAnimation = true; if (this.onChange != null) this.onChange.accept(getValues()); - WidgetTree.resize(this); + scheduleResize(); } } @@ -116,6 +157,7 @@ public static class Item extends DraggableWidget> implements IValueWi private Predicate dropPredicate; private SortableListWidget listWidget; private int index = -1; + private int movingFrom = -1; public Item(T value) { this.value = value; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/TransformWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/TransformWidget.java new file mode 100644 index 000000000..07834570a --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/TransformWidget.java @@ -0,0 +1,59 @@ +package com.cleanroommc.modularui.widgets; + +import com.cleanroommc.modularui.api.layout.IViewportStack; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.utils.Matrix4f; +import com.cleanroommc.modularui.utils.Vector3f; +import com.cleanroommc.modularui.widget.DelegatingSingleChildWidget; + +import java.util.function.Consumer; + +public class TransformWidget extends DelegatingSingleChildWidget { + + private static final Vector3f sharedVec = new Vector3f(); + + private final Matrix4f constTransform = new Matrix4f(); + private boolean hasConstTransform = false; + private Consumer transform; + + public TransformWidget() {} + + public TransformWidget(IWidget child) { + child(child); + } + + @Override + public void transform(IViewportStack stack) { + super.transform(stack); + if (this.hasConstTransform) stack.multiply(this.constTransform); + if (this.transform != null) this.transform.accept(stack); + } + + public TransformWidget transform(Consumer transform) { + this.transform = transform; + return this; + } + + public TransformWidget translate(float x, float y) { + this.hasConstTransform = true; + this.constTransform.translate(x, y); + return this; + } + + public TransformWidget rotate(float angle, float x, float y, float z) { + this.hasConstTransform = true; + this.constTransform.rotate(angle, vec(x, y, z)); + return this; + } + + public TransformWidget scale(float x, float y) { + this.hasConstTransform = true; + this.constTransform.scale(vec(x, y, 1)); + return this; + } + + private static Vector3f vec(float x, float y, float z) { + sharedVec.set(x, y, z); + return sharedVec; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/slot/ItemSlot.java b/src/main/java/com/cleanroommc/modularui/widgets/slot/ItemSlot.java index a812cdbaa..31a46af4f 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/slot/ItemSlot.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/slot/ItemSlot.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.widgets.slot; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.widget.IVanillaSlot; import com.cleanroommc.modularui.api.widget.Interactable; @@ -9,6 +10,7 @@ import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.integration.jei.JeiIngredientProvider; import com.cleanroommc.modularui.screen.ClientScreenHandler; +import com.cleanroommc.modularui.screen.NEAAnimationHandler; import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetSlotTheme; @@ -16,6 +18,7 @@ import com.cleanroommc.modularui.value.sync.ItemSlotSH; import com.cleanroommc.modularui.value.sync.SyncHandler; import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.neverenoughanimations.NEAConfig; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.inventory.GuiContainer; @@ -86,7 +89,7 @@ public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { } protected void drawOverlay() { - if (isHovering()) { + if (isHovering() && (!ModularUI.Mods.NEA.isLoaded() || NEAConfig.itemHoverOverlay)) { GlStateManager.colorMask(true, true, true, false); GuiDraw.drawRect(1, 1, 16, 16, getSlotHoverColor()); GlStateManager.colorMask(true, true, true, true); @@ -169,9 +172,9 @@ public ItemSlot slot(IItemHandlerModifiable itemHandler, int index) { } @SideOnly(Side.CLIENT) - private void drawSlot(Slot slotIn) { + private void drawSlot(ModularSlot slotIn) { GuiScreen guiScreen = getScreen().getScreenWrapper().getGuiScreen(); - if (!(guiScreen instanceof GuiContainer)) + if (!(guiScreen instanceof GuiContainer guiContainer)) throw new IllegalStateException("The gui must be an instance of GuiContainer if it contains slots!"); GuiContainerAccessor acc = (GuiContainerAccessor) guiScreen; RenderItem renderItem = ((GuiScreenAccessor) guiScreen).getItemRender(); @@ -219,10 +222,18 @@ private void drawSlot(Slot slotIn) { GuiDraw.drawRect(1, 1, 16, 16, -2130706433); } + ItemStack virtualStack = NEAAnimationHandler.injectVirtualStack(guiContainer, slotIn); + if (virtualStack != null) { + itemstack = virtualStack; + } + if (!itemstack.isEmpty()) { GlStateManager.enableDepth(); + float itemScale = NEAAnimationHandler.injectHoverScale(guiContainer, slotIn); // render the item itself renderItem.renderItemAndEffectIntoGUI(guiScreen.mc.player, itemstack, 1, 1); + // TODO render item borders from item borders mod here + if (amount < 0) { amount = itemstack.getCount(); } @@ -232,6 +243,7 @@ private void drawSlot(Slot slotIn) { itemstack.setCount(1); // required to not render the amount overlay // render other overlays like durability bar renderItem.renderItemOverlayIntoGUI(((GuiScreenAccessor) guiScreen).getFontRenderer(), itemstack, 1, 1, null); + NEAAnimationHandler.endHoverScale(); itemstack.setCount(cachedCount); GlStateManager.disableDepth(); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/slot/PhantomItemSlot.java b/src/main/java/com/cleanroommc/modularui/widgets/slot/PhantomItemSlot.java index 588c43946..99e5610f7 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/slot/PhantomItemSlot.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/slot/PhantomItemSlot.java @@ -33,7 +33,7 @@ public boolean isValidSyncHandler(SyncHandler syncHandler) { @Override protected void drawOverlay() { - if (ModularUI.isJeiLoaded() && (ModularUIJeiPlugin.hasDraggingGhostIngredient() || ModularUIJeiPlugin.hoveringOverIngredient(this))) { + if (ModularUI.Mods.JEI.isLoaded() && (ModularUIJeiPlugin.hasDraggingGhostIngredient() || ModularUIJeiPlugin.hoveringOverIngredient(this))) { GlStateManager.colorMask(true, true, true, false); drawHighlight(getArea(), isHovering()); GlStateManager.colorMask(true, true, true, true);