gdk_pixbuf/subclass/
pixbuf_animation.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3// rustdoc-stripper-ignore-next
4//! Traits intended for subclassing [`PixbufAnimation`].
5
6use std::{
7    mem::MaybeUninit,
8    sync::OnceLock,
9    time::{Duration, SystemTime},
10};
11
12use glib::{prelude::*, subclass::prelude::*, translate::*};
13
14use crate::{ffi, Pixbuf, PixbufAnimation, PixbufAnimationIter};
15
16pub trait PixbufAnimationImpl: ObjectImpl + ObjectSubclass<Type: IsA<PixbufAnimation>> {
17    /// Checks whether the animation is a static image.
18    ///
19    /// If you load a file with gdk_pixbuf_animation_new_from_file() and it
20    /// turns out to be a plain, unanimated image, then this function will
21    /// return `TRUE`. Use gdk_pixbuf_animation_get_static_image() to retrieve
22    /// the image.
23    ///
24    /// # Deprecated since 2.44
25    ///
26    /// Use a different image loading library for animatable assets
27    ///
28    /// # Returns
29    ///
30    /// `TRUE` if the "animation" was really just an image
31    fn is_static_image(&self) -> bool {
32        self.parent_is_static_image()
33    }
34
35    /// Retrieves a static image for the animation.
36    ///
37    /// If an animation is really just a plain image (has only one frame),
38    /// this function returns that image.
39    ///
40    /// If the animation is an animation, this function returns a reasonable
41    /// image to use as a static unanimated image, which might be the first
42    /// frame, or something more sophisticated depending on the file format.
43    ///
44    /// If an animation hasn't loaded any frames yet, this function will
45    /// return `NULL`.
46    ///
47    /// # Deprecated since 2.44
48    ///
49    /// Use a different image loading library for animatable assets
50    ///
51    /// # Returns
52    ///
53    /// unanimated image representing the animation
54    fn static_image(&self) -> Option<Pixbuf> {
55        self.parent_static_image()
56    }
57
58    /// fills @width and @height with the frame size of the animation.
59    fn size(&self) -> (i32, i32) {
60        self.parent_size()
61    }
62
63    /// Get an iterator for displaying an animation.
64    ///
65    /// The iterator provides the frames that should be displayed at a
66    /// given time.
67    ///
68    /// @start_time would normally come from g_get_current_time(), and marks
69    /// the beginning of animation playback. After creating an iterator, you
70    /// should immediately display the pixbuf returned by
71    /// gdk_pixbuf_animation_iter_get_pixbuf(). Then, you should install
72    /// a timeout (with g_timeout_add()) or by some other mechanism ensure
73    /// that you'll update the image after
74    /// gdk_pixbuf_animation_iter_get_delay_time() milliseconds. Each time
75    /// the image is updated, you should reinstall the timeout with the new,
76    /// possibly-changed delay time.
77    ///
78    /// As a shortcut, if @start_time is `NULL`, the result of
79    /// g_get_current_time() will be used automatically.
80    ///
81    /// To update the image (i.e. possibly change the result of
82    /// gdk_pixbuf_animation_iter_get_pixbuf() to a new frame of the animation),
83    /// call gdk_pixbuf_animation_iter_advance().
84    ///
85    /// If you're using #GdkPixbufLoader, in addition to updating the image
86    /// after the delay time, you should also update it whenever you
87    /// receive the area_updated signal and
88    /// gdk_pixbuf_animation_iter_on_currently_loading_frame() returns
89    /// `TRUE`. In this case, the frame currently being fed into the loader
90    /// has received new data, so needs to be refreshed. The delay time for
91    /// a frame may also be modified after an area_updated signal, for
92    /// example if the delay time for a frame is encoded in the data after
93    /// the frame itself. So your timeout should be reinstalled after any
94    /// area_updated signal.
95    ///
96    /// A delay time of -1 is possible, indicating "infinite".
97    ///
98    /// # Deprecated since 2.44
99    ///
100    /// Use a different image loading library for animatable assets
101    /// ## `start_time`
102    /// time when the animation starts playing
103    ///
104    /// # Returns
105    ///
106    /// an iterator to move over the animation
107    fn iter(&self, start_time: SystemTime) -> PixbufAnimationIter {
108        self.parent_iter(start_time)
109    }
110}
111
112pub trait PixbufAnimationImplExt: PixbufAnimationImpl {
113    fn parent_is_static_image(&self) -> bool {
114        unsafe {
115            let data = Self::type_data();
116            let parent_class = data.as_ref().parent_class() as *mut ffi::GdkPixbufAnimationClass;
117            let f = (*parent_class)
118                .is_static_image
119                .expect("No parent class implementation for \"is_static_image\"");
120
121            from_glib(f(self
122                .obj()
123                .unsafe_cast_ref::<PixbufAnimation>()
124                .to_glib_none()
125                .0))
126        }
127    }
128
129    fn parent_static_image(&self) -> Option<Pixbuf> {
130        unsafe {
131            let data = Self::type_data();
132            let parent_class = data.as_ref().parent_class() as *mut ffi::GdkPixbufAnimationClass;
133            let f = (*parent_class)
134                .get_static_image
135                .expect("No parent class implementation for \"get_static_image\"");
136
137            from_glib_none(f(self
138                .obj()
139                .unsafe_cast_ref::<PixbufAnimation>()
140                .to_glib_none()
141                .0))
142        }
143    }
144
145    fn parent_size(&self) -> (i32, i32) {
146        unsafe {
147            let data = Self::type_data();
148            let parent_class = data.as_ref().parent_class() as *mut ffi::GdkPixbufAnimationClass;
149            let f = (*parent_class)
150                .get_size
151                .expect("No parent class implementation for \"get_size\"");
152            let mut width = MaybeUninit::uninit();
153            let mut height = MaybeUninit::uninit();
154            f(
155                self.obj()
156                    .unsafe_cast_ref::<PixbufAnimation>()
157                    .to_glib_none()
158                    .0,
159                width.as_mut_ptr(),
160                height.as_mut_ptr(),
161            );
162            (width.assume_init(), height.assume_init())
163        }
164    }
165
166    fn parent_iter(&self, start_time: SystemTime) -> PixbufAnimationIter {
167        unsafe {
168            let data = Self::type_data();
169            let parent_class = data.as_ref().parent_class() as *mut ffi::GdkPixbufAnimationClass;
170            let f = (*parent_class)
171                .get_iter
172                .expect("No parent class implementation for \"get_iter\"");
173
174            let diff = start_time
175                .duration_since(SystemTime::UNIX_EPOCH)
176                .expect("failed to convert time");
177            let time = glib::ffi::GTimeVal {
178                tv_sec: diff.as_secs() as _,
179                tv_usec: diff.subsec_micros() as _,
180            };
181            from_glib_full(f(
182                self.obj()
183                    .unsafe_cast_ref::<PixbufAnimation>()
184                    .to_glib_none()
185                    .0,
186                &time,
187            ))
188        }
189    }
190}
191
192impl<T: PixbufAnimationImpl> PixbufAnimationImplExt for T {}
193
194unsafe impl<T: PixbufAnimationImpl> IsSubclassable<T> for PixbufAnimation {
195    fn class_init(class: &mut ::glib::Class<Self>) {
196        Self::parent_class_init::<T>(class);
197
198        let klass = class.as_mut();
199        klass.get_static_image = Some(animation_get_static_image::<T>);
200        klass.get_size = Some(animation_get_size::<T>);
201        klass.get_iter = Some(animation_get_iter::<T>);
202        klass.is_static_image = Some(animation_is_static_image::<T>);
203    }
204}
205
206unsafe extern "C" fn animation_is_static_image<T: PixbufAnimationImpl>(
207    ptr: *mut ffi::GdkPixbufAnimation,
208) -> glib::ffi::gboolean {
209    let instance = &*(ptr as *mut T::Instance);
210    let imp = instance.imp();
211
212    imp.is_static_image().into_glib()
213}
214
215unsafe extern "C" fn animation_get_size<T: PixbufAnimationImpl>(
216    ptr: *mut ffi::GdkPixbufAnimation,
217    width_ptr: *mut libc::c_int,
218    height_ptr: *mut libc::c_int,
219) {
220    if width_ptr.is_null() && height_ptr.is_null() {
221        return;
222    }
223
224    let instance = &*(ptr as *mut T::Instance);
225    let imp = instance.imp();
226
227    let (width, height) = imp.size();
228    if !width_ptr.is_null() {
229        *width_ptr = width;
230    }
231    if !height_ptr.is_null() {
232        *height_ptr = height;
233    }
234}
235
236unsafe extern "C" fn animation_get_static_image<T: PixbufAnimationImpl>(
237    ptr: *mut ffi::GdkPixbufAnimation,
238) -> *mut ffi::GdkPixbuf {
239    let instance = &*(ptr as *mut T::Instance);
240    let imp = instance.imp();
241
242    let instance = imp.obj();
243    let static_image = imp.static_image();
244    // Ensure that a) the static image stays alive as long as the animation instance and b) that
245    // the same static image is returned every time. This is a requirement by the gdk-pixbuf API.
246    let static_image_quark = {
247        static QUARK: OnceLock<glib::Quark> = OnceLock::new();
248        *QUARK.get_or_init(|| glib::Quark::from_str("gtk-rs-subclass-static-image"))
249    };
250    if let Some(old_image) = instance.qdata::<Option<Pixbuf>>(static_image_quark) {
251        let old_image = old_image.as_ref();
252
253        if let Some(old_image) = old_image {
254            assert_eq!(
255                Some(old_image),
256                static_image.as_ref(),
257                "Did not return same static image again"
258            );
259        }
260    }
261    instance.set_qdata(static_image_quark, static_image.clone());
262    static_image.to_glib_none().0
263}
264
265unsafe extern "C" fn animation_get_iter<T: PixbufAnimationImpl>(
266    ptr: *mut ffi::GdkPixbufAnimation,
267    start_time_ptr: *const glib::ffi::GTimeVal,
268) -> *mut ffi::GdkPixbufAnimationIter {
269    let instance = &*(ptr as *mut T::Instance);
270    let imp = instance.imp();
271
272    let start_time = SystemTime::UNIX_EPOCH
273        + Duration::from_secs((*start_time_ptr).tv_sec.try_into().unwrap())
274        + Duration::from_micros((*start_time_ptr).tv_usec.try_into().unwrap());
275
276    imp.iter(start_time).into_glib_ptr()
277}