Screen Transition
Source: src/screen_transition.c · 418 lines · 30 functions.
Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
ensureSnapshot() · source line 18
static bool ensureSnapshot(screen_transition_t *transition)
- Decision
transition->snapshot != NULLTrue- Exit this function
return true;
FalseContinue after this decision.
- Execute
transition->snapshot = heap_caps_malloc(SCREEN_TRANSITION_FRAME_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - Exit this function
return transition->snapshot != NULL;
Read original function source
static bool ensureSnapshot(screen_transition_t *transition)
{
// Allocate on first use, then retain it for every later transition in this screen.
if (transition->snapshot != NULL) return true;
transition->snapshot = heap_caps_malloc(SCREEN_TRANSITION_FRAME_BYTES,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
return transition->snapshot != NULL;
}ensureScrollIncomingSnapshot() · source line 27
static bool ensureScrollIncomingSnapshot(screen_transition_t *transition)
- Decision
transition->scroll_incoming_snapshot != NULLTrue- Exit this function
return true;
FalseContinue after this decision.
- Execute
transition->scroll_incoming_snapshot = heap_caps_malloc( SCREEN_TRANSITION_FRAME_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - Exit this function
return transition->scroll_incoming_snapshot != NULL;
Read original function source
static bool ensureScrollIncomingSnapshot(screen_transition_t *transition)
{
if (transition->scroll_incoming_snapshot != NULL) return true;
transition->scroll_incoming_snapshot = heap_caps_malloc(
SCREEN_TRANSITION_FRAME_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
return transition->scroll_incoming_snapshot != NULL;
}screenTransitionStart() · source line 35
bool screenTransitionStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t freeze_ms, uint32_t zoom_ms)Calls into this documentation:
- Decision
transition == NULL || frame == NULL || zoom_ms == 0UTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
!ensureSnapshot(transition)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES); - Execute
transition->started_at_ms = now_ms; - Execute
transition->freeze_ms = freeze_ms; - Execute
transition->zoom_ms = zoom_ms; - Execute
transition->active = true; - Execute
transition->complete = false; - Execute
transition->zoom_in = false; - Exit this function
return true;
Read original function source
bool screenTransitionStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t freeze_ms, uint32_t zoom_ms)
{
if (transition == NULL || frame == NULL || zoom_ms == 0U) return false;
if (!ensureSnapshot(transition)) return false;
// Freeze exactly the completed caller frame before the caller draws another scene.
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
transition->started_at_ms = now_ms;
transition->freeze_ms = freeze_ms;
transition->zoom_ms = zoom_ms;
transition->active = true;
transition->complete = false;
transition->zoom_in = false;
return true;
}screenTransitionZoomInStart() · source line 51
bool screenTransitionZoomInStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t zoom_ms)Calls into this documentation:
- Decision
!screenTransitionStart(transition, frame, now_ms, 0U, zoom_ms)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
transition->zoom_in = true; - Exit this function
return true;
Read original function source
bool screenTransitionZoomInStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t zoom_ms)
{
if (!screenTransitionStart(transition, frame, now_ms, 0U, zoom_ms)) return false;
transition->zoom_in = true;
return true;
}screenTransitionDraw() · source line 59
void screenTransitionDraw(screen_transition_t *transition, uint16_t *frame,
uint32_t now_ms)Calls into this documentation:
- Decision
transition == NULL || frame == NULL || !transition->active || transition->snapshot == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed_ms = now_ms - transition->started_at_ms; - Decision
!transition->zoom_in && elapsed_ms < transition->freeze_msTrue- Execute
memcpy(frame, transition->snapshot, SCREEN_TRANSITION_FRAME_BYTES); - Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t zoom_elapsed_ms = transition->zoom_in ? elapsed_ms : elapsed_ms - transition->freeze_ms; - Execute
float progress = (float)zoom_elapsed_ms / (float)transition->zoom_ms; - Decision
progress > 1.0fTrue- Execute
progress = 1.0f;
FalseContinue after this decision.
- Execute
const float scale = transition->zoom_in ? progress * progress : 1.0f - progress * progress; - Execute
int width = (int)lroundf((float)LCD_WIDTH * scale); - Execute
int height = (int)lroundf((float)LCD_HEIGHT * scale); - Decision
width < 1True- Execute
width = 1;
FalseContinue after this decision.
- Decision
height < 1True- Execute
height = 1;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Execute
draw_rgb565_scaled(frame, (LCD_WIDTH - width) / 2, (LCD_HEIGHT - height) / 2, transition->snapshot, NULL, LCD_WIDTH, LCD_HEIGHT, width, height); - Decision
progress >= 1.0fTrue- Execute
transition->complete = true;
FalseContinue after this decision.
Read original function source
void screenTransitionDraw(screen_transition_t *transition, uint16_t *frame,
uint32_t now_ms)
{
if (transition == NULL || frame == NULL || !transition->active ||
transition->snapshot == NULL) return;
const uint32_t elapsed_ms = now_ms - transition->started_at_ms;
if (!transition->zoom_in && elapsed_ms < transition->freeze_ms) {
// The initial phase is a literal frozen copy, not a re-rendered scene.
memcpy(frame, transition->snapshot, SCREEN_TRANSITION_FRAME_BYTES);
return;
}
const uint32_t zoom_elapsed_ms = transition->zoom_in
? elapsed_ms : elapsed_ms - transition->freeze_ms;
float progress = (float)zoom_elapsed_ms / (float)transition->zoom_ms;
if (progress > 1.0f) progress = 1.0f;
// Square progress eases into the outgoing dot and eases away from the incoming dot.
const float scale = transition->zoom_in ? progress * progress : 1.0f - progress * progress;
int width = (int)lroundf((float)LCD_WIDTH * scale);
int height = (int)lroundf((float)LCD_HEIGHT * scale);
if (width < 1) width = 1;
if (height < 1) height = 1;
clear(frame, COLOR_BLACK);
draw_rgb565_scaled(frame, (LCD_WIDTH - width) / 2, (LCD_HEIGHT - height) / 2,
transition->snapshot, NULL, LCD_WIDTH, LCD_HEIGHT, width, height);
if (progress >= 1.0f) transition->complete = true;
}screenTransitionActive() · source line 87
bool screenTransitionActive(const screen_transition_t *transition)
- Exit this function
return transition != NULL && transition->active;
Read original function source
bool screenTransitionActive(const screen_transition_t *transition)
{
return transition != NULL && transition->active;
}screenTransitionComplete() · source line 92
bool screenTransitionComplete(const screen_transition_t *transition)
- Exit this function
return transition != NULL && transition->complete;
Read original function source
bool screenTransitionComplete(const screen_transition_t *transition)
{
return transition != NULL && transition->complete;
}screenTransitionReset() · source line 97
void screenTransitionReset(screen_transition_t *transition)
- Decision
transition == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
transition->active = false; - Execute
transition->complete = false; - Execute
transition->zoom_in = false;
Read original function source
void screenTransitionReset(screen_transition_t *transition)
{
if (transition == NULL) return;
transition->active = false;
transition->complete = false;
transition->zoom_in = false;
}screenTransitionDeinitialize() · source line 105
void screenTransitionDeinitialize(screen_transition_t *transition)
- Decision
transition == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
heap_caps_free(transition->snapshot); - Execute
heap_caps_free(transition->reveal_block_order); - Execute
heap_caps_free(transition->scroll_incoming_snapshot); - Execute
*transition = (screen_transition_t){0};
Read original function source
void screenTransitionDeinitialize(screen_transition_t *transition)
{
if (transition == NULL) return;
// Both allocations are owned by this reusable effect and live only while its caller is open.
heap_caps_free(transition->snapshot);
heap_caps_free(transition->reveal_block_order);
heap_caps_free(transition->scroll_incoming_snapshot);
*transition = (screen_transition_t){0};
}screenRevealStart() · source line 115
bool screenRevealStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t reveal_ms, unsigned reveal_percent)Calls into this documentation:
- Decision
transition == NULL || frame == NULL || reveal_ms == 0U || reveal_percent == 0UTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
!ensureSnapshot(transition)True- Exit this function
return false;
FalseContinue after this decision.
- Decision
transition->reveal_block_order == NULLTrue- Execute
transition->reveal_block_order = heap_caps_malloc( SCREEN_REVEAL_BLOCK_COUNT * sizeof(transition->reveal_block_order[0]), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - Decision
transition->reveal_block_order == NULLTrue- Exit this function
return false;
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES); - FOR loop
uint16_t block = 0U; block < SCREEN_REVEAL_BLOCK_COUNT; ++blockRepeat body while the loop condition permits; then continue below.
- Execute
transition->reveal_block_order[block] = block;
- FOR loop
uint16_t block = SCREEN_REVEAL_BLOCK_COUNT - 1U; block > 0U; --blockRepeat body while the loop condition permits; then continue below.
- Execute
const uint16_t swap_with = (uint16_t)(esp_random() % (block + 1U)); - Execute
const uint16_t saved = transition->reveal_block_order[block]; - Execute
transition->reveal_block_order[block] = transition->reveal_block_order[swap_with]; - Execute
transition->reveal_block_order[swap_with] = saved;
- Decision
reveal_percent > 100UTrue- Execute
reveal_percent = 100U;
FalseContinue after this decision.
- Execute
transition->reveal_target_count = (uint16_t)((SCREEN_REVEAL_BLOCK_COUNT * reveal_percent + 99U) / 100U); - Execute
transition->reveal_block_count = 0U; - Execute
transition->reveal_started_at_ms = now_ms; - Execute
transition->reveal_ms = reveal_ms; - Execute
transition->reveal_active = true; - Execute
transition->reveal_complete = false; - Exit this function
return true;
Read original function source
bool screenRevealStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t reveal_ms, unsigned reveal_percent)
{
if (transition == NULL || frame == NULL || reveal_ms == 0U || reveal_percent == 0U) {
return false;
}
if (!ensureSnapshot(transition)) return false;
if (transition->reveal_block_order == NULL) {
transition->reveal_block_order = heap_caps_malloc(
SCREEN_REVEAL_BLOCK_COUNT * sizeof(transition->reveal_block_order[0]),
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (transition->reveal_block_order == NULL) return false;
}
// Capture the already rendered destination stage before replacing it with black blocks.
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
// Build a complete non-repeating block order, then shuffle it for this reveal.
for (uint16_t block = 0U; block < SCREEN_REVEAL_BLOCK_COUNT; ++block) {
transition->reveal_block_order[block] = block;
}
for (uint16_t block = SCREEN_REVEAL_BLOCK_COUNT - 1U; block > 0U; --block) {
const uint16_t swap_with = (uint16_t)(esp_random() % (block + 1U));
const uint16_t saved = transition->reveal_block_order[block];
transition->reveal_block_order[block] = transition->reveal_block_order[swap_with];
transition->reveal_block_order[swap_with] = saved;
}
if (reveal_percent > 100U) reveal_percent = 100U;
// Round up so the requested percentage is never under-represented by a block.
transition->reveal_target_count = (uint16_t)((SCREEN_REVEAL_BLOCK_COUNT * reveal_percent +
99U) / 100U);
transition->reveal_block_count = 0U;
transition->reveal_started_at_ms = now_ms;
transition->reveal_ms = reveal_ms;
transition->reveal_active = true;
transition->reveal_complete = false;
return true;
}drawRevealBlock() · source line 152
static void drawRevealBlock(uint16_t *frame, const uint16_t *snapshot, uint16_t block)
- Execute
const int x = (int)(block % SCREEN_REVEAL_COLUMNS) * SCREEN_REVEAL_BLOCK_PIXELS; - Execute
const int y = (int)(block / SCREEN_REVEAL_COLUMNS) * SCREEN_REVEAL_BLOCK_PIXELS; - Execute
int width = SCREEN_REVEAL_BLOCK_PIXELS; - Execute
int height = SCREEN_REVEAL_BLOCK_PIXELS; - Decision
x + width > LCD_WIDTHTrue- Execute
width = LCD_WIDTH - x;
FalseContinue after this decision.
- Decision
y + height > LCD_HEIGHTTrue- Execute
height = LCD_HEIGHT - y;
FalseContinue after this decision.
- FOR loop
int row = 0; row < height; ++rowRepeat body while the loop condition permits; then continue below.
- Execute
memcpy(&frame[(size_t)(y + row) * LCD_WIDTH + x], &snapshot[(size_t)(y + row) * LCD_WIDTH + x], (size_t)width * sizeof(frame[0]));
Read original function source
static void drawRevealBlock(uint16_t *frame, const uint16_t *snapshot, uint16_t block)
{
// Copy an unscaled 8 x 8 cell; clipped edge cells handle display sizes not divisible by eight.
const int x = (int)(block % SCREEN_REVEAL_COLUMNS) * SCREEN_REVEAL_BLOCK_PIXELS;
const int y = (int)(block / SCREEN_REVEAL_COLUMNS) * SCREEN_REVEAL_BLOCK_PIXELS;
int width = SCREEN_REVEAL_BLOCK_PIXELS;
int height = SCREEN_REVEAL_BLOCK_PIXELS;
if (x + width > LCD_WIDTH) width = LCD_WIDTH - x;
if (y + height > LCD_HEIGHT) height = LCD_HEIGHT - y;
for (int row = 0; row < height; ++row) {
memcpy(&frame[(size_t)(y + row) * LCD_WIDTH + x],
&snapshot[(size_t)(y + row) * LCD_WIDTH + x],
(size_t)width * sizeof(frame[0]));
}
}screenRevealDraw() · source line 168
void screenRevealDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
Calls into this documentation:
- Decision
transition == NULL || frame == NULL || !transition->reveal_active || transition->snapshot == NULL || transition->reveal_block_order == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed_ms = now_ms - transition->reveal_started_at_ms; - Execute
uint16_t visible_blocks = transition->reveal_target_count; - Decision
elapsed_ms < transition->reveal_msTrue- Execute
visible_blocks = (uint16_t)(((uint64_t)transition->reveal_target_count * elapsed_ms) / transition->reveal_ms);
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - FOR loop
uint16_t index = 0U; index < visible_blocks; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
drawRevealBlock(frame, transition->snapshot, transition->reveal_block_order[index]);
- Execute
transition->reveal_block_count = visible_blocks; - Decision
elapsed_ms >= transition->reveal_msTrue- Execute
memcpy(frame, transition->snapshot, SCREEN_TRANSITION_FRAME_BYTES); - Execute
transition->reveal_complete = true;
FalseContinue after this decision.
Read original function source
void screenRevealDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
{
if (transition == NULL || frame == NULL || !transition->reveal_active ||
transition->snapshot == NULL || transition->reveal_block_order == NULL) return;
const uint32_t elapsed_ms = now_ms - transition->reveal_started_at_ms;
uint16_t visible_blocks = transition->reveal_target_count;
if (elapsed_ms < transition->reveal_ms) {
// Reveal only the early portion of the shuffled order until the configured percentage.
visible_blocks = (uint16_t)(((uint64_t)transition->reveal_target_count * elapsed_ms) /
transition->reveal_ms);
}
clear(frame, COLOR_BLACK);
for (uint16_t index = 0U; index < visible_blocks; ++index) {
drawRevealBlock(frame, transition->snapshot, transition->reveal_block_order[index]);
}
transition->reveal_block_count = visible_blocks;
if (elapsed_ms >= transition->reveal_ms) {
// The partial pattern has done its job: restore the complete frozen destination frame.
memcpy(frame, transition->snapshot, SCREEN_TRANSITION_FRAME_BYTES);
transition->reveal_complete = true;
}
}screenRevealActive() · source line 191
bool screenRevealActive(const screen_transition_t *transition)
- Exit this function
return transition != NULL && transition->reveal_active;
Read original function source
bool screenRevealActive(const screen_transition_t *transition)
{
return transition != NULL && transition->reveal_active;
}screenRevealComplete() · source line 196
bool screenRevealComplete(const screen_transition_t *transition)
- Exit this function
return transition != NULL && transition->reveal_complete;
Read original function source
bool screenRevealComplete(const screen_transition_t *transition)
{
return transition != NULL && transition->reveal_complete;
}screenRevealReset() · source line 201
void screenRevealReset(screen_transition_t *transition)
- Decision
transition == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
transition->reveal_active = false; - Execute
transition->reveal_complete = false; - Execute
transition->reveal_block_count = 0U;
Read original function source
void screenRevealReset(screen_transition_t *transition)
{
if (transition == NULL) return;
transition->reveal_active = false;
transition->reveal_complete = false;
transition->reveal_block_count = 0U;
}screenScrollStart() · source line 209
static bool screenScrollStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step, bool scroll_in)Calls into this documentation:
- Decision
transition == NULL || frame == NULL || step_ms == 0U || pixels_per_step == 0UTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
!ensureSnapshot(transition)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES); - Execute
transition->scroll_started_at_ms = now_ms; - Execute
transition->scroll_step_ms = step_ms; - Execute
transition->scroll_pixels_per_step = pixels_per_step; - Execute
transition->scroll_direction = direction; - Execute
transition->scroll_has_incoming = false; - Execute
transition->scroll_gap_pixels = 0U; - Execute
transition->scroll_active = true; - Execute
transition->scroll_complete = false; - Execute
transition->scroll_in = scroll_in; - Exit this function
return true;
Read original function source
static bool screenScrollStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step, bool scroll_in)
{
if (transition == NULL || frame == NULL || step_ms == 0U || pixels_per_step == 0U) {
return false;
}
if (!ensureSnapshot(transition)) return false;
// Capture the complete source before translating it so every step uses the same pixels.
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
transition->scroll_started_at_ms = now_ms;
transition->scroll_step_ms = step_ms;
transition->scroll_pixels_per_step = pixels_per_step;
transition->scroll_direction = direction;
transition->scroll_has_incoming = false;
transition->scroll_gap_pixels = 0U;
transition->scroll_active = true;
transition->scroll_complete = false;
transition->scroll_in = scroll_in;
return true;
}screenScrollOutStart() · source line 231
bool screenScrollOutStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step)Calls into this documentation:
- Exit this function
return screenScrollStart(transition, frame, now_ms, direction, step_ms, pixels_per_step, false);
Read original function source
bool screenScrollOutStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step)
{
return screenScrollStart(transition, frame, now_ms, direction, step_ms,
pixels_per_step, false);
}screenScrollInStart() · source line 239
bool screenScrollInStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step)Calls into this documentation:
- Exit this function
return screenScrollStart(transition, frame, now_ms, direction, step_ms, pixels_per_step, true);
Read original function source
bool screenScrollInStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step)
{
return screenScrollStart(transition, frame, now_ms, direction, step_ms,
pixels_per_step, true);
}screenScrollSetIncoming() · source line 247
bool screenScrollSetIncoming(screen_transition_t *transition, const uint16_t *frame,
screen_transition_scroll_direction_t direction,
uint16_t gap_pixels)Calls into this documentation:
- Decision
transition == NULL || frame == NULL || !transition->scroll_active || transition->scroll_in || !ensureScrollIncomingSnapshot(transition)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
memcpy(transition->scroll_incoming_snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES); - Execute
transition->scroll_incoming_direction = direction; - Execute
transition->scroll_gap_pixels = gap_pixels; - Execute
transition->scroll_has_incoming = true; - Exit this function
return true;
Read original function source
bool screenScrollSetIncoming(screen_transition_t *transition, const uint16_t *frame,
screen_transition_scroll_direction_t direction,
uint16_t gap_pixels)
{
if (transition == NULL || frame == NULL || !transition->scroll_active ||
transition->scroll_in || !ensureScrollIncomingSnapshot(transition)) {
return false;
}
// Preserve the fully rendered destination so it can travel beside the outgoing frame.
memcpy(transition->scroll_incoming_snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
transition->scroll_incoming_direction = direction;
transition->scroll_gap_pixels = gap_pixels;
transition->scroll_has_incoming = true;
return true;
}screenScrollAxisPixels() · source line 265
static uint32_t screenScrollAxisPixels(screen_transition_scroll_direction_t direction)
- Exit this function
return direction == SCREEN_TRANSITION_SCROLL_UP || direction == SCREEN_TRANSITION_SCROLL_DOWN ? LCD_HEIGHT : LCD_WIDTH;
Read original function source
static uint32_t screenScrollAxisPixels(screen_transition_scroll_direction_t direction)
{
return direction == SCREEN_TRANSITION_SCROLL_UP ||
direction == SCREEN_TRANSITION_SCROLL_DOWN ? LCD_HEIGHT : LCD_WIDTH;
}screenScrollDraw() · source line 271
void screenScrollDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
Calls into this documentation:
- Decision
transition == NULL || frame == NULL || !transition->scroll_active || transition->snapshot == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed_ms = now_ms - transition->scroll_started_at_ms; - Execute
const uint32_t steps = elapsed_ms / transition->scroll_step_ms; - Execute
uint32_t offset = steps * transition->scroll_pixels_per_step; - Execute
const uint32_t axis_pixels = screenScrollAxisPixels(transition->scroll_direction); - Execute
const uint32_t travel_pixels = axis_pixels + (transition->scroll_has_incoming ? transition->scroll_gap_pixels : 0U); - Decision
offset > travel_pixelsTrue- Execute
offset = travel_pixels;
FalseContinue after this decision.
- Execute
int x = 0; - Execute
int y = 0; - Select a case
transition->scroll_directionEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case SCREEN_TRANSITION_SCROLL_LEFT: - Execute
x = transition->scroll_in ? -LCD_WIDTH + (int)offset : -(int)offset; - Exit enclosing loop or switch
break; - Case / label
case SCREEN_TRANSITION_SCROLL_RIGHT: - Execute
x = transition->scroll_in ? LCD_WIDTH - (int)offset : (int)offset; - Exit enclosing loop or switch
break; - Case / label
case SCREEN_TRANSITION_SCROLL_UP: - Execute
y = transition->scroll_in ? -LCD_HEIGHT + (int)offset : -(int)offset; - Exit enclosing loop or switch
break; - Case / label
case SCREEN_TRANSITION_SCROLL_DOWN: - Execute
y = transition->scroll_in ? LCD_HEIGHT - (int)offset : (int)offset; - Exit enclosing loop or switch
break;
- Execute
clear(frame, COLOR_BLACK); - Execute
draw_rgb565_scaled(frame, x, y, transition->snapshot, NULL, LCD_WIDTH, LCD_HEIGHT, LCD_WIDTH, LCD_HEIGHT); - Decision
transition->scroll_has_incomingTrue- Execute
int incoming_x = 0; - Execute
int incoming_y = 0; - Select a case
transition->scroll_incoming_directionEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case SCREEN_TRANSITION_SCROLL_LEFT: - Execute
incoming_x = -LCD_WIDTH - (int)transition->scroll_gap_pixels + (int)offset; - Exit enclosing loop or switch
break; - Case / label
case SCREEN_TRANSITION_SCROLL_RIGHT: - Execute
incoming_x = LCD_WIDTH + (int)transition->scroll_gap_pixels - (int)offset; - Exit enclosing loop or switch
break; - Case / label
case SCREEN_TRANSITION_SCROLL_UP: - Execute
incoming_y = -LCD_HEIGHT - (int)transition->scroll_gap_pixels + (int)offset; - Exit enclosing loop or switch
break; - Case / label
case SCREEN_TRANSITION_SCROLL_DOWN: - Execute
incoming_y = LCD_HEIGHT + (int)transition->scroll_gap_pixels - (int)offset; - Exit enclosing loop or switch
break;
- Execute
draw_rgb565_scaled(frame, incoming_x, incoming_y, transition->scroll_incoming_snapshot, NULL, LCD_WIDTH, LCD_HEIGHT, LCD_WIDTH, LCD_HEIGHT);
FalseContinue after this decision.
- Decision
offset >= travel_pixelsTrue- Execute
transition->scroll_complete = true;
FalseContinue after this decision.
Read original function source
void screenScrollDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
{
if (transition == NULL || frame == NULL || !transition->scroll_active ||
transition->snapshot == NULL) return;
const uint32_t elapsed_ms = now_ms - transition->scroll_started_at_ms;
const uint32_t steps = elapsed_ms / transition->scroll_step_ms;
uint32_t offset = steps * transition->scroll_pixels_per_step;
const uint32_t axis_pixels = screenScrollAxisPixels(transition->scroll_direction);
const uint32_t travel_pixels = axis_pixels +
(transition->scroll_has_incoming ? transition->scroll_gap_pixels : 0U);
if (offset > travel_pixels) offset = travel_pixels;
// An outgoing image moves from its origin toward the selected edge. An incoming
// image starts beyond that edge and moves back to its origin on either axis.
int x = 0;
int y = 0;
switch (transition->scroll_direction) {
case SCREEN_TRANSITION_SCROLL_LEFT:
x = transition->scroll_in ? -LCD_WIDTH + (int)offset : -(int)offset;
break;
case SCREEN_TRANSITION_SCROLL_RIGHT:
x = transition->scroll_in ? LCD_WIDTH - (int)offset : (int)offset;
break;
case SCREEN_TRANSITION_SCROLL_UP:
y = transition->scroll_in ? -LCD_HEIGHT + (int)offset : -(int)offset;
break;
case SCREEN_TRANSITION_SCROLL_DOWN:
y = transition->scroll_in ? LCD_HEIGHT - (int)offset : (int)offset;
break;
}
clear(frame, COLOR_BLACK);
draw_rgb565_scaled(frame, x, y, transition->snapshot, NULL,
LCD_WIDTH, LCD_HEIGHT, LCD_WIDTH, LCD_HEIGHT);
if (transition->scroll_has_incoming) {
// The second frame moves in at the same rate. A zero gap makes the two
// frame edges touch; increasing gap_pixels leaves a controlled black band.
int incoming_x = 0;
int incoming_y = 0;
switch (transition->scroll_incoming_direction) {
case SCREEN_TRANSITION_SCROLL_LEFT:
incoming_x = -LCD_WIDTH - (int)transition->scroll_gap_pixels + (int)offset;
break;
case SCREEN_TRANSITION_SCROLL_RIGHT:
incoming_x = LCD_WIDTH + (int)transition->scroll_gap_pixels - (int)offset;
break;
case SCREEN_TRANSITION_SCROLL_UP:
incoming_y = -LCD_HEIGHT - (int)transition->scroll_gap_pixels + (int)offset;
break;
case SCREEN_TRANSITION_SCROLL_DOWN:
incoming_y = LCD_HEIGHT + (int)transition->scroll_gap_pixels - (int)offset;
break;
}
draw_rgb565_scaled(frame, incoming_x, incoming_y,
transition->scroll_incoming_snapshot, NULL,
LCD_WIDTH, LCD_HEIGHT, LCD_WIDTH, LCD_HEIGHT);
}
if (offset >= travel_pixels) transition->scroll_complete = true;
}screenScrollActive() · source line 329
bool screenScrollActive(const screen_transition_t *transition)
- Exit this function
return transition != NULL && transition->scroll_active;
Read original function source
bool screenScrollActive(const screen_transition_t *transition)
{
return transition != NULL && transition->scroll_active;
}screenScrollComplete() · source line 334
bool screenScrollComplete(const screen_transition_t *transition)
- Exit this function
return transition != NULL && transition->scroll_complete;
Read original function source
bool screenScrollComplete(const screen_transition_t *transition)
{
return transition != NULL && transition->scroll_complete;
}screenScrollReset() · source line 339
void screenScrollReset(screen_transition_t *transition)
- Decision
transition == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
transition->scroll_active = false; - Execute
transition->scroll_complete = false; - Execute
transition->scroll_in = false; - Execute
transition->scroll_has_incoming = false; - Execute
transition->scroll_gap_pixels = 0U;
Read original function source
void screenScrollReset(screen_transition_t *transition)
{
if (transition == NULL) return;
transition->scroll_active = false;
transition->scroll_complete = false;
transition->scroll_in = false;
transition->scroll_has_incoming = false;
transition->scroll_gap_pixels = 0U;
}screenSliceStart() · source line 349
bool screenSliceStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint16_t slice_count, uint32_t slice_ms)Calls into this documentation:
- Decision
transition == NULL || frame == NULL || slice_count == 0U || slice_ms == 0UTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
!ensureSnapshot(transition)True- Exit this function
return false;
FalseContinue after this decision.
- Decision
slice_count > (uint16_t)LCD_HEIGHTTrue- Execute
slice_count = (uint16_t)LCD_HEIGHT;
FalseContinue after this decision.
- Execute
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES); - Execute
transition->slice_started_at_ms = now_ms; - Execute
transition->slice_ms = slice_ms; - Execute
transition->slice_count = slice_count; - Execute
transition->slice_active = true; - Execute
transition->slice_complete = false; - Exit this function
return true;
Read original function source
bool screenSliceStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint16_t slice_count, uint32_t slice_ms)
{
if (transition == NULL || frame == NULL || slice_count == 0U || slice_ms == 0U) {
return false;
}
if (!ensureSnapshot(transition)) return false;
if (slice_count > (uint16_t)LCD_HEIGHT) slice_count = (uint16_t)LCD_HEIGHT;
// Capture the fully rendered outgoing scene so every strip keeps its original pixels.
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
transition->slice_started_at_ms = now_ms;
transition->slice_ms = slice_ms;
transition->slice_count = slice_count;
transition->slice_active = true;
transition->slice_complete = false;
return true;
}drawSliceRows() · source line 367
static void drawSliceRows(uint16_t *frame, const uint16_t *snapshot, int first_row,
int row_count, int offset_x)- Decision
offset_x <= -LCD_WIDTH || offset_x >= LCD_WIDTHTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const int source_x = offset_x < 0 ? -offset_x : 0; - Execute
const int destination_x = offset_x > 0 ? offset_x : 0; - Execute
const int copy_width = LCD_WIDTH - (offset_x < 0 ? -offset_x : offset_x); - FOR loop
int row = 0; row < row_count; ++rowRepeat body while the loop condition permits; then continue below.
- Execute
const size_t index = (size_t)(first_row + row) * LCD_WIDTH; - Execute
memcpy(&frame[index + destination_x], &snapshot[index + source_x], (size_t)copy_width * sizeof(frame[0]));
Read original function source
static void drawSliceRows(uint16_t *frame, const uint16_t *snapshot, int first_row,
int row_count, int offset_x)
{
if (offset_x <= -LCD_WIDTH || offset_x >= LCD_WIDTH) return;
const int source_x = offset_x < 0 ? -offset_x : 0;
const int destination_x = offset_x > 0 ? offset_x : 0;
const int copy_width = LCD_WIDTH - (offset_x < 0 ? -offset_x : offset_x);
for (int row = 0; row < row_count; ++row) {
const size_t index = (size_t)(first_row + row) * LCD_WIDTH;
memcpy(&frame[index + destination_x], &snapshot[index + source_x],
(size_t)copy_width * sizeof(frame[0]));
}
}screenSliceDraw() · source line 381
void screenSliceDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
Calls into this documentation:
- Decision
transition == NULL || frame == NULL || !transition->slice_active || transition->snapshot == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed_ms = now_ms - transition->slice_started_at_ms; - Execute
float progress = (float)elapsed_ms / (float)transition->slice_ms; - Decision
progress > 1.0fTrue- Execute
progress = 1.0f;
FalseContinue after this decision.
- Execute
const int offset = (int)lroundf((float)LCD_WIDTH * progress); - Execute
clear(frame, COLOR_BLACK); - Execute
const int base_height = LCD_HEIGHT / (int)transition->slice_count; - Execute
const int remainder = LCD_HEIGHT % (int)transition->slice_count; - Execute
int first_row = 0; - FOR loop
uint16_t slice = 0U; slice < transition->slice_count; ++sliceRepeat body while the loop condition permits; then continue below.
- Execute
const int row_count = base_height + (slice < (uint16_t)remainder ? 1 : 0); - Execute
const int direction_offset = (slice & 1U) == 0U ? -offset : offset; - Execute
drawSliceRows(frame, transition->snapshot, first_row, row_count, direction_offset); - Execute
first_row += row_count;
- Decision
progress >= 1.0fTrue- Execute
transition->slice_complete = true;
FalseContinue after this decision.
Read original function source
void screenSliceDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
{
if (transition == NULL || frame == NULL || !transition->slice_active ||
transition->snapshot == NULL) return;
const uint32_t elapsed_ms = now_ms - transition->slice_started_at_ms;
float progress = (float)elapsed_ms / (float)transition->slice_ms;
if (progress > 1.0f) progress = 1.0f;
const int offset = (int)lroundf((float)LCD_WIDTH * progress);
clear(frame, COLOR_BLACK);
// Spread any remainder rows over the earliest strips so the complete display is covered.
const int base_height = LCD_HEIGHT / (int)transition->slice_count;
const int remainder = LCD_HEIGHT % (int)transition->slice_count;
int first_row = 0;
for (uint16_t slice = 0U; slice < transition->slice_count; ++slice) {
const int row_count = base_height + (slice < (uint16_t)remainder ? 1 : 0);
const int direction_offset = (slice & 1U) == 0U ? -offset : offset;
drawSliceRows(frame, transition->snapshot, first_row, row_count, direction_offset);
first_row += row_count;
}
if (progress >= 1.0f) transition->slice_complete = true;
}screenSliceActive() · source line 403
bool screenSliceActive(const screen_transition_t *transition)
- Exit this function
return transition != NULL && transition->slice_active;
Read original function source
bool screenSliceActive(const screen_transition_t *transition)
{
return transition != NULL && transition->slice_active;
}screenSliceComplete() · source line 408
bool screenSliceComplete(const screen_transition_t *transition)
- Exit this function
return transition != NULL && transition->slice_complete;
Read original function source
bool screenSliceComplete(const screen_transition_t *transition)
{
return transition != NULL && transition->slice_complete;
}screenSliceReset() · source line 413
void screenSliceReset(screen_transition_t *transition)
- Decision
transition == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
transition->slice_active = false; - Execute
transition->slice_complete = false;
Read original function source
void screenSliceReset(screen_transition_t *transition)
{
if (transition == NULL) return;
transition->slice_active = false;
transition->slice_complete = false;
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "screen_transition.h"
#include "esp_heap_caps.h"
#include "esp_random.h"
#include "game_graphics.h"
#include <math.h>
#include <string.h>
// Shared PSRAM image capacity. It must remain one complete RGB565 LCD frame.
#define SCREEN_TRANSITION_FRAME_BYTES (LCD_WIDTH * LCD_HEIGHT * sizeof(uint16_t))
// Rebuild tuning: these square cells are selected in a shuffled order.
#define SCREEN_REVEAL_BLOCK_PIXELS 8U
#define SCREEN_REVEAL_COLUMNS ((LCD_WIDTH + SCREEN_REVEAL_BLOCK_PIXELS - 1U) / SCREEN_REVEAL_BLOCK_PIXELS)
#define SCREEN_REVEAL_ROWS ((LCD_HEIGHT + SCREEN_REVEAL_BLOCK_PIXELS - 1U) / SCREEN_REVEAL_BLOCK_PIXELS)
#define SCREEN_REVEAL_BLOCK_COUNT (SCREEN_REVEAL_COLUMNS * SCREEN_REVEAL_ROWS)
static bool ensureSnapshot(screen_transition_t *transition)
{
// Allocate on first use, then retain it for every later transition in this screen.
if (transition->snapshot != NULL) return true;
transition->snapshot = heap_caps_malloc(SCREEN_TRANSITION_FRAME_BYTES,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
return transition->snapshot != NULL;
}
static bool ensureScrollIncomingSnapshot(screen_transition_t *transition)
{
if (transition->scroll_incoming_snapshot != NULL) return true;
transition->scroll_incoming_snapshot = heap_caps_malloc(
SCREEN_TRANSITION_FRAME_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
return transition->scroll_incoming_snapshot != NULL;
}
bool screenTransitionStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t freeze_ms, uint32_t zoom_ms)
{
if (transition == NULL || frame == NULL || zoom_ms == 0U) return false;
if (!ensureSnapshot(transition)) return false;
// Freeze exactly the completed caller frame before the caller draws another scene.
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
transition->started_at_ms = now_ms;
transition->freeze_ms = freeze_ms;
transition->zoom_ms = zoom_ms;
transition->active = true;
transition->complete = false;
transition->zoom_in = false;
return true;
}
bool screenTransitionZoomInStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t zoom_ms)
{
if (!screenTransitionStart(transition, frame, now_ms, 0U, zoom_ms)) return false;
transition->zoom_in = true;
return true;
}
void screenTransitionDraw(screen_transition_t *transition, uint16_t *frame,
uint32_t now_ms)
{
if (transition == NULL || frame == NULL || !transition->active ||
transition->snapshot == NULL) return;
const uint32_t elapsed_ms = now_ms - transition->started_at_ms;
if (!transition->zoom_in && elapsed_ms < transition->freeze_ms) {
// The initial phase is a literal frozen copy, not a re-rendered scene.
memcpy(frame, transition->snapshot, SCREEN_TRANSITION_FRAME_BYTES);
return;
}
const uint32_t zoom_elapsed_ms = transition->zoom_in
? elapsed_ms : elapsed_ms - transition->freeze_ms;
float progress = (float)zoom_elapsed_ms / (float)transition->zoom_ms;
if (progress > 1.0f) progress = 1.0f;
// Square progress eases into the outgoing dot and eases away from the incoming dot.
const float scale = transition->zoom_in ? progress * progress : 1.0f - progress * progress;
int width = (int)lroundf((float)LCD_WIDTH * scale);
int height = (int)lroundf((float)LCD_HEIGHT * scale);
if (width < 1) width = 1;
if (height < 1) height = 1;
clear(frame, COLOR_BLACK);
draw_rgb565_scaled(frame, (LCD_WIDTH - width) / 2, (LCD_HEIGHT - height) / 2,
transition->snapshot, NULL, LCD_WIDTH, LCD_HEIGHT, width, height);
if (progress >= 1.0f) transition->complete = true;
}
bool screenTransitionActive(const screen_transition_t *transition)
{
return transition != NULL && transition->active;
}
bool screenTransitionComplete(const screen_transition_t *transition)
{
return transition != NULL && transition->complete;
}
void screenTransitionReset(screen_transition_t *transition)
{
if (transition == NULL) return;
transition->active = false;
transition->complete = false;
transition->zoom_in = false;
}
void screenTransitionDeinitialize(screen_transition_t *transition)
{
if (transition == NULL) return;
// Both allocations are owned by this reusable effect and live only while its caller is open.
heap_caps_free(transition->snapshot);
heap_caps_free(transition->reveal_block_order);
heap_caps_free(transition->scroll_incoming_snapshot);
*transition = (screen_transition_t){0};
}
bool screenRevealStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint32_t reveal_ms, unsigned reveal_percent)
{
if (transition == NULL || frame == NULL || reveal_ms == 0U || reveal_percent == 0U) {
return false;
}
if (!ensureSnapshot(transition)) return false;
if (transition->reveal_block_order == NULL) {
transition->reveal_block_order = heap_caps_malloc(
SCREEN_REVEAL_BLOCK_COUNT * sizeof(transition->reveal_block_order[0]),
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (transition->reveal_block_order == NULL) return false;
}
// Capture the already rendered destination stage before replacing it with black blocks.
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
// Build a complete non-repeating block order, then shuffle it for this reveal.
for (uint16_t block = 0U; block < SCREEN_REVEAL_BLOCK_COUNT; ++block) {
transition->reveal_block_order[block] = block;
}
for (uint16_t block = SCREEN_REVEAL_BLOCK_COUNT - 1U; block > 0U; --block) {
const uint16_t swap_with = (uint16_t)(esp_random() % (block + 1U));
const uint16_t saved = transition->reveal_block_order[block];
transition->reveal_block_order[block] = transition->reveal_block_order[swap_with];
transition->reveal_block_order[swap_with] = saved;
}
if (reveal_percent > 100U) reveal_percent = 100U;
// Round up so the requested percentage is never under-represented by a block.
transition->reveal_target_count = (uint16_t)((SCREEN_REVEAL_BLOCK_COUNT * reveal_percent +
99U) / 100U);
transition->reveal_block_count = 0U;
transition->reveal_started_at_ms = now_ms;
transition->reveal_ms = reveal_ms;
transition->reveal_active = true;
transition->reveal_complete = false;
return true;
}
static void drawRevealBlock(uint16_t *frame, const uint16_t *snapshot, uint16_t block)
{
// Copy an unscaled 8 x 8 cell; clipped edge cells handle display sizes not divisible by eight.
const int x = (int)(block % SCREEN_REVEAL_COLUMNS) * SCREEN_REVEAL_BLOCK_PIXELS;
const int y = (int)(block / SCREEN_REVEAL_COLUMNS) * SCREEN_REVEAL_BLOCK_PIXELS;
int width = SCREEN_REVEAL_BLOCK_PIXELS;
int height = SCREEN_REVEAL_BLOCK_PIXELS;
if (x + width > LCD_WIDTH) width = LCD_WIDTH - x;
if (y + height > LCD_HEIGHT) height = LCD_HEIGHT - y;
for (int row = 0; row < height; ++row) {
memcpy(&frame[(size_t)(y + row) * LCD_WIDTH + x],
&snapshot[(size_t)(y + row) * LCD_WIDTH + x],
(size_t)width * sizeof(frame[0]));
}
}
void screenRevealDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
{
if (transition == NULL || frame == NULL || !transition->reveal_active ||
transition->snapshot == NULL || transition->reveal_block_order == NULL) return;
const uint32_t elapsed_ms = now_ms - transition->reveal_started_at_ms;
uint16_t visible_blocks = transition->reveal_target_count;
if (elapsed_ms < transition->reveal_ms) {
// Reveal only the early portion of the shuffled order until the configured percentage.
visible_blocks = (uint16_t)(((uint64_t)transition->reveal_target_count * elapsed_ms) /
transition->reveal_ms);
}
clear(frame, COLOR_BLACK);
for (uint16_t index = 0U; index < visible_blocks; ++index) {
drawRevealBlock(frame, transition->snapshot, transition->reveal_block_order[index]);
}
transition->reveal_block_count = visible_blocks;
if (elapsed_ms >= transition->reveal_ms) {
// The partial pattern has done its job: restore the complete frozen destination frame.
memcpy(frame, transition->snapshot, SCREEN_TRANSITION_FRAME_BYTES);
transition->reveal_complete = true;
}
}
bool screenRevealActive(const screen_transition_t *transition)
{
return transition != NULL && transition->reveal_active;
}
bool screenRevealComplete(const screen_transition_t *transition)
{
return transition != NULL && transition->reveal_complete;
}
void screenRevealReset(screen_transition_t *transition)
{
if (transition == NULL) return;
transition->reveal_active = false;
transition->reveal_complete = false;
transition->reveal_block_count = 0U;
}
static bool screenScrollStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step, bool scroll_in)
{
if (transition == NULL || frame == NULL || step_ms == 0U || pixels_per_step == 0U) {
return false;
}
if (!ensureSnapshot(transition)) return false;
// Capture the complete source before translating it so every step uses the same pixels.
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
transition->scroll_started_at_ms = now_ms;
transition->scroll_step_ms = step_ms;
transition->scroll_pixels_per_step = pixels_per_step;
transition->scroll_direction = direction;
transition->scroll_has_incoming = false;
transition->scroll_gap_pixels = 0U;
transition->scroll_active = true;
transition->scroll_complete = false;
transition->scroll_in = scroll_in;
return true;
}
bool screenScrollOutStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step)
{
return screenScrollStart(transition, frame, now_ms, direction, step_ms,
pixels_per_step, false);
}
bool screenScrollInStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, screen_transition_scroll_direction_t direction,
uint32_t step_ms, uint16_t pixels_per_step)
{
return screenScrollStart(transition, frame, now_ms, direction, step_ms,
pixels_per_step, true);
}
bool screenScrollSetIncoming(screen_transition_t *transition, const uint16_t *frame,
screen_transition_scroll_direction_t direction,
uint16_t gap_pixels)
{
if (transition == NULL || frame == NULL || !transition->scroll_active ||
transition->scroll_in || !ensureScrollIncomingSnapshot(transition)) {
return false;
}
// Preserve the fully rendered destination so it can travel beside the outgoing frame.
memcpy(transition->scroll_incoming_snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
transition->scroll_incoming_direction = direction;
transition->scroll_gap_pixels = gap_pixels;
transition->scroll_has_incoming = true;
return true;
}
// Horizontal scrolls cross LCD_WIDTH pixels; vertical scrolls cross LCD_HEIGHT.
// Keeping this in one helper means the caller uses the same step settings for all axes.
static uint32_t screenScrollAxisPixels(screen_transition_scroll_direction_t direction)
{
return direction == SCREEN_TRANSITION_SCROLL_UP ||
direction == SCREEN_TRANSITION_SCROLL_DOWN ? LCD_HEIGHT : LCD_WIDTH;
}
void screenScrollDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
{
if (transition == NULL || frame == NULL || !transition->scroll_active ||
transition->snapshot == NULL) return;
const uint32_t elapsed_ms = now_ms - transition->scroll_started_at_ms;
const uint32_t steps = elapsed_ms / transition->scroll_step_ms;
uint32_t offset = steps * transition->scroll_pixels_per_step;
const uint32_t axis_pixels = screenScrollAxisPixels(transition->scroll_direction);
const uint32_t travel_pixels = axis_pixels +
(transition->scroll_has_incoming ? transition->scroll_gap_pixels : 0U);
if (offset > travel_pixels) offset = travel_pixels;
// An outgoing image moves from its origin toward the selected edge. An incoming
// image starts beyond that edge and moves back to its origin on either axis.
int x = 0;
int y = 0;
switch (transition->scroll_direction) {
case SCREEN_TRANSITION_SCROLL_LEFT:
x = transition->scroll_in ? -LCD_WIDTH + (int)offset : -(int)offset;
break;
case SCREEN_TRANSITION_SCROLL_RIGHT:
x = transition->scroll_in ? LCD_WIDTH - (int)offset : (int)offset;
break;
case SCREEN_TRANSITION_SCROLL_UP:
y = transition->scroll_in ? -LCD_HEIGHT + (int)offset : -(int)offset;
break;
case SCREEN_TRANSITION_SCROLL_DOWN:
y = transition->scroll_in ? LCD_HEIGHT - (int)offset : (int)offset;
break;
}
clear(frame, COLOR_BLACK);
draw_rgb565_scaled(frame, x, y, transition->snapshot, NULL,
LCD_WIDTH, LCD_HEIGHT, LCD_WIDTH, LCD_HEIGHT);
if (transition->scroll_has_incoming) {
// The second frame moves in at the same rate. A zero gap makes the two
// frame edges touch; increasing gap_pixels leaves a controlled black band.
int incoming_x = 0;
int incoming_y = 0;
switch (transition->scroll_incoming_direction) {
case SCREEN_TRANSITION_SCROLL_LEFT:
incoming_x = -LCD_WIDTH - (int)transition->scroll_gap_pixels + (int)offset;
break;
case SCREEN_TRANSITION_SCROLL_RIGHT:
incoming_x = LCD_WIDTH + (int)transition->scroll_gap_pixels - (int)offset;
break;
case SCREEN_TRANSITION_SCROLL_UP:
incoming_y = -LCD_HEIGHT - (int)transition->scroll_gap_pixels + (int)offset;
break;
case SCREEN_TRANSITION_SCROLL_DOWN:
incoming_y = LCD_HEIGHT + (int)transition->scroll_gap_pixels - (int)offset;
break;
}
draw_rgb565_scaled(frame, incoming_x, incoming_y,
transition->scroll_incoming_snapshot, NULL,
LCD_WIDTH, LCD_HEIGHT, LCD_WIDTH, LCD_HEIGHT);
}
if (offset >= travel_pixels) transition->scroll_complete = true;
}
bool screenScrollActive(const screen_transition_t *transition)
{
return transition != NULL && transition->scroll_active;
}
bool screenScrollComplete(const screen_transition_t *transition)
{
return transition != NULL && transition->scroll_complete;
}
void screenScrollReset(screen_transition_t *transition)
{
if (transition == NULL) return;
transition->scroll_active = false;
transition->scroll_complete = false;
transition->scroll_in = false;
transition->scroll_has_incoming = false;
transition->scroll_gap_pixels = 0U;
}
bool screenSliceStart(screen_transition_t *transition, const uint16_t *frame,
uint32_t now_ms, uint16_t slice_count, uint32_t slice_ms)
{
if (transition == NULL || frame == NULL || slice_count == 0U || slice_ms == 0U) {
return false;
}
if (!ensureSnapshot(transition)) return false;
if (slice_count > (uint16_t)LCD_HEIGHT) slice_count = (uint16_t)LCD_HEIGHT;
// Capture the fully rendered outgoing scene so every strip keeps its original pixels.
memcpy(transition->snapshot, frame, SCREEN_TRANSITION_FRAME_BYTES);
transition->slice_started_at_ms = now_ms;
transition->slice_ms = slice_ms;
transition->slice_count = slice_count;
transition->slice_active = true;
transition->slice_complete = false;
return true;
}
static void drawSliceRows(uint16_t *frame, const uint16_t *snapshot, int first_row,
int row_count, int offset_x)
{
if (offset_x <= -LCD_WIDTH || offset_x >= LCD_WIDTH) return;
const int source_x = offset_x < 0 ? -offset_x : 0;
const int destination_x = offset_x > 0 ? offset_x : 0;
const int copy_width = LCD_WIDTH - (offset_x < 0 ? -offset_x : offset_x);
for (int row = 0; row < row_count; ++row) {
const size_t index = (size_t)(first_row + row) * LCD_WIDTH;
memcpy(&frame[index + destination_x], &snapshot[index + source_x],
(size_t)copy_width * sizeof(frame[0]));
}
}
void screenSliceDraw(screen_transition_t *transition, uint16_t *frame, uint32_t now_ms)
{
if (transition == NULL || frame == NULL || !transition->slice_active ||
transition->snapshot == NULL) return;
const uint32_t elapsed_ms = now_ms - transition->slice_started_at_ms;
float progress = (float)elapsed_ms / (float)transition->slice_ms;
if (progress > 1.0f) progress = 1.0f;
const int offset = (int)lroundf((float)LCD_WIDTH * progress);
clear(frame, COLOR_BLACK);
// Spread any remainder rows over the earliest strips so the complete display is covered.
const int base_height = LCD_HEIGHT / (int)transition->slice_count;
const int remainder = LCD_HEIGHT % (int)transition->slice_count;
int first_row = 0;
for (uint16_t slice = 0U; slice < transition->slice_count; ++slice) {
const int row_count = base_height + (slice < (uint16_t)remainder ? 1 : 0);
const int direction_offset = (slice & 1U) == 0U ? -offset : offset;
drawSliceRows(frame, transition->snapshot, first_row, row_count, direction_offset);
first_row += row_count;
}
if (progress >= 1.0f) transition->slice_complete = true;
}
bool screenSliceActive(const screen_transition_t *transition)
{
return transition != NULL && transition->slice_active;
}
bool screenSliceComplete(const screen_transition_t *transition)
{
return transition != NULL && transition->slice_complete;
}
void screenSliceReset(screen_transition_t *transition)
{
if (transition == NULL) return;
transition->slice_active = false;
transition->slice_complete = false;
}