notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 1b15d8a708f61d561da31b4e7268520d738086ba
parent 19f69e5b4b1b6b336043dc80c61ad1d8c8a0d935
Author: kernelkind <kernelkind@gmail.com>
Date:   Sat, 22 Nov 2025 20:27:52 -0700

feat(gif): new ProcessedGifFrame

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck/src/media/gif.rs | 95++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 94 insertions(+), 1 deletion(-)

diff --git a/crates/notedeck/src/media/gif.rs b/crates/notedeck/src/media/gif.rs @@ -3,8 +3,8 @@ use std::{ time::{Instant, SystemTime}, }; -use crate::media::AnimationMode; use crate::AnimationOld; +use crate::{media::AnimationMode, Animation}; use crate::{GifState, GifStateMap, TextureStateOld, TexturedImage, TexturesCache}; use egui::TextureHandle; use std::time::Duration; @@ -120,6 +120,99 @@ fn process_gif_frame_old( } } +pub(crate) struct ProcessedGifFrame<'a> { + pub texture: &'a TextureHandle, + pub maybe_new_state: Option<GifState>, + pub repaint_at: Option<SystemTime>, +} + +/// Process a gif state frame, and optionally present a new +/// state and when to repaint it +pub(crate) fn process_gif_frame<'a>( + animation: &'a Animation, + frame_state: Option<&GifState>, + animation_mode: AnimationMode, +) -> ProcessedGifFrame<'a> { + let now = Instant::now(); + + match frame_state { + Some(prev_state) => { + let should_advance = animation_mode.can_animate() + && (now - prev_state.last_frame_rendered >= prev_state.last_frame_duration); + + if should_advance { + let maybe_new_index = if prev_state.last_frame_index < animation.num_frames() - 1 { + prev_state.last_frame_index + 1 + } else { + 0 + }; + + match animation.get_frame(maybe_new_index) { + Some(frame) => { + let next_frame_time = match animation_mode { + AnimationMode::Continuous { fps } => match fps { + Some(fps) => { + let max_delay_ms = Duration::from_millis((1000.0 / fps) as u64); + SystemTime::now().checked_add(frame.delay.max(max_delay_ms)) + } + None => SystemTime::now().checked_add(frame.delay), + }, + + AnimationMode::NoAnimation | AnimationMode::Reactive => None, + }; + + ProcessedGifFrame { + texture: &frame.texture, + maybe_new_state: Some(GifState { + last_frame_rendered: now, + last_frame_duration: frame.delay, + next_frame_time, + last_frame_index: maybe_new_index, + }), + repaint_at: next_frame_time, + } + } + None => { + let (texture, maybe_new_state) = + match animation.get_frame(prev_state.last_frame_index) { + Some(frame) => (&frame.texture, None), + None => (&animation.first_frame.texture, None), + }; + + ProcessedGifFrame { + texture, + maybe_new_state, + repaint_at: prev_state.next_frame_time, + } + } + } + } else { + let (texture, maybe_new_state) = + match animation.get_frame(prev_state.last_frame_index) { + Some(frame) => (&frame.texture, None), + None => (&animation.first_frame.texture, None), + }; + + ProcessedGifFrame { + texture, + maybe_new_state, + repaint_at: prev_state.next_frame_time, + } + } + } + None => ProcessedGifFrame { + texture: &animation.first_frame.texture, + maybe_new_state: Some(GifState { + last_frame_rendered: now, + last_frame_duration: animation.first_frame.delay, + next_frame_time: None, + last_frame_index: 0, + }), + repaint_at: None, + }, + } +} + pub fn ensure_latest_texture( ui: &egui::Ui, url: &str,