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