-
Notifications
You must be signed in to change notification settings - Fork 0
Animations
Two interpolators (Tween and Spring), a wide easing library, ARGB-aware color interpolation, and a render-time wrapper that applies them to any component.
import org.triggersstudio.moddinglib.client.ui.animation.Tween;
import org.triggersstudio.moddinglib.client.ui.animation.Spring;
import org.triggersstudio.moddinglib.client.ui.animation.Easing;
import org.triggersstudio.moddinglib.client.ui.animation.ColorTween;
import org.triggersstudio.moddinglib.client.ui.animation.Direction;Time-based interpolated value implementing DoubleSupplier. The current value is computed lazily from System.nanoTime() on every getAsDouble() call — there's no global ticker. CPU is zero when nothing reads it.
Tween fade = Tween.from(0.0).to(1.0)
.duration(Duration.ofMillis(300))
.easing(Easing.OUT_CUBIC)
.play();Builder methods: to(double), duration(Duration), durationMs(long), easing(DoubleUnaryOperator), loop(), yoyo(), onComplete(Runnable). Finish with .play() (build + start) or .build() (build, leave stopped).
Tween fade = Tween.over(0.0, 1.0, 300, Easing.OUT_CUBIC).play();tween.play() // start (or restart) from time zero
tween.replay() // alias for play()
tween.stop() // reset to start, stop the tween
tween.isDone() // true when the tween reaches its end value (always false for looping)
tween.isStarted() // true once play() was called at least once
tween.onComplete(Runnable callback)onComplete fires exactly once when the tween reaches its end — looping tweens never fire it; yoyo cycles never fire it on direction reversal. The callback runs on the render thread (it executes inside getAsDouble()).
Tween rotate = Tween.from(0.0).to(360.0).durationMs(800).loop().play();
Tween bounce = Tween.from(0.0).to(20.0).durationMs(500).yoyo().play();yoyo() implies loop(); the direction alternates every cycle.
Tween fadeIn = Tween.fadeIn(300); // 0 → 1, 300 ms, OUT_CUBIC, started
Tween fadeOut = Tween.fadeOut(300); // 1 → 0, 300 ms, OUT_CUBIC, startedPhysics-based mass-spring-damper interpolator. Unlike Tween, calling target(...) mid-flight does not restart the animation — the system keeps its current position and velocity and re-aims at the new target. Stays smooth under heavy interaction (drag handles, search-as-you-type, hover scaling).
Integration: semi-implicit Euler at a fixed 1/240 s sub-step. getAsDouble() integrates as many sub-steps as needed to catch up since the last sample. CPU is zero when nothing reads it.
Spring.smooth(double initial) // moderate stiffness, near-critical damping. Default.
Spring.snappy(double initial) // high stiffness, slightly over-damped. Fast, no overshoot.
Spring.bouncy(double initial) // under-damped. Overshoots and oscillates.
Spring.strong(double initial) // very stiff. Almost instant.Spring s = Spring.from(0.0)
.stiffness(180)
.damping(20)
.mass(1.0)
.restThreshold(0.001)
.target(100.0) // set initial target
.velocity(0) // set initial velocity
.onSettled(() -> ...)
.build();spring.target(double t) // re-aim, smooth transition. No-op if t == current target.
spring.set(double v) // snap. Position = velocity = 0, target = v, atRest = true.
spring.onSettled(Runnable) // replace the rest callback.spring.position()
spring.velocity()
spring.target()
spring.isAtRest()
spring.getAsDouble() // sample now (also drives integration)Spring xPos = Spring.bouncy(0.0);
Animated(child).translateX(xPos::getAsDouble).build();
xPos.target(200.0); // glide toward 200, with bouncy overshoot
xPos.target(50.0); // re-aim mid-flight, smoothlyStandard easings as DoubleUnaryOperator. Each family exposes IN, OUT, IN_OUT, and OUT_IN variants.
| Family | Constants |
|---|---|
| Linear | LINEAR |
| Sine |
IN_SINE, OUT_SINE, IN_OUT_SINE, OUT_IN_SINE
|
| Quad (power 2) |
IN_QUAD, OUT_QUAD, IN_OUT_QUAD, OUT_IN_QUAD
|
| Cubic (power 3) |
IN_CUBIC, OUT_CUBIC, IN_OUT_CUBIC, OUT_IN_CUBIC
|
| Quart (power 4) |
IN_QUART, OUT_QUART, IN_OUT_QUART, OUT_IN_QUART
|
| Quint (power 5) |
IN_QUINT, OUT_QUINT, IN_OUT_QUINT, OUT_IN_QUINT
|
| Expo |
IN_EXPO, OUT_EXPO, IN_OUT_EXPO, OUT_IN_EXPO
|
| Circ |
IN_CIRC, OUT_CIRC, IN_OUT_CIRC, OUT_IN_CIRC
|
| Back (overshoot) |
IN_BACK, OUT_BACK, IN_OUT_BACK, OUT_IN_BACK
|
| Elastic |
IN_ELASTIC, OUT_ELASTIC, IN_OUT_ELASTIC, OUT_IN_ELASTIC
|
| Bounce |
IN_BOUNCE, OUT_BOUNCE, IN_OUT_BOUNCE, OUT_IN_BOUNCE
|
| Bezier (CSS-style) |
BEZIER_IN, BEZIER_OUT, BEZIER_IN_OUT, BEZIER_OUT_IN
|
| Steps |
STEPS_START (steps(1, true)), STEPS_END (steps(1, false)) |
Easing.bezier(double cp1x, double cp1y, double cp2x, double cp2y) // CSS cubic-bezier
Easing.steps(int n, boolean jumpStart) // stair-step
Easing.power(double exponent) // arbitrary exponent (sugar for inPower)
Easing.inPower(double p)
Easing.outPower(double p)
Easing.inOutPower(double p)
Easing.outInPower(double p)Easing.combine(in, out) // first half "in" rescaled, second half "out" rescaled
Easing.outIn(inCurve) // build OUT_IN from an IN curve
Easing.reverse(inCurve) // 1 - in(1 - t)ARGB color interpolator. Mirrors Tween's lifecycle (play / stop / replay, loop / yoyo, onComplete) but exposes getAsInt() instead of getAsDouble().
ColorTween glow = ColorTween.from(0xFF_22_22_22)
.to(0xFF_00_AA_FF)
.durationMs(400)
.easing(Easing.IN_OUT_CUBIC)
.play();ColorTween glow = ColorTween.over(0xFF_22_22_22, 0xFF_00_AA_FF, 400, Easing.IN_OUT_CUBIC).play();int argb = glow.getAsInt(); // raw int, ARGB-packed
Integer boxed = glow.get(); // boxed variantPlug into the animated builder via backgroundColor(IntSupplier):
Animated(child).backgroundColor(glow::getAsInt).build();Render-time wrapper around any UIComponent. Suppliers are read on every frame and applied as transform / paint overrides. Layout is unchanged — animations are visual-only, so a fading-out child still occupies its layout slot.
Animated(child)
.opacity(DoubleSupplier)
.translateX(DoubleSupplier)
.translateY(DoubleSupplier)
.translate(DoubleSupplier x, DoubleSupplier y)
.scale(DoubleSupplier)
.backgroundColor(IntSupplier)
.build();Tween appear = Tween.over(0.0, 1.0, 400, Easing.OUT_CUBIC).play();
Spring xPos = Spring.smooth(0.0);
UIComponent animated = Animated(card)
.opacity(appear)
.translateX(xPos)
.build();
xPos.target(120); // glideFadeIn(UIComponent child, long durationMs) // 0 → 1 alpha, OUT_CUBIC
FadeOut(UIComponent child, long durationMs) // 1 → 0 alpha, OUT_CUBIC
SlideIn(UIComponent child, Direction from, long durationMs)SlideIn ramps a 100 px translation from the requested side to zero, with Easing.OUT_CUBIC. Use the Animated builder if you need a different offset, easing, or to combine slide with fade.
public enum Direction { LEFT, TOP, RIGHT, BOTTOM }SlideIn(child, Direction.LEFT, 300) slides the child in from the left.