Skip to content

Animations

Run a frame callback at a fixed rate, use the four built-in effects, and draw button feedback with ButtonUI.

Snippets on this page are compiled from tests/docs/graphics.cpp.

The animator

Animator(gfx, frame_fn, user_data, fps) calls frame_fn(gfx, frame, user_data) each time a frame is due. It keeps no clock of its own: you tell it how much time has passed.

void run_plasma(Display& display, uint32_t (*millis)()) {
  auto gfx = display.CreateGraphicsContext();
  isf15acp4::Animator anim(gfx, isf15acp4::animations::Plasma, nullptr, 25);  // 25 FPS: 40 ms per frame

  uint32_t last = millis();
  for (;;) {
    const uint32_t now = millis();
    if (anim.Tick(now - last)) {                          // renders when a frame is due
      display.Present(gfx);
    }
    last = now;
  }
}
Member Does
Tick(elapsed_ms) Adds the time; when at least one frame period has built up, renders one frame and returns true
RenderNow() Renders the next frame at once
SetFps(fps) Frame period 1000 / fps ms (33 ms when fps is 0)
Frame(), Reset() The frame counter; Reset() sets it and the time to zero

Tick() renders at most one frame per call and keeps any extra time, so after a late call the next calls catch up one frame each; no frame number is skipped. The animator only draws: you call Present() when it returns true.

Built-in effects

Function Draws
ColorWheel A rotating colour wheel, with a label
Plasma A moving sine-interference pattern
ScrollingText A title and a line of text moving left
PulseButton A frame and a growing bar, green when pressed

They are in isf15acp4::animations. ColorWheel and Plasma ignore user_data. ScrollingText takes an animations::ScrollTextState* (its text; null shows "ISF15ACP4"), PulseButton an animations::PulseButtonState* (its pressed; null is not pressed).

ColorWheel and Plasma compute every pixel with floating-point sine functions: on an MCU without an FPU that can take longer than the frame period.

Your own effect

A frame function is a plain noexcept function. Pass your state through user_data:

struct Bar {
  uint8_t level;                                          // 0..95, updated by your code
};

void DrawBar(isf15acp4::GraphicsContext& gfx, uint32_t frame, void* user_data) noexcept {
  const auto* bar = static_cast<const Bar*>(user_data);
  gfx.Clear(colors::Black);
  gfx.FillRect({0, 28, bar->level, 36}, (frame / 15) % 2 ? colors::Green : colors::Yellow);
}

void start_bar(isf15acp4::GraphicsContext& gfx, Bar& bar) {
  isf15acp4::Animator anim(gfx, &DrawBar, &bar, 30);
  anim.RenderNow();                                       // first frame without waiting
}

The animator keeps a reference to the context, so the context must outlive it.

Button feedback

ButtonUI debounces the switch (API guide) and draws two kinds of feedback:

isf15acp4::ButtonUI ui;
ui.DrawStatus(gfx, "START", display.IsPressed());       // title bar, state text, frame
isf15acp4::ButtonUI::DrawGauge(gfx, 48, 40, 20, load,   // half-circle gauge, load in 0..1
                               colors::Green, colors::Gray);
display.Present(gfx);
Call Draws
DrawStatus() A full status screen: title bar with the label, "PRESSED" or "READY", a border
DrawGauge() A half disc below its centre, the first value (0 to 1) of it in fg

DrawStatus() clears the frame to dark green when active, black otherwise. DrawGauge() is static.

The examples combine these: isf15acp4_animation_demo cycles the four effects on each press, and isf15acp4_button_dashboard draws a gauge driven by the switch.