From cab209566804d3c44bd7694539d1a3fb849b46d3 Mon Sep 17 00:00:00 2001 From: Speiger Date: Tue, 4 Jun 2024 22:56:58 +0200 Subject: [PATCH] Finished the gui animation system --- .../rendering/gui/animation/GuiAnimation.java | 46 ++++++---- .../gui/animation/GuiAnimationSnapshot.java | 72 ++++++++++++++++ .../rendering/gui/animation/GuiAnimator.java | 86 ++++++++++++++++++- .../rendering/gui/animation/IAction.java | 17 ++-- .../gui/components/base/GuiComponent.java | 37 ++++---- 5 files changed, 217 insertions(+), 41 deletions(-) create mode 100644 src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimationSnapshot.java diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimation.java b/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimation.java index d40fc53..147e9a8 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimation.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimation.java @@ -1,8 +1,10 @@ package speiger.src.coreengine.rendering.gui.animation; import java.util.Objects; +import java.util.function.BiConsumer; import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer; +import speiger.src.collections.objects.functions.function.ToFloatFunction; import speiger.src.collections.objects.maps.impl.misc.LinkedEnum2ObjectMap; import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap; import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap.Entry; @@ -12,26 +14,35 @@ import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox; public class GuiAnimation { Object2ObjectMap actions; + BiConsumer listener; float duration; - private GuiAnimation(Object2ObjectMap actions, float duration) { + public GuiAnimation(Object2ObjectMap actions, BiConsumer listener, float duration) { this.actions = actions; + this.listener = listener; this.duration = duration; } public Builder copy() { return new Builder(this); } public static Builder of() { return new Builder(); } + @Override + public boolean equals(Object obj) { return obj instanceof GuiAnimation animation && animation.actions.equals(actions); } + @Override + public int hashCode() { return actions.hashCode(); } + public BiConsumer listener() { return listener; } public float duration() { return duration; } - public void apply(GuiComponent owner, float progress) { + + public void apply(ToFloatFunction getter, ObjectFloatConsumer setter, float progress) { for(Entry entry : Object2ObjectMaps.fastIterable(actions)) { IAction action = entry.getValue(); - action.apply(entry.getKey(), owner, Math.min(progress, action.duration())); + action.apply(entry.getKey(), getter, setter, Math.min(progress, action.duration())); } } public static class Builder { Object2ObjectMap actions = new LinkedEnum2ObjectMap<>(Target.class); + BiConsumer listener; private Builder() {} private Builder(GuiAnimation owner) { @@ -43,31 +54,34 @@ public class GuiAnimation { return this; } + public Builder withListener(BiConsumer listener) { + this.listener = listener; + return this; + } + public GuiAnimation build() { float duration = 0F; - actions.values().mapToFloat(IAction::duration).reduce(Math::max); for(IAction action : actions.values()) { duration = Math.max(duration, action.duration()); } - return new GuiAnimation(actions, duration); + return new GuiAnimation(actions, listener, duration); } } public static enum Target { - X(IGuiBox::setX), - Y(IGuiBox::setY), - WIDTH(IGuiBox::setWidth), - HEIGHT(IGuiBox::setHeight), - SCALE(IGuiBox::setScale); + X(IGuiBox::getBaseX), + Y(IGuiBox::getBaseY), + WIDTH(IGuiBox::getBaseWidth), + HEIGHT(IGuiBox::getBaseHeight), + SCALE(IGuiBox::getBaseScale); - ObjectFloatConsumer consumer; + ToFloatFunction provider; - private Target(ObjectFloatConsumer consumer) { - this.consumer = consumer; + private Target(ToFloatFunction provider) { + this.provider = provider; } - public void set(GuiComponent component, float value) { - consumer.accept(component.getBox(), value); - } + public int changeState() { return this == X || this == Y ? 1 : 2; } + public float get(GuiComponent component) { return provider.applyAsFloat(component.getBox()); } } } diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimationSnapshot.java b/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimationSnapshot.java new file mode 100644 index 0000000..bfe487a --- /dev/null +++ b/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimationSnapshot.java @@ -0,0 +1,72 @@ +package speiger.src.coreengine.rendering.gui.animation; + +import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer; +import speiger.src.collections.objects.functions.function.ToFloatFunction; +import speiger.src.coreengine.rendering.gui.animation.GuiAnimation.Target; +import speiger.src.coreengine.rendering.gui.components.base.GuiComponent; +import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox; + +public class GuiAnimationSnapshot implements ToFloatFunction, ObjectFloatConsumer { + final GuiAnimator animator; + final float x; + final float y; + final float width; + final float height; + final float scale; + final boolean looping; + float progress; + + public GuiAnimationSnapshot(GuiAnimator animator, IGuiBox box, boolean looping) { + this(animator, box.getBaseX(), box.getBaseY(), box.getBaseWidth(), box.getBaseHeight(), box.getBaseScale(), looping); + } + + public GuiAnimationSnapshot(GuiAnimator animator, float x, float y, float width, float height, float scale, boolean looping) { + this.animator = animator; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.scale = scale; + this.looping = looping; + } + + @Override + public float applyAsFloat(Target k) { + return switch(k) { + case X -> x; + case Y -> y; + case WIDTH -> width; + case HEIGHT -> height; + case SCALE -> scale; + default -> 0F; + }; + } + + @Override + public void accept(Target k, float v) { + switch(k) { + case X -> animator.accept(k, v - x); + case Y -> animator.accept(k, v - y); + case WIDTH -> animator.accept(k, v - width); + case HEIGHT -> animator.accept(k, v - height); + case SCALE -> animator.accept(k, v / scale); + } + } + + public GuiAnimationSnapshot applyDifference(GuiComponent component) { + for(Target target : Target.values()) { + animator.accept(target, applyAsFloat(target) - target.get(component)); + } + return this; + } + + public boolean update(GuiAnimation animation, float partialTime) { + progress += partialTime; + animation.apply(this, this, Math.min(progress, animation.duration())); + if(progress >= animation.duration()) { + if(!looping) return true; + progress = 0F; + } + return false; + } +} diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimator.java b/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimator.java index a067889..a8bb9ea 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimator.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/animation/GuiAnimator.java @@ -1,5 +1,89 @@ package speiger.src.coreengine.rendering.gui.animation; -public class GuiAnimator { +import java.util.List; +import java.util.Map.Entry; + +import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer; +import speiger.src.collections.objects.lists.ObjectArrayList; +import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap; +import speiger.src.collections.objects.utils.maps.Object2ObjectMaps; +import speiger.src.coreengine.rendering.gui.animation.GuiAnimation.Target; +import speiger.src.coreengine.rendering.gui.components.base.GuiComponent; + +public class GuiAnimator implements ObjectFloatConsumer { + protected GuiComponent owner; + float xDiff = 0F; + float yDiff = 0F; + float widthDiff = 0F; + float heightDiff = 0F; + float scaleDiff = 1F; + int changeState = 0; + Object2ObjectMap activeAnimations = Object2ObjectMap.builder().linkedMap(); + List toDelete = new ObjectArrayList<>(); + public GuiAnimator(GuiComponent owner) { + this.owner = owner; + } + + public void addAnimation(GuiAnimation animation, boolean looping) { + activeAnimations.put(animation, new GuiAnimationSnapshot(this, owner.getBox(), looping)); + } + + public boolean isAnimationPlaying(GuiAnimation animation) { + return activeAnimations.containsKey(animation); + } + + public void removeAnimation(GuiAnimation animation) { + activeAnimations.remove(animation); + } + + private void reset() { + if(changeState == 0) return; + owner.getBox().move(-xDiff, -yDiff).grow(-widthDiff, -heightDiff).scale(1F / scaleDiff); + xDiff = 0F; + yDiff = 0F; + widthDiff = 0F; + heightDiff = 0F; + scaleDiff = 1F; + changeState = 0; + + } + + public void apply() { + if(changeState == 0) return; + owner.getBox().move(xDiff, yDiff).grow(widthDiff, heightDiff).scale(scaleDiff); + } + + public boolean update(float partialTime) { + reset(); + for(Entry entry : Object2ObjectMaps.fastIterable(activeAnimations)) { + GuiAnimation animation = entry.getKey(); + if(entry.getValue().applyDifference(owner).update(animation, partialTime)) { + toDelete.add(animation); + } + } + if(changeState > 0) { + owner.onChanged(changeState > 1); + } + for(int i = 0,m=toDelete.size();i 0; + } + + @Override + public void accept(Target k, float v) { + switch(k) { + case X -> xDiff += v; + case Y -> yDiff += v; + case WIDTH -> widthDiff += v; + case HEIGHT -> heightDiff += v; + case SCALE -> scaleDiff *= v; + } + changeState |= k.changeState(); + } } diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/animation/IAction.java b/src/main/java/speiger/src/coreengine/rendering/gui/animation/IAction.java index 0ee0662..e22c434 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/animation/IAction.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/animation/IAction.java @@ -1,11 +1,12 @@ package speiger.src.coreengine.rendering.gui.animation; +import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer; +import speiger.src.collections.objects.functions.function.ToFloatFunction; import speiger.src.coreengine.rendering.gui.animation.GuiAnimation.Target; -import speiger.src.coreengine.rendering.gui.components.base.GuiComponent; public interface IAction { public float duration(); - public void apply(Target target, GuiComponent component, float progress); + public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress); public default IAction withPreDelay(float delay) { return this instanceof PreDelayedAction delayed ? new PreDelayedAction(delayed.action(), delay + delayed.delay()) : new PreDelayedAction(this, delay); } public default IAction withPostDelay(float delay) { return this instanceof PostDelayedAction delayed ? new PostDelayedAction(delayed.action(), delay + delayed.delay()) : new PostDelayedAction(this, delay); } public default IAction reverse() { return this instanceof ReversedAction reversed ? reversed.action() : new ReversedAction(this); } @@ -14,8 +15,8 @@ public interface IAction { @Override public float duration() { return delay + action.duration(); } @Override - public void apply(Target target, GuiComponent component, float progress) { - action.apply(target, component, Math.max(progress - delay, 0F)); + public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress) { + action.apply(target, getter, setter, Math.max(progress - delay, 0F)); } } @@ -23,8 +24,8 @@ public interface IAction { @Override public float duration() { return action.duration() + delay; } @Override - public void apply(Target target, GuiComponent component, float progress) { - action.apply(target, component, Math.min(progress, action.duration())); + public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress) { + action.apply(target, getter, setter, Math.min(progress, action.duration())); } } @@ -32,8 +33,8 @@ public interface IAction { @Override public float duration() { return action.duration(); } @Override - public void apply(Target target, GuiComponent component, float progress) { - action.apply(target, component, action.duration() - progress); + public void apply(Target target, ToFloatFunction getter, ObjectFloatConsumer setter, float progress) { + action.apply(target, getter, setter, action.duration() - progress); } } diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/components/base/GuiComponent.java b/src/main/java/speiger/src/coreengine/rendering/gui/components/base/GuiComponent.java index f5e030e..12abc5d 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/components/base/GuiComponent.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/components/base/GuiComponent.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.function.Consumer; import speiger.src.collections.objects.lists.ObjectArrayList; +import speiger.src.coreengine.rendering.gui.animation.GuiAnimator; import speiger.src.coreengine.rendering.gui.layout.constraints.ConstraintContainer; import speiger.src.coreengine.rendering.gui.renderer.IUIRenderer; import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox; @@ -19,7 +20,7 @@ public abstract non-sealed class GuiComponent extends FlagObject implements ICas private static final int FLAG_SCISSORED = 1 << 4; final IGuiBox box; ConstraintContainer constraints; - Object animator; // TODO implement + GuiAnimator animator; GuiComponent parent; List children = new ObjectArrayList<>(); InteractionContainer interactions = new InteractionContainer(this::isInteractable); @@ -52,38 +53,44 @@ public abstract non-sealed class GuiComponent extends FlagObject implements ICas notifyListeners(LISTENER_CLOSED); } + public void updateAnimations(float partialTicks) { + if(animator == null || animator.update(partialTicks)) return; + animator = null; + } public void updateComponent() { children.forEach(GuiComponent::updateComponent); } - protected void renderComponent(IUIRenderer renderer, int mouseX, int mouseY, float particalTicks) { - renderChildren(renderer, mouseX, mouseY, particalTicks); + protected void renderComponent(IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) { + renderChildren(renderer, mouseX, mouseY, partialTicks); } - protected void renderChildren(IUIRenderer renderer, int mouseX, int mouseY, float particalTicks) { + protected void renderChildren(IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) { for(GuiComponent child : children) { if(child.isManualManaged()) continue; - renderComponent(child, renderer, mouseX, mouseY, particalTicks); + renderComponent(child, renderer, mouseX, mouseY, partialTicks); } } - public static boolean renderComponent(GuiComponent comp, IUIRenderer renderer, int mouseX, int mouseY, float particalTicks) { - if(!comp.isVisible() || (comp.isScissored() && !renderer.isInScissors(comp.getBox()))) return false; + public static boolean renderComponent(GuiComponent comp, IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) { + if(!comp.isVisible()) return false; + comp.updateAnimations(partialTicks); if(comp.isScissored()) { + if(!renderer.isInScissors(comp.getBox())) return false; renderer.pushScissors(comp.getBox()); if(comp.renderer != null) { - comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, particalTicks); - comp.renderChildren(renderer, mouseX, mouseY, particalTicks); + comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, partialTicks); + comp.renderChildren(renderer, mouseX, mouseY, partialTicks); } - else comp.renderComponent(renderer, mouseX, mouseY, particalTicks); + else comp.renderComponent(renderer, mouseX, mouseY, partialTicks); renderer.popScissors(); } else if(comp.renderer != null) { - comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, particalTicks); - comp.renderChildren(renderer, mouseX, mouseY, particalTicks); + comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, partialTicks); + comp.renderChildren(renderer, mouseX, mouseY, partialTicks); } - else comp.renderComponent(renderer, mouseX, mouseY, particalTicks); + else comp.renderComponent(renderer, mouseX, mouseY, partialTicks); return true; } @@ -136,9 +143,7 @@ public abstract non-sealed class GuiComponent extends FlagObject implements ICas public void onChanged(boolean repaint) { // TODO implement changes xD if(constraints != null) constraints.apply(this, parent); - if(animator != null) { - // TODO implement animations; - } + if(animator != null) animator.apply(); box.onChanged(); notifyListeners(LISTENER_ON_CHANGE); if(repaint) repaint();