CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
CanvasAnim.cpp
Go to the documentation of this file.
1
6
8
9#include <cstring>
10
11namespace cdc::ui::anim {
12
13namespace {
14
16constexpr int32_t ONE = 256;
17
18int32_t quadIn(int32_t t) { return (t * t) >> 8; }
19int32_t cubicIn(int32_t t) { return (((t * t) >> 8) * t) >> 8; }
20
23int32_t backOut(int32_t t) {
24 constexpr int32_t S = 435;
25 int32_t u = t - ONE; // -256..0
26 int32_t u2 = (u * u) >> 8;
27 int32_t u3 = (u2 * u) >> 8;
28 return ONE + (((S + ONE) * u3) >> 8) + ((S * u2) >> 8);
29}
30
33int32_t bounceOut(int32_t t) {
34 int32_t v;
35 if (t < 93) { // t < 1/2.75
36 v = (1936 * t * t) >> 16;
37 } else if (t < 186) { // t < 2/2.75
38 int32_t u = t - 140; // 1.5/2.75
39 v = ((1936 * u * u) >> 16) + 192; // + 0.75
40 } else if (t < 233) { // t < 2.5/2.75
41 int32_t u = t - 209; // 2.25/2.75
42 v = ((1936 * u * u) >> 16) + 240; // + 0.9375
43 } else {
44 int32_t u = t - 244; // 2.625/2.75
45 v = ((1936 * u * u) >> 16) + 252; // + 0.984375
46 }
47 return v > ONE ? ONE : v;
48}
49
52const int16_t kElasticOut[17] = {
53 0, 213, 349, 305, 233, 228, 256, 268, 260,
54 252, 253, 257, 257, 256, 255, 256, 256,
55};
56
57int32_t elasticOut(int32_t t) {
58 int32_t idx = t >> 4; // 0..15
59 int32_t frac = t & 15; // within the segment
60 int32_t a = kElasticOut[idx];
61 int32_t b = kElasticOut[idx + 1];
62 return a + ((b - a) * frac) / 16;
63}
64
65} // namespace
66
67int32_t ease(uint8_t curve, int32_t t) {
68 if (t <= 0) return 0;
69 if (t >= ONE) return ONE;
70 switch (curve) {
71 case EASE_QUAD_IN: return quadIn(t);
72 case EASE_QUAD_OUT: return ONE - quadIn(ONE - t);
73 case EASE_QUAD_IN_OUT: return t < ONE / 2 ? (t * t) >> 7
74 : ONE - (((ONE - t) * (ONE - t)) >> 7);
75 case EASE_CUBIC_IN: return cubicIn(t);
76 case EASE_CUBIC_OUT: return ONE - cubicIn(ONE - t);
77 case EASE_CUBIC_IN_OUT: {
78 if (t < ONE / 2) return (4 * t * t * t) >> 16;
79 int32_t u = ONE - t;
80 return ONE - ((4 * u * u * u) >> 16);
81 }
82 case EASE_OVERSHOOT: return backOut(t);
83 case EASE_BOUNCE: return bounceOut(t);
84 case EASE_ELASTIC: return elasticOut(t);
85 case EASE_STEP: return 0;
86 case EASE_LINEAR:
87 default: return t;
88 }
89}
90
91// --- CanvasAnimator ----------------------------------------------------------
92
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;
97 }
98 return nullptr;
99}
100
101const CanvasAnimator::Slot* CanvasAnimator::findSlot(uint32_t handle) const {
102 return const_cast<CanvasAnimator*>(this)->findSlot(handle);
103}
104
105CanvasAnimator::Slot* CanvasAnimator::allocSlot() {
106 for (auto& s : slots_) {
107 if (s.phase == Phase::Free) {
108 s = Slot{};
109 s.handle = nextHandle_++;
110 if (nextHandle_ == 0) nextHandle_ = 1;
111 return &s;
112 }
113 }
114 return nullptr;
115}
116
117uint32_t CanvasAnimator::start(const TweenConfig& cfg, uint32_t nowMs) {
118 if (cfg.elemId == 0 || cfg.durationMs == 0 || cfg.durationMs > MAX_DURATION_MS
119 || cfg.delayMs > MAX_DURATION_MS || cfg.easing >= EASE_COUNT) {
120 return 0;
121 }
122 if (cfg.startAfter != 0 && !findSlot(cfg.startAfter)) return 0;
123
124 Slot* s = allocSlot();
125 if (!s) return 0;
126 s->elemId = cfg.elemId;
127 s->doneActionId = cfg.doneActionId;
128 s->fromX = cfg.fromX;
129 s->fromY = cfg.fromY;
130 s->toX = cfg.toX;
131 s->toY = cfg.toY;
132 s->durationMs = cfg.durationMs;
133 s->delayMs = cfg.delayMs;
134 s->remaining = cfg.repeat;
135 s->easing = cfg.easing;
136 s->flags = cfg.flags;
137 s->kind = Kind::Move;
138 if (cfg.startAfter != 0) {
139 s->startAfter = cfg.startAfter;
140 s->phase = Phase::Chained;
141 } else {
142 s->startMs = nowMs + cfg.delayMs;
143 s->phase = Phase::Delayed;
144 }
145 return s->handle;
146}
147
148uint32_t CanvasAnimator::blink(uint32_t elemId, uint16_t periodMs, uint16_t count,
149 uint32_t doneActionId, uint32_t nowMs) {
150 if (elemId == 0 || periodMs == 0 || count == 0) return 0;
151 Slot* s = allocSlot();
152 if (!s) return 0;
153 s->elemId = elemId;
154 s->doneActionId = doneActionId;
155 s->durationMs = periodMs;
156 // Two visibility phases per blink; REPEAT_FOREVER stays forever.
157 s->blinkCount = (count == REPEAT_FOREVER)
159 : static_cast<uint16_t>(count * 2);
160 s->kind = Kind::Blink;
161 s->phase = Phase::Delayed;
162 s->startMs = nowMs;
163 s->reversed = false; // false = currently visible
164 return s->handle;
165}
166
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;
172 releaseChain(child);
173 }
174 }
175}
176
177bool CanvasAnimator::cancel(uint32_t handle) {
178 if (handle == 0) {
179 bool any = false;
180 for (auto& s : slots_) {
181 any |= (s.phase != Phase::Free);
182 s.phase = Phase::Free;
183 }
184 return any;
185 }
186 Slot* s = findSlot(handle);
187 if (!s) return false;
188 s->phase = Phase::Free;
189 releaseChain(handle);
190 return true;
191}
192
193void CanvasAnimator::cancelForElem(uint32_t elemId) {
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);
199 }
200 }
201}
202
203bool CanvasAnimator::pause(uint32_t handle, bool paused, uint32_t nowMs) {
204 Slot* s = findSlot(handle);
205 if (!s) return false;
206 if (paused == s->paused) return true;
207 if (paused) {
208 s->pausedAtMs = nowMs;
209 s->paused = true;
210 } else {
211 s->startMs += nowMs - s->pausedAtMs;
212 s->paused = false;
213 }
214 return true;
215}
216
217int8_t CanvasAnimator::state(uint32_t handle) const {
218 const Slot* s = findSlot(handle);
219 if (!s) return -1;
220 if (s->paused) return STATE_PAUSED;
221 if (s->phase == Phase::Running) return STATE_RUNNING;
222 return STATE_DELAYED;
223}
224
226 uint8_t n = 0;
227 for (const auto& s : slots_) {
228 if (s.phase != Phase::Free) ++n;
229 }
230 return n;
231}
232
233void CanvasAnimator::shiftTime(uint32_t deltaMs) {
234 for (auto& s : slots_) {
235 if (s.phase == Phase::Delayed || s.phase == Phase::Running) {
236 s.startMs += deltaMs;
237 }
238 }
239}
240
242 for (auto& s : slots_) s.phase = Phase::Free;
243}
244
245void CanvasAnimator::finishSlot(Slot& s, AnimTarget& target, uint32_t nowMs,
246 Completion* done, uint8_t doneCap,
247 uint8_t* doneCount) {
248 if (s.flags & FLAG_HIDE_DONE) {
249 target.setElemVisible(s.elemId, false);
250 }
251 if (s.doneActionId != 0 && done && *doneCount < doneCap) {
252 done[*doneCount].doneActionId = s.doneActionId;
253 done[*doneCount].handle = s.handle;
254 done[*doneCount].refId = s.elemId;
255 ++*doneCount;
256 }
257 uint32_t handle = s.handle;
258 s.phase = Phase::Free;
259 // Wake chained successors.
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;
264 }
265 }
266}
267
268bool CanvasAnimator::advanceMove(Slot& s, uint32_t nowMs, AnimTarget& target) {
269 if (static_cast<int32_t>(nowMs - s.startMs) < 0) return false;
270
271 if (!s.started) {
272 if (s.flags & FLAG_FROM_CURRENT) {
273 int16_t ox = 0, oy = 0;
274 if (target.getElemOffset(s.elemId, &ox, &oy)) {
275 s.fromX = ox;
276 s.fromY = oy;
277 }
278 }
279 if (s.flags & FLAG_SHOW_START) {
280 target.setElemVisible(s.elemId, true);
281 }
282 s.started = true;
283 s.phase = Phase::Running;
284 }
285
286 // Fold completed runs (repeat/yoyo) keeping the time base steady.
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);
293 return true; // caller finishes the slot
294 }
295 if (s.remaining != REPEAT_FOREVER) --s.remaining;
296 if (s.flags & FLAG_YOYO) s.reversed = !s.reversed;
297 s.startMs += s.durationMs;
298 elapsed = nowMs - s.startMs;
299 }
300
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);
310 return false;
311}
312
313// Write the eased offset, remembering the last value so advance() can report
314// whether anything visible actually moved this tick.
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);
318 s.lastX = x;
319 s.lastY = y;
320 s.hasLast = true;
321 s.moved = true;
322 return true;
323}
324
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; // toggle visibility phase
331 target.setElemVisible(s.elemId, !s.reversed);
332 s.moved = true;
333 if (s.blinkCount != REPEAT_FOREVER && --s.blinkCount == 0) {
334 target.setElemVisible(s.elemId, true);
335 finished = true;
336 break;
337 }
338 }
339 return finished;
340}
341
342bool CanvasAnimator::advance(uint32_t nowMs, AnimTarget& target,
343 Completion* done, uint8_t doneCap,
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) {
349 continue;
350 }
351 s.moved = false;
352 bool finished = (s.kind == Kind::Move) ? advanceMove(s, nowMs, target)
353 : advanceBlink(s, nowMs, target);
354 changed |= s.moved;
355 if (finished) finishSlot(s, target, nowMs, done, doneCap, doneCount);
356 }
357 return changed;
358}
359
360// --- SpriteStore -------------------------------------------------------------
361
362void SpriteStore::setArena(uint8_t* buf, uint32_t cap) {
363 arena_ = buf;
364 arenaCap_ = buf ? cap : 0;
365 arenaUsed_ = 0;
366 for (auto& s : slots_) s = Slot{};
367}
368
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;
373 }
374 return nullptr;
375}
376
377const SpriteStore::Slot* SpriteStore::findSlot(uint32_t handle) const {
378 return const_cast<SpriteStore*>(this)->findSlot(handle);
379}
380
381uint32_t SpriteStore::frameBytes(const Slot& s) const {
382 return static_cast<uint32_t>((s.w + 7) / 8) * s.h;
383}
384
385int32_t SpriteStore::arenaAlloc(uint32_t bytes) {
386 if (!arena_ || arenaUsed_ + bytes > arenaCap_) return -1;
387 uint32_t off = arenaUsed_;
388 arenaUsed_ += bytes;
389 return static_cast<int32_t>(off);
390}
391
392// Close the gap at [off, off+bytes) and re-point every block above it. The
393// arena is append-allocated, so a single memmove keeps it compact.
394void SpriteStore::arenaFree(uint32_t off, uint32_t bytes) {
395 if (bytes == 0) return;
396 memmove(arena_ + off, arena_ + off + bytes, arenaUsed_ - off - bytes);
397 arenaUsed_ -= 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;
403 }
404}
405
406int32_t SpriteStore::create(uint16_t w, uint16_t h, uint16_t frameCount,
407 const uint8_t* data, uint32_t len) {
408 if (w == 0 || h == 0 || frameCount == 0 || frameCount > MAX_FRAMES || !data) {
409 return 0;
410 }
411 Slot* free = nullptr;
412 for (auto& s : slots_) {
413 if (s.handle == 0) {
414 free = &s;
415 break;
416 }
417 }
418 if (!free) return -1;
419
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;
425
426 memcpy(arena_ + off, data, bytes);
427 *free = Slot{};
428 free->handle = nextHandle_++;
429 // Sprite handles stay 16-bit so display-list commands can reference them
430 // in the existing u16 slot.
431 if (nextHandle_ > 0xFFFF) nextHandle_ = 1;
432 free->sheetOff = static_cast<uint32_t>(off);
433 free->bytes = bytes;
434 free->w = w;
435 free->h = h;
436 free->frameCount = frameCount;
437 return static_cast<int32_t>(free->handle);
438}
439
440bool SpriteStore::setMask(uint32_t handle, const uint8_t* mask, uint32_t len) {
441 Slot* s = findSlot(handle);
442 if (!s || !mask || len < s->bytes) return false;
443 if (!s->hasMask) {
444 int32_t off = arenaAlloc(s->bytes);
445 if (off < 0) return false;
446 s->maskOff = static_cast<uint32_t>(off);
447 s->hasMask = true;
448 }
449 memcpy(arena_ + s->maskOff, mask, s->bytes);
450 return true;
451}
452
453bool SpriteStore::setFrameDurations(uint32_t handle, const uint16_t* ms,
454 uint16_t count) {
455 Slot* s = findSlot(handle);
456 if (!s) return false;
457 if (count == 0) {
458 if (s->hasDur) {
459 arenaFree(s->durOff, static_cast<uint32_t>(s->frameCount) * 2);
460 s->hasDur = false;
461 }
462 return true;
463 }
464 if (!ms || count != s->frameCount) return false;
465 if (!s->hasDur) {
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);
469 s->hasDur = true;
470 }
471 memcpy(arena_ + s->durOff, ms, static_cast<uint32_t>(count) * 2);
472 return true;
473}
474
475bool SpriteStore::destroy(uint32_t handle) {
476 Slot* s = findSlot(handle);
477 if (!s) return false;
478 // Free the highest block first so lower offsets stay valid in between.
479 struct Block { uint32_t off, bytes; };
480 Block blocks[3];
481 int n = 0;
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};
485 *s = Slot{};
486 for (int i = 0; i < n; ++i) {
487 int hi = 0;
488 for (int j = 1; j < n; ++j) {
489 if (blocks[j].off > blocks[hi].off) hi = j;
490 }
491 arenaFree(blocks[hi].off, blocks[hi].bytes);
492 blocks[hi].off = 0;
493 blocks[hi].bytes = 0;
494 }
495 return true;
496}
497
498bool SpriteStore::setFlags(uint32_t handle, uint8_t flags) {
499 Slot* s = findSlot(handle);
500 if (!s) return false;
501 s->flags = flags;
502 return true;
503}
504
505bool SpriteStore::setScale(uint32_t handle, uint8_t scale) {
506 Slot* s = findSlot(handle);
507 if (!s || scale == 0 || scale > 4) return false;
508 s->scale = scale;
509 return true;
510}
511
512bool SpriteStore::scroll(uint32_t handle, uint16_t windowW, uint16_t stepPx,
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;
519 s->scrollX = 0;
520 s->frameMs = frameMs < 50 ? 50 : frameMs;
521 s->mode = SPRITE_LOOP;
522 s->remaining = REPEAT_FOREVER;
523 s->doneActionId = 0;
524 s->playing = true;
525 s->nextFrameAt = nowMs + s->frameMs;
526 return true;
527}
528
529bool SpriteStore::setFrame(uint32_t handle, uint16_t frame) {
530 Slot* s = findSlot(handle);
531 if (!s || frame >= s->frameCount) return false;
532 s->curFrame = frame;
533 return true;
534}
535
536int32_t SpriteStore::frame(uint32_t handle) const {
537 const Slot* s = findSlot(handle);
538 return s ? s->curFrame : -1;
539}
540
541uint16_t SpriteStore::frameDuration(const Slot& s) const {
542 if (s.hasDur) {
543 uint16_t ms;
544 memcpy(&ms, arena_ + s.durOff + static_cast<uint32_t>(s.curFrame) * 2,
545 sizeof(ms));
546 if (ms > 0) return ms;
547 }
548 return s.frameMs;
549}
550
551bool SpriteStore::play(uint32_t handle, uint8_t mode, uint16_t frameMs,
552 uint16_t repeat, uint32_t doneActionId, uint32_t nowMs) {
553 Slot* s = findSlot(handle);
554 if (!s || mode > SPRITE_PING_PONG) return false;
555 // Floor the pace at 50 ms; anything faster is beyond the panel anyway and
556 // would busy-spin the catch-up loop.
557 s->frameMs = frameMs < 50 ? 50 : frameMs;
558 s->mode = mode;
559 s->remaining = repeat;
560 s->doneActionId = doneActionId;
561 s->curFrame = 0;
562 s->dir = 1;
563 s->playing = true;
564 s->nextFrameAt = nowMs + frameDuration(*s);
565 return true;
566}
567
568bool SpriteStore::stop(uint32_t handle) {
569 Slot* s = findSlot(handle);
570 if (!s) return false;
571 s->playing = false;
572 return true;
573}
574
576 for (auto& s : slots_) {
577 if (s.handle != 0) s.playing = false;
578 }
579}
580
582 for (const auto& s : slots_) {
583 if (s.handle != 0 && s.playing) return true;
584 }
585 return false;
586}
587
588void SpriteStore::shiftTime(uint32_t deltaMs) {
589 for (auto& s : slots_) {
590 if (s.handle != 0 && s.playing) s.nextFrameAt += deltaMs;
591 }
592}
593
595 arenaUsed_ = 0;
596 for (auto& s : slots_) s = Slot{};
597}
598
599bool SpriteStore::advance(uint32_t nowMs, Completion* done, uint8_t doneCap,
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) {
605 // Marquee: slide the window, wrapping over content + gap.
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;
610 changed = true;
611 }
612 continue;
613 }
614 while (s.playing && static_cast<int32_t>(nowMs - s.nextFrameAt) >= 0) {
615 bool cycleDone = false;
616 if (s.frameCount == 1) {
617 cycleDone = true;
618 } else if (s.mode == SPRITE_PING_PONG) {
619 int32_t next = s.curFrame + s.dir;
620 if (next < 0) {
621 // Bounce at the start: one there-and-back completed. The
622 // next cycle's first step happens in the same beat, so a
623 // continuing loop shows ...2 1 0 1 2... without doubling
624 // the end frames.
625 s.dir = 1;
626 next = 1;
627 cycleDone = true;
628 } else if (next >= s.frameCount) {
629 s.dir = -1;
630 next = s.frameCount - 2;
631 }
632 s.curFrame = static_cast<uint16_t>(next);
633 changed = true;
634 } else {
635 uint16_t next = s.curFrame + 1;
636 if (next >= s.frameCount) {
637 cycleDone = true; // wrap (LOOP) or hold (ONCE) decided below
638 } else {
639 s.curFrame = next;
640 changed = true;
641 }
642 }
643
644 if (cycleDone) {
645 bool moreRuns = (s.remaining == REPEAT_FOREVER) || (s.remaining > 0);
646 if (s.mode == SPRITE_ONCE) moreRuns = false;
647 if (moreRuns) {
648 if (s.remaining != REPEAT_FOREVER) --s.remaining;
649 if (s.mode == SPRITE_LOOP && s.frameCount > 1) {
650 s.curFrame = 0;
651 changed = true;
652 }
653 } else {
654 // ONCE and exhausted LOOP hold the last frame; a finished
655 // PING_PONG rests where it bounced, at frame 0.
656 if (s.mode == SPRITE_PING_PONG) s.curFrame = 0;
657 s.playing = false;
658 if (s.doneActionId != 0 && done && doneCount
659 && *doneCount < doneCap) {
660 done[*doneCount].doneActionId = s.doneActionId;
661 done[*doneCount].handle = s.handle;
662 done[*doneCount].refId = s.curFrame;
663 ++*doneCount;
664 }
665 break;
666 }
667 }
668 s.nextFrameAt += frameDuration(s);
669 }
670 }
671 return changed;
672}
673
674bool SpriteStore::frameView(uint32_t handle, FrameView* out) const {
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;
680 out->w = s->w;
681 out->h = s->h;
682 out->flags = s->flags;
683 out->scale = s->scale;
684 out->scrollX = s->scrollX;
685 out->scrollWindow = s->scrollWindow;
686 out->scrollGap = s->scrollGap;
687 return true;
688}
689
690} // namespace cdc::ui::anim
Host-side animation engine for the plugin canvas: fixed-point easing, element-offset tweens and multi...
uint8_t flags
Interface the animator drives; implemented by CanvasView on its elements.
Definition CanvasAnim.h:77
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
Definition CanvasAnim.h:120
static constexpr uint16_t MAX_FRAMES
Definition CanvasAnim.h:215
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.
uint8_t curve
@ FLAG_SHOW_START
Show the element when the delay elapses.
Definition CanvasAnim.h:40
@ FLAG_HIDE_DONE
Hide the element on completion.
Definition CanvasAnim.h:39
@ FLAG_YOYO
Each repeat run alternates direction.
Definition CanvasAnim.h:38
@ FLAG_FROM_CURRENT
Ignore from_x/y, start at the live offset.
Definition CanvasAnim.h:37
constexpr uint16_t REPEAT_FOREVER
Repeat-forever sentinel shared by tweens and sprite playback.
Definition CanvasAnim.h:66
@ SPRITE_ONCE
Play to the last frame and hold it.
Definition CanvasAnim.h:52
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).
Definition CanvasAnim.h:73
uint32_t handle
Tween handle or sprite handle.
Definition CanvasAnim.h:72
Replay accessors: geometry, flags and pixel pointers of a sprite.
Definition CanvasAnim.h:267
const uint8_t * data
Current frame pixels.
Definition CanvasAnim.h:268
const uint8_t * mask
Mask plane or null.
Definition CanvasAnim.h:269
uint16_t scrollGap
Blank gap between wraps.
Definition CanvasAnim.h:275
uint16_t scrollWindow
Window width; 0 = not scrolling.
Definition CanvasAnim.h:274
uint16_t scrollX
Scroll position (scrollWindow > 0).
Definition CanvasAnim.h:273
Tween start configuration (mirrors host_anim_t plus the blink shorthand).
Definition CanvasAnim.h:94
uint16_t repeat
Extra runs; REPEAT_FOREVER = endless.
Definition CanvasAnim.h:102
uint32_t startAfter
Predecessor tween handle; 0 = start now.
Definition CanvasAnim.h:106