From d1c7a5a239eaa7f9cf56cb38c29552f261c2235d Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 15:06:25 -0500 Subject: [PATCH] handle gif state Signed-off-by: kernelkind --- crates/notedeck_columns/src/gif.rs | 122 +++++++++++++++++++++++++++++ crates/notedeck_columns/src/lib.rs | 1 + 2 files changed, 123 insertions(+) create mode 100644 crates/notedeck_columns/src/gif.rs diff --git a/crates/notedeck_columns/src/gif.rs b/crates/notedeck_columns/src/gif.rs new file mode 100644 index 0000000..714ded2 --- /dev/null +++ b/crates/notedeck_columns/src/gif.rs @@ -0,0 +1,122 @@ +use std::{ + sync::mpsc::TryRecvError, + time::{Instant, SystemTime}, +}; + +use egui::TextureHandle; +use notedeck::{GifState, GifStateMap, TexturedImage}; + +pub struct LatextTexture<'a> { + pub texture: &'a TextureHandle, + pub request_next_repaint: Option, +} + +/// This is necessary because other repaint calls can effectively steal our repaint request. +/// So we must keep on requesting to repaint at our desired time to ensure our repaint goes through. +/// See [`egui::Context::request_repaint_after`] +pub fn handle_repaint<'a>(ui: &egui::Ui, latest: LatextTexture<'a>) -> &'a TextureHandle { + if let Some(repaint) = latest.request_next_repaint { + if let Ok(dur) = repaint.duration_since(SystemTime::now()) { + ui.ctx().request_repaint_after(dur); + } + } + latest.texture +} + +#[must_use = "caller should pass the return value to `gif::handle_repaint`"] +pub fn retrieve_latest_texture<'a>( + url: &str, + gifs: &'a mut GifStateMap, + cached_image: &'a mut TexturedImage, +) -> LatextTexture<'a> { + match cached_image { + TexturedImage::Static(texture) => LatextTexture { + texture, + request_next_repaint: None, + }, + TexturedImage::Animated(animation) => { + if let Some(receiver) = &animation.receiver { + loop { + match receiver.try_recv() { + Ok(frame) => animation.other_frames.push(frame), + Err(TryRecvError::Empty) => { + break; + } + Err(TryRecvError::Disconnected) => { + animation.receiver = None; + break; + } + } + } + } + + let now = Instant::now(); + let (texture, maybe_new_state, request_next_repaint) = match gifs.get(url) { + Some(prev_state) => { + let should_advance = + now - prev_state.last_frame_rendered >= prev_state.last_frame_duration; + + if should_advance { + let maybe_new_index = if animation.receiver.is_some() + || 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 = SystemTime::now().checked_add(frame.delay); + ( + &frame.texture, + Some(GifState { + last_frame_rendered: now, + last_frame_duration: frame.delay, + next_frame_time, + last_frame_index: maybe_new_index, + }), + next_frame_time, + ) + } + None => { + let (tex, state) = + match animation.get_frame(prev_state.last_frame_index) { + Some(frame) => (&frame.texture, None), + None => (&animation.first_frame.texture, None), + }; + + (tex, state, prev_state.next_frame_time) + } + } + } else { + let (tex, state) = match animation.get_frame(prev_state.last_frame_index) { + Some(frame) => (&frame.texture, None), + None => (&animation.first_frame.texture, None), + }; + (tex, state, prev_state.next_frame_time) + } + } + None => ( + &animation.first_frame.texture, + Some(GifState { + last_frame_rendered: now, + last_frame_duration: animation.first_frame.delay, + next_frame_time: None, + last_frame_index: 0, + }), + None, + ), + }; + + if let Some(new_state) = maybe_new_state { + gifs.insert(url.to_owned(), new_state); + } + + LatextTexture { + texture, + request_next_repaint, + } + } + } +} diff --git a/crates/notedeck_columns/src/lib.rs b/crates/notedeck_columns/src/lib.rs index 3ae0e64..a68e6f2 100644 --- a/crates/notedeck_columns/src/lib.rs +++ b/crates/notedeck_columns/src/lib.rs @@ -15,6 +15,7 @@ mod deck_state; mod decks; mod draft; mod frame_history; +mod gif; mod images; mod key_parsing; pub mod login_manager;