Finished the gui animation system

This commit is contained in:
Speiger 2024-06-04 22:56:58 +02:00
parent 36ce0209fb
commit cab2095668
5 changed files with 217 additions and 41 deletions

View File

@ -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<Target, IAction> actions;
BiConsumer<GuiAnimation, GuiComponent> listener;
float duration;
private GuiAnimation(Object2ObjectMap<Target, IAction> actions, float duration) {
public GuiAnimation(Object2ObjectMap<Target, IAction> actions, BiConsumer<GuiAnimation, GuiComponent> 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<GuiAnimation, GuiComponent> listener() { return listener; }
public float duration() { return duration; }
public void apply(GuiComponent owner, float progress) {
public void apply(ToFloatFunction<Target> getter, ObjectFloatConsumer<Target> setter, float progress) {
for(Entry<Target, IAction> 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<Target, IAction> actions = new LinkedEnum2ObjectMap<>(Target.class);
BiConsumer<GuiAnimation, GuiComponent> listener;
private Builder() {}
private Builder(GuiAnimation owner) {
@ -43,31 +54,34 @@ public class GuiAnimation {
return this;
}
public Builder withListener(BiConsumer<GuiAnimation, GuiComponent> 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<IGuiBox> consumer;
ToFloatFunction<IGuiBox> provider;
private Target(ObjectFloatConsumer<IGuiBox> consumer) {
this.consumer = consumer;
private Target(ToFloatFunction<IGuiBox> 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()); }
}
}

View File

@ -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<Target>, ObjectFloatConsumer<Target> {
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;
}
}

View File

@ -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<Target> {
protected GuiComponent owner;
float xDiff = 0F;
float yDiff = 0F;
float widthDiff = 0F;
float heightDiff = 0F;
float scaleDiff = 1F;
int changeState = 0;
Object2ObjectMap<GuiAnimation, GuiAnimationSnapshot> activeAnimations = Object2ObjectMap.builder().linkedMap();
List<GuiAnimation> 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<GuiAnimation, GuiAnimationSnapshot> 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<m;i++) {
GuiAnimation animation = toDelete.get(i);
activeAnimations.remove(animation);
if(animation.listener() != null) {
animation.listener().accept(animation, owner);
}
}
return activeAnimations.size() > 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();
}
}

View File

@ -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<Target> getter, ObjectFloatConsumer<Target> 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<Target> getter, ObjectFloatConsumer<Target> 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<Target> getter, ObjectFloatConsumer<Target> 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<Target> getter, ObjectFloatConsumer<Target> setter, float progress) {
action.apply(target, getter, setter, action.duration() - progress);
}
}

View File

@ -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<GuiComponent> 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();