feat(image-picker): play animated thumbnails on hover

Hovering a GIF/WebP thumbnail in the picker now previews its animation:

- The picker's Thumb gains a lazily-loaded frame sequence (via LoadAnimatedRGBA)
  fetched the first time an animatable (.gif/.webp) thumbnail is hovered; still
  images and other formats keep their single static thumbnail (no re-decode).
- On hover the current frame is drawn on the ImGui clock; leaving the thumbnail
  returns it to the still frame-0 preview.
- A clear-on-read flag (ImagePicker::consumeAnimationActive) is OR'd into
  ConsumeContactsAvatarAnimation so the render loop keeps drawing while a hover
  preview plays and idles otherwise. clearThumbs frees all frame textures too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 11:22:57 -05:00
parent 4b8d80b2fc
commit fd0cab41f0
2 changed files with 70 additions and 7 deletions

View File

@@ -254,7 +254,13 @@ static void deleteAvatarLibraryFile(const std::string& path) {
// UI-loop accessor: true if an animated avatar frame was drawn since the last call (clear-on-read), so
// the main render loop keeps producing frames while an animation plays and idles once it stops / the
// contacts view is hidden. Declared in contacts_tab.h.
bool ConsumeContactsAvatarAnimation() { bool v = s_avatarAnimatedThisFrame; s_avatarAnimatedThisFrame = false; return v; }
bool ConsumeContactsAvatarAnimation() {
// Also consume the image picker's hover-preview animation flag (the picker renders within this tab).
bool picker = ImagePicker::consumeAnimationActive();
bool v = s_avatarAnimatedThisFrame || picker;
s_avatarAnimatedThisFrame = false;
return v;
}
void RenderContactsTab(App* app)
{