ui: add AnimationMode to control GIF rendering behavior

Introduces an `AnimationMode` enum with `Reactive`, `Continuous`, and
`NoAnimation` variants to allow fine-grained control over GIF playback
across the UI. This supports performance optimizations and accessibility
features, such as disabling animations when requested.

- Plumbs AnimationMode through image rendering paths
- Replaces hardcoded gif frame logic with reusable `process_gif_frame`
- Supports customizable FPS in Continuous mode
- Enables global animation opt-out via `NoteOptions::NoAnimations`
- Applies mode-specific logic in profile pictures, posts, media carousels, and viewer

Animation behavior by context
-----------------------------

- Profile pictures: Reactive (render only on interaction/activity)
- PostView: NoAnimation if disabled in NoteOptions, else Continuous (uncapped)
- Media carousels: NoAnimation or Continuous (capped at 24fps)
- Viewer/gallery: Always Continuous (full animation)

In the future, we can customize these by power settings.

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2025-08-04 13:38:27 -07:00
parent 54b86ee5a6
commit b94e715539
8 changed files with 206 additions and 73 deletions

View File

@@ -15,6 +15,7 @@ use egui::{
use enostr::{FilledKeypair, FullKeypair, NoteId, Pubkey, RelayPool};
use nostrdb::{Ndb, Transaction};
use notedeck::media::gif::ensure_latest_texture;
use notedeck::media::AnimationMode;
use notedeck::{get_render_state, JobsCache, PixelDimensions, RenderState};
use notedeck_ui::{
@@ -37,6 +38,7 @@ pub struct PostView<'a, 'd> {
inner_rect: egui::Rect,
note_options: NoteOptions,
jobs: &'a mut JobsCache,
animation_mode: AnimationMode,
}
#[derive(Clone)]
@@ -110,6 +112,11 @@ impl<'a, 'd> PostView<'a, 'd> {
note_options: NoteOptions,
jobs: &'a mut JobsCache,
) -> Self {
let animation_mode = if note_options.contains(NoteOptions::NoAnimations) {
AnimationMode::NoAnimation
} else {
AnimationMode::Continuous { fps: None }
};
PostView {
note_context,
draft,
@@ -117,6 +124,7 @@ impl<'a, 'd> PostView<'a, 'd> {
post_type,
inner_rect,
note_options,
animation_mode,
jobs,
}
}
@@ -129,6 +137,11 @@ impl<'a, 'd> PostView<'a, 'd> {
PostView::id().with("scroll")
}
pub fn animation_mode(mut self, animation_mode: AnimationMode) -> Self {
self.animation_mode = animation_mode;
self
}
fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> egui::Response {
ui.spacing_mut().item_spacing.x = 12.0;
@@ -492,6 +505,7 @@ impl<'a, 'd> PostView<'a, 'd> {
height,
cur_state,
url,
self.animation_mode,
)
}
to_remove.reverse();
@@ -582,6 +596,7 @@ fn render_post_view_media(
height: u32,
render_state: RenderState,
url: &str,
animation_mode: AnimationMode,
) {
match render_state.texture_state {
notedeck::TextureState::Pending => {
@@ -605,7 +620,7 @@ fn render_post_view_media(
.to_vec();
let texture_handle =
ensure_latest_texture(ui, url, render_state.gifs, renderable_media);
ensure_latest_texture(ui, url, render_state.gifs, renderable_media, animation_mode);
let img_resp = ui.add(
egui::Image::new(&texture_handle)
.max_size(size)