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; package speiger.src.coreengine.rendering.gui.animation;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiConsumer;
import speiger.src.collections.objects.functions.consumer.ObjectFloatConsumer; 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.impl.misc.LinkedEnum2ObjectMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap; import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap.Entry; 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 { public class GuiAnimation {
Object2ObjectMap<Target, IAction> actions; Object2ObjectMap<Target, IAction> actions;
BiConsumer<GuiAnimation, GuiComponent> listener;
float duration; 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.actions = actions;
this.listener = listener;
this.duration = duration; this.duration = duration;
} }
public Builder copy() { return new Builder(this); } public Builder copy() { return new Builder(this); }
public static Builder of() { return new Builder(); } 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 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)) { for(Entry<Target, IAction> entry : Object2ObjectMaps.fastIterable(actions)) {
IAction action = entry.getValue(); 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 { public static class Builder {
Object2ObjectMap<Target, IAction> actions = new LinkedEnum2ObjectMap<>(Target.class); Object2ObjectMap<Target, IAction> actions = new LinkedEnum2ObjectMap<>(Target.class);
BiConsumer<GuiAnimation, GuiComponent> listener;
private Builder() {} private Builder() {}
private Builder(GuiAnimation owner) { private Builder(GuiAnimation owner) {
@ -43,31 +54,34 @@ public class GuiAnimation {
return this; return this;
} }
public Builder withListener(BiConsumer<GuiAnimation, GuiComponent> listener) {
this.listener = listener;
return this;
}
public GuiAnimation build() { public GuiAnimation build() {
float duration = 0F; float duration = 0F;
actions.values().mapToFloat(IAction::duration).reduce(Math::max);
for(IAction action : actions.values()) { for(IAction action : actions.values()) {
duration = Math.max(duration, action.duration()); duration = Math.max(duration, action.duration());
} }
return new GuiAnimation(actions, duration); return new GuiAnimation(actions, listener, duration);
} }
} }
public static enum Target { public static enum Target {
X(IGuiBox::setX), X(IGuiBox::getBaseX),
Y(IGuiBox::setY), Y(IGuiBox::getBaseY),
WIDTH(IGuiBox::setWidth), WIDTH(IGuiBox::getBaseWidth),
HEIGHT(IGuiBox::setHeight), HEIGHT(IGuiBox::getBaseHeight),
SCALE(IGuiBox::setScale); SCALE(IGuiBox::getBaseScale);
ObjectFloatConsumer<IGuiBox> consumer; ToFloatFunction<IGuiBox> provider;
private Target(ObjectFloatConsumer<IGuiBox> consumer) { private Target(ToFloatFunction<IGuiBox> provider) {
this.consumer = consumer; this.provider = provider;
} }
public void set(GuiComponent component, float value) { public int changeState() { return this == X || this == Y ? 1 : 2; }
consumer.accept(component.getBox(), value); 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; 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; 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.animation.GuiAnimation.Target;
import speiger.src.coreengine.rendering.gui.components.base.GuiComponent;
public interface IAction { public interface IAction {
public float duration(); 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 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 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); } public default IAction reverse() { return this instanceof ReversedAction reversed ? reversed.action() : new ReversedAction(this); }
@ -14,8 +15,8 @@ public interface IAction {
@Override @Override
public float duration() { return delay + action.duration(); } public float duration() { return delay + action.duration(); }
@Override @Override
public void apply(Target target, GuiComponent component, float progress) { public void apply(Target target, ToFloatFunction<Target> getter, ObjectFloatConsumer<Target> setter, float progress) {
action.apply(target, component, Math.max(progress - delay, 0F)); action.apply(target, getter, setter, Math.max(progress - delay, 0F));
} }
} }
@ -23,8 +24,8 @@ public interface IAction {
@Override @Override
public float duration() { return action.duration() + delay; } public float duration() { return action.duration() + delay; }
@Override @Override
public void apply(Target target, GuiComponent component, float progress) { public void apply(Target target, ToFloatFunction<Target> getter, ObjectFloatConsumer<Target> setter, float progress) {
action.apply(target, component, Math.min(progress, action.duration())); action.apply(target, getter, setter, Math.min(progress, action.duration()));
} }
} }
@ -32,8 +33,8 @@ public interface IAction {
@Override @Override
public float duration() { return action.duration(); } public float duration() { return action.duration(); }
@Override @Override
public void apply(Target target, GuiComponent component, float progress) { public void apply(Target target, ToFloatFunction<Target> getter, ObjectFloatConsumer<Target> setter, float progress) {
action.apply(target, component, action.duration() - 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 java.util.function.Consumer;
import speiger.src.collections.objects.lists.ObjectArrayList; 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.layout.constraints.ConstraintContainer;
import speiger.src.coreengine.rendering.gui.renderer.IUIRenderer; import speiger.src.coreengine.rendering.gui.renderer.IUIRenderer;
import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox; 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; private static final int FLAG_SCISSORED = 1 << 4;
final IGuiBox box; final IGuiBox box;
ConstraintContainer constraints; ConstraintContainer constraints;
Object animator; // TODO implement GuiAnimator animator;
GuiComponent parent; GuiComponent parent;
List<GuiComponent> children = new ObjectArrayList<>(); List<GuiComponent> children = new ObjectArrayList<>();
InteractionContainer interactions = new InteractionContainer(this::isInteractable); InteractionContainer interactions = new InteractionContainer(this::isInteractable);
@ -52,38 +53,44 @@ public abstract non-sealed class GuiComponent extends FlagObject implements ICas
notifyListeners(LISTENER_CLOSED); notifyListeners(LISTENER_CLOSED);
} }
public void updateAnimations(float partialTicks) {
if(animator == null || animator.update(partialTicks)) return;
animator = null;
}
public void updateComponent() { public void updateComponent() {
children.forEach(GuiComponent::updateComponent); children.forEach(GuiComponent::updateComponent);
} }
protected void renderComponent(IUIRenderer renderer, int mouseX, int mouseY, float particalTicks) { protected void renderComponent(IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) {
renderChildren(renderer, mouseX, mouseY, particalTicks); 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) { for(GuiComponent child : children) {
if(child.isManualManaged()) continue; 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) { public static boolean renderComponent(GuiComponent comp, IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) {
if(!comp.isVisible() || (comp.isScissored() && !renderer.isInScissors(comp.getBox()))) return false; if(!comp.isVisible()) return false;
comp.updateAnimations(partialTicks);
if(comp.isScissored()) { if(comp.isScissored()) {
if(!renderer.isInScissors(comp.getBox())) return false;
renderer.pushScissors(comp.getBox()); renderer.pushScissors(comp.getBox());
if(comp.renderer != null) { if(comp.renderer != null) {
comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, particalTicks); comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, partialTicks);
comp.renderChildren(renderer, mouseX, mouseY, particalTicks); comp.renderChildren(renderer, mouseX, mouseY, partialTicks);
} }
else comp.renderComponent(renderer, mouseX, mouseY, particalTicks); else comp.renderComponent(renderer, mouseX, mouseY, partialTicks);
renderer.popScissors(); renderer.popScissors();
} }
else if(comp.renderer != null) { else if(comp.renderer != null) {
comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, particalTicks); comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, partialTicks);
comp.renderChildren(renderer, mouseX, mouseY, particalTicks); comp.renderChildren(renderer, mouseX, mouseY, partialTicks);
} }
else comp.renderComponent(renderer, mouseX, mouseY, particalTicks); else comp.renderComponent(renderer, mouseX, mouseY, partialTicks);
return true; return true;
} }
@ -136,9 +143,7 @@ public abstract non-sealed class GuiComponent extends FlagObject implements ICas
public void onChanged(boolean repaint) { public void onChanged(boolean repaint) {
// TODO implement changes xD // TODO implement changes xD
if(constraints != null) constraints.apply(this, parent); if(constraints != null) constraints.apply(this, parent);
if(animator != null) { if(animator != null) animator.apply();
// TODO implement animations;
}
box.onChanged(); box.onChanged();
notifyListeners(LISTENER_ON_CHANGE); notifyListeners(LISTENER_ON_CHANGE);
if(repaint) repaint(); if(repaint) repaint();