16constexpr int32_t ONE = 256;
18int32_t quadIn(int32_t t) {
return (t * t) >> 8; }
19int32_t cubicIn(int32_t t) {
return (((t * t) >> 8) * t) >> 8; }
23int32_t backOut(int32_t t) {
24 constexpr int32_t S = 435;
26 int32_t u2 = (u * u) >> 8;
27 int32_t u3 = (u2 * u) >> 8;
28 return ONE + (((S + ONE) * u3) >> 8) + ((S * u2) >> 8);
33int32_t bounceOut(int32_t t) {
36 v = (1936 * t * t) >> 16;
39 v = ((1936 * u * u) >> 16) + 192;
42 v = ((1936 * u * u) >> 16) + 240;
45 v = ((1936 * u * u) >> 16) + 252;
47 return v > ONE ? ONE : v;
52const int16_t kElasticOut[17] = {
53 0, 213, 349, 305, 233, 228, 256, 268, 260,
54 252, 253, 257, 257, 256, 255, 256, 256,
57int32_t elasticOut(int32_t t) {
59 int32_t frac = t & 15;
60 int32_t a = kElasticOut[idx];
61 int32_t b = kElasticOut[idx + 1];
62 return a + ((b - a) * frac) / 16;
69 if (t >= ONE)
return ONE;
74 : ONE - (((ONE - t) * (ONE - t)) >> 7);
78 if (t < ONE / 2)
return (4 * t * t * t) >> 16;
80 return ONE - ((4 * u * u * u) >> 16);
93CanvasAnimator::Slot* CanvasAnimator::findSlot(uint32_t handle) {
94 if (handle == 0)
return nullptr;
95 for (
auto& s : slots_) {
96 if (s.phase != Phase::Free && s.handle == handle)
return &s;
101const CanvasAnimator::Slot* CanvasAnimator::findSlot(uint32_t handle)
const {
102 return const_cast<CanvasAnimator*
>(
this)->findSlot(handle);
105CanvasAnimator::Slot* CanvasAnimator::allocSlot() {
106 for (
auto& s : slots_) {
107 if (s.phase == Phase::Free) {
109 s.handle = nextHandle_++;
110 if (nextHandle_ == 0) nextHandle_ = 1;
124 Slot* s = allocSlot();
128 s->fromX = cfg.
fromX;
129 s->fromY = cfg.
fromY;
134 s->remaining = cfg.
repeat;
136 s->flags = cfg.
flags;
137 s->kind = Kind::Move;
140 s->phase = Phase::Chained;
142 s->startMs = nowMs + cfg.
delayMs;
143 s->phase = Phase::Delayed;
149 uint32_t doneActionId, uint32_t nowMs) {
150 if (elemId == 0 || periodMs == 0 || count == 0)
return 0;
151 Slot* s = allocSlot();
154 s->doneActionId = doneActionId;
155 s->durationMs = periodMs;
159 :
static_cast<uint16_t
>(count * 2);
160 s->kind = Kind::Blink;
161 s->phase = Phase::Delayed;
167void CanvasAnimator::releaseChain(uint32_t handle) {
168 for (
auto& s : slots_) {
169 if (s.phase == Phase::Chained && s.startAfter == handle) {
170 uint32_t child = s.handle;
171 s.phase = Phase::Free;
180 for (
auto& s : slots_) {
181 any |= (s.phase != Phase::Free);
182 s.phase = Phase::Free;
186 Slot* s = findSlot(handle);
187 if (!s)
return false;
188 s->phase = Phase::Free;
189 releaseChain(handle);
194 for (
auto& s : slots_) {
195 if (s.phase != Phase::Free && s.elemId == elemId) {
196 uint32_t handle = s.handle;
197 s.phase = Phase::Free;
198 releaseChain(handle);
204 Slot* s = findSlot(handle);
205 if (!s)
return false;
206 if (paused == s->paused)
return true;
208 s->pausedAtMs = nowMs;
211 s->startMs += nowMs - s->pausedAtMs;
218 const Slot* s = findSlot(handle);
227 for (
const auto& s : slots_) {
228 if (s.phase != Phase::Free) ++n;
234 for (
auto& s : slots_) {
235 if (s.phase == Phase::Delayed || s.phase == Phase::Running) {
236 s.startMs += deltaMs;
242 for (
auto& s : slots_) s.phase = Phase::Free;
245void CanvasAnimator::finishSlot(Slot& s,
AnimTarget& target, uint32_t nowMs,
247 uint8_t* doneCount) {
251 if (s.doneActionId != 0 && done && *doneCount < doneCap) {
253 done[*doneCount].
handle = s.handle;
254 done[*doneCount].
refId = s.elemId;
257 uint32_t handle = s.handle;
258 s.phase = Phase::Free;
260 for (
auto& c : slots_) {
261 if (c.phase == Phase::Chained && c.startAfter == handle) {
262 c.phase = Phase::Delayed;
263 c.startMs = nowMs + c.delayMs;
268bool CanvasAnimator::advanceMove(Slot& s, uint32_t nowMs,
AnimTarget& target) {
269 if (
static_cast<int32_t
>(nowMs - s.startMs) < 0)
return false;
273 int16_t ox = 0, oy = 0;
274 if (target.getElemOffset(s.elemId, &ox, &oy)) {
280 target.setElemVisible(s.elemId,
true);
283 s.phase = Phase::Running;
287 uint32_t elapsed = nowMs - s.startMs;
288 while (elapsed >= s.durationMs) {
289 if (s.remaining == 0) {
290 int16_t tx = s.reversed ? s.fromX : s.toX;
291 int16_t ty = s.reversed ? s.fromY : s.toY;
292 writeOffset(s, target, tx, ty);
296 if (s.flags &
FLAG_YOYO) s.reversed = !s.reversed;
297 s.startMs += s.durationMs;
298 elapsed = nowMs - s.startMs;
301 int32_t t =
static_cast<int32_t
>((elapsed * 256u) / s.durationMs);
302 int32_t e =
ease(s.easing, t);
303 int16_t fx = s.reversed ? s.toX : s.fromX;
304 int16_t fy = s.reversed ? s.toY : s.fromY;
305 int16_t tx = s.reversed ? s.fromX : s.toX;
306 int16_t ty = s.reversed ? s.fromY : s.toY;
307 int16_t x =
static_cast<int16_t
>(fx + ((
static_cast<int32_t
>(tx - fx) * e) >> 8));
308 int16_t y =
static_cast<int16_t
>(fy + ((
static_cast<int32_t
>(ty - fy) * e) >> 8));
309 writeOffset(s, target, x, y);
315bool CanvasAnimator::writeOffset(Slot& s,
AnimTarget& target, int16_t x, int16_t y) {
316 if (s.hasLast && s.lastX == x && s.lastY == y)
return false;
317 target.setElemOffset(s.elemId, x, y);
325bool CanvasAnimator::advanceBlink(Slot& s, uint32_t nowMs,
AnimTarget& target) {
326 s.phase = Phase::Running;
327 bool finished =
false;
328 while (
static_cast<int32_t
>(nowMs - (s.startMs + s.durationMs)) >= 0) {
329 s.startMs += s.durationMs;
330 s.reversed = !s.reversed;
331 target.setElemVisible(s.elemId, !s.reversed);
334 target.setElemVisible(s.elemId,
true);
344 uint8_t* doneCount) {
345 if (doneCount) *doneCount = 0;
346 bool changed =
false;
347 for (
auto& s : slots_) {
348 if (s.phase == Phase::Free || s.phase == Phase::Chained || s.paused) {
352 bool finished = (s.kind == Kind::Move) ? advanceMove(s, nowMs, target)
353 : advanceBlink(s, nowMs, target);
355 if (finished) finishSlot(s, target, nowMs, done, doneCap, doneCount);
364 arenaCap_ = buf ? cap : 0;
366 for (
auto& s : slots_) s = Slot{};
369SpriteStore::Slot* SpriteStore::findSlot(uint32_t handle) {
370 if (handle == 0)
return nullptr;
371 for (
auto& s : slots_) {
372 if (s.handle == handle)
return &s;
377const SpriteStore::Slot* SpriteStore::findSlot(uint32_t handle)
const {
378 return const_cast<SpriteStore*
>(
this)->findSlot(handle);
381uint32_t SpriteStore::frameBytes(
const Slot& s)
const {
382 return static_cast<uint32_t
>((s.w + 7) / 8) * s.h;
385int32_t SpriteStore::arenaAlloc(uint32_t bytes) {
386 if (!arena_ || arenaUsed_ + bytes > arenaCap_)
return -1;
387 uint32_t off = arenaUsed_;
389 return static_cast<int32_t
>(off);
394void SpriteStore::arenaFree(uint32_t off, uint32_t bytes) {
395 if (bytes == 0)
return;
396 memmove(arena_ + off, arena_ + off + bytes, arenaUsed_ - off - bytes);
398 for (
auto& s : slots_) {
399 if (s.handle == 0)
continue;
400 if (s.sheetOff > off) s.sheetOff -= bytes;
401 if (s.hasMask && s.maskOff > off) s.maskOff -= bytes;
402 if (s.hasDur && s.durOff > off) s.durOff -= bytes;
407 const uint8_t* data, uint32_t len) {
408 if (w == 0 || h == 0 || frameCount == 0 || frameCount >
MAX_FRAMES || !data) {
411 Slot* free =
nullptr;
412 for (
auto& s : slots_) {
418 if (!free)
return -1;
420 uint32_t stride =
static_cast<uint32_t
>((w + 7) / 8);
421 uint32_t bytes = stride * h * frameCount;
422 if (bytes == 0 || len < bytes)
return 0;
423 int32_t off = arenaAlloc(bytes);
424 if (off < 0)
return -1;
426 memcpy(arena_ + off, data, bytes);
428 free->handle = nextHandle_++;
431 if (nextHandle_ > 0xFFFF) nextHandle_ = 1;
432 free->sheetOff =
static_cast<uint32_t
>(off);
436 free->frameCount = frameCount;
437 return static_cast<int32_t
>(free->handle);
441 Slot* s = findSlot(handle);
442 if (!s || !mask || len < s->bytes)
return false;
444 int32_t off = arenaAlloc(s->bytes);
445 if (off < 0)
return false;
446 s->maskOff =
static_cast<uint32_t
>(off);
449 memcpy(arena_ + s->maskOff, mask, s->bytes);
455 Slot* s = findSlot(handle);
456 if (!s)
return false;
459 arenaFree(s->durOff,
static_cast<uint32_t
>(s->frameCount) * 2);
464 if (!ms || count != s->frameCount)
return false;
466 int32_t off = arenaAlloc(
static_cast<uint32_t
>(count) * 2);
467 if (off < 0)
return false;
468 s->durOff =
static_cast<uint32_t
>(off);
471 memcpy(arena_ + s->durOff, ms,
static_cast<uint32_t
>(count) * 2);
476 Slot* s = findSlot(handle);
477 if (!s)
return false;
479 struct Block { uint32_t off, bytes; };
482 blocks[n++] = {s->sheetOff, s->bytes};
483 if (s->hasMask) blocks[n++] = {s->maskOff, s->bytes};
484 if (s->hasDur) blocks[n++] = {s->durOff,
static_cast<uint32_t
>(s->frameCount) * 2};
486 for (
int i = 0; i < n; ++i) {
488 for (
int j = 1; j < n; ++j) {
489 if (blocks[j].off > blocks[hi].off) hi = j;
491 arenaFree(blocks[hi].off, blocks[hi].bytes);
493 blocks[hi].bytes = 0;
499 Slot* s = findSlot(handle);
500 if (!s)
return false;
506 Slot* s = findSlot(handle);
507 if (!s ||
scale == 0 ||
scale > 4)
return false;
513 uint16_t gapPx, uint16_t frameMs, uint32_t nowMs) {
514 Slot* s = findSlot(handle);
515 if (!s || windowW == 0 || stepPx == 0)
return false;
516 s->scrollWindow = windowW;
517 s->scrollStep = stepPx;
518 s->scrollGap = gapPx;
520 s->frameMs = frameMs < 50 ? 50 : frameMs;
525 s->nextFrameAt = nowMs + s->frameMs;
530 Slot* s = findSlot(handle);
531 if (!s ||
frame >= s->frameCount)
return false;
537 const Slot* s = findSlot(handle);
538 return s ? s->curFrame : -1;
541uint16_t SpriteStore::frameDuration(
const Slot& s)
const {
544 memcpy(&ms, arena_ + s.durOff +
static_cast<uint32_t
>(s.curFrame) * 2,
546 if (ms > 0)
return ms;
552 uint16_t repeat, uint32_t doneActionId, uint32_t nowMs) {
553 Slot* s = findSlot(handle);
557 s->frameMs = frameMs < 50 ? 50 : frameMs;
559 s->remaining = repeat;
560 s->doneActionId = doneActionId;
564 s->nextFrameAt = nowMs + frameDuration(*s);
569 Slot* s = findSlot(handle);
570 if (!s)
return false;
576 for (
auto& s : slots_) {
577 if (s.handle != 0) s.playing =
false;
582 for (
const auto& s : slots_) {
583 if (s.handle != 0 && s.playing)
return true;
589 for (
auto& s : slots_) {
590 if (s.handle != 0 && s.playing) s.nextFrameAt += deltaMs;
596 for (
auto& s : slots_) s = Slot{};
600 uint8_t* doneCount) {
601 bool changed =
false;
602 for (
auto& s : slots_) {
603 if (s.handle == 0 || !s.playing)
continue;
604 if (s.scrollWindow != 0) {
606 uint32_t span =
static_cast<uint32_t
>(s.w) + s.scrollGap;
607 while (
static_cast<int32_t
>(nowMs - s.nextFrameAt) >= 0) {
608 s.scrollX =
static_cast<uint16_t
>((s.scrollX + s.scrollStep) % span);
609 s.nextFrameAt += s.frameMs;
614 while (s.playing &&
static_cast<int32_t
>(nowMs - s.nextFrameAt) >= 0) {
615 bool cycleDone =
false;
616 if (s.frameCount == 1) {
619 int32_t next = s.curFrame + s.dir;
628 }
else if (next >= s.frameCount) {
630 next = s.frameCount - 2;
632 s.curFrame =
static_cast<uint16_t
>(next);
635 uint16_t next = s.curFrame + 1;
636 if (next >= s.frameCount) {
645 bool moreRuns = (s.remaining ==
REPEAT_FOREVER) || (s.remaining > 0);
658 if (s.doneActionId != 0 && done && doneCount
659 && *doneCount < doneCap) {
661 done[*doneCount].
handle = s.handle;
662 done[*doneCount].
refId = s.curFrame;
668 s.nextFrameAt += frameDuration(s);
675 const Slot* s = findSlot(handle);
676 if (!s || !out || !arena_)
return false;
677 uint32_t fb = frameBytes(*s);
678 out->
data = arena_ + s->sheetOff + fb * s->curFrame;
679 out->
mask = s->hasMask ? arena_ + s->maskOff + fb * s->curFrame :
nullptr;
682 out->
flags = s->flags;
683 out->
scale = s->scale;
Host-side animation engine for the plugin canvas: fixed-point easing, element-offset tweens and multi...
Interface the animator drives; implemented by CanvasView on its elements.
virtual bool setElemVisible(uint32_t elemId, bool visible)=0
uint8_t activeCount() const
Number of live slots (delayed, chained, running or paused).
bool pause(uint32_t handle, bool paused, uint32_t nowMs)
void reset()
Drop all slots without firing completions (teardown).
bool advance(uint32_t nowMs, AnimTarget &target, Completion *done, uint8_t doneCap, uint8_t *doneCount)
Advance all tweens to nowMs, applying offsets/visibility.
void cancelForElem(uint32_t elemId)
Silently cancel every tween bound to an element (element was removed).
uint32_t start(const TweenConfig &cfg, uint32_t nowMs)
Start an offset tween. A FROM_CURRENT start captures the live offset lazily on the first advance() af...
void shiftTime(uint32_t deltaMs)
Shift every time base forward (view was paused for deltaMs).
int8_t state(uint32_t handle) const
uint32_t blink(uint32_t elemId, uint16_t periodMs, uint16_t count, uint32_t doneActionId, uint32_t nowMs)
Start a visibility blink: count on/off cycles of periodMs per phase, ending visible....
bool cancel(uint32_t handle)
Cancel a tween and its chain (handle 0 = all). No completion fires.
static constexpr uint16_t MAX_DURATION_MS
static constexpr uint16_t MAX_FRAMES
bool stop(uint32_t handle)
bool destroy(uint32_t handle)
bool setMask(uint32_t handle, const uint8_t *mask, uint32_t len)
Attach a mask plane (same sheet layout/size as the frame data).
bool advance(uint32_t nowMs, Completion *done, uint8_t doneCap, uint8_t *doneCount)
bool setFlags(uint32_t handle, uint8_t flags)
int32_t create(uint16_t w, uint16_t h, uint16_t frameCount, const uint8_t *data, uint32_t len)
Copy a frame sheet into the arena and allocate a sprite slot.
void setArena(uint8_t *buf, uint32_t cap)
Inject the pixel arena. Must happen before create(); resets the store.
bool play(uint32_t handle, uint8_t mode, uint16_t frameMs, uint16_t repeat, uint32_t doneActionId, uint32_t nowMs)
int32_t frame(uint32_t handle) const
Current frame or -1.
bool frameView(uint32_t handle, FrameView *out) const
bool scroll(uint32_t handle, uint16_t windowW, uint16_t stepPx, uint16_t gapPx, uint16_t frameMs, uint32_t nowMs)
Turn the sprite into a horizontal scroller (marquee backing).
void shiftTime(uint32_t deltaMs)
bool setFrame(uint32_t handle, uint16_t frame)
bool setFrameDurations(uint32_t handle, const uint16_t *ms, uint16_t count)
bool setScale(uint32_t handle, uint8_t scale)
Integer upscale factor 1..4 applied when the sprite is drawn.
@ FLAG_SHOW_START
Show the element when the delay elapses.
@ FLAG_HIDE_DONE
Hide the element on completion.
@ FLAG_YOYO
Each repeat run alternates direction.
@ FLAG_FROM_CURRENT
Ignore from_x/y, start at the live offset.
constexpr uint16_t REPEAT_FOREVER
Repeat-forever sentinel shared by tweens and sprite playback.
@ SPRITE_ONCE
Play to the last frame and hold it.
int32_t ease(uint8_t curve, int32_t t)
Apply an easing curve to a normalized 8.8 fixed-point time.
uint32_t refId
Element id (tween) or final frame (sprite).
uint32_t handle
Tween handle or sprite handle.
Replay accessors: geometry, flags and pixel pointers of a sprite.
const uint8_t * data
Current frame pixels.
const uint8_t * mask
Mask plane or null.
uint16_t scrollGap
Blank gap between wraps.
uint16_t scrollWindow
Window width; 0 = not scrolling.
uint16_t scrollX
Scroll position (scrollWindow > 0).
Tween start configuration (mirrors host_anim_t plus the blink shorthand).
uint16_t repeat
Extra runs; REPEAT_FOREVER = endless.
uint32_t startAfter
Predecessor tween handle; 0 = start now.