gdk_pixbuf/subclass/
pixbuf_animation_iter.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 [`PixbufAnimationIter`].
5
6use std::{
7    sync::OnceLock,
8    time::{Duration, SystemTime},
9};
10
11use glib::{prelude::*, subclass::prelude::*, translate::*};
12
13use crate::{ffi, Pixbuf, PixbufAnimationIter};
14
15pub trait PixbufAnimationIterImpl:
16    ObjectImpl + ObjectSubclass<Type: IsA<PixbufAnimationIter>>
17{
18    // rustdoc-stripper-ignore-next
19    /// Time in milliseconds, returning `None` implies showing the same pixbuf forever.
20    // rustdoc-stripper-ignore-next-stop
21    /// Gets the number of milliseconds the current pixbuf should be displayed,
22    /// or -1 if the current pixbuf should be displayed forever.
23    ///
24    /// The `g_timeout_add()` function conveniently takes a timeout in milliseconds,
25    /// so you can use a timeout to schedule the next update.
26    ///
27    /// Note that some formats, like GIF, might clamp the timeout values in the
28    /// image file to avoid updates that are just too quick. The minimum timeout
29    /// for GIF images is currently 20 milliseconds.
30    ///
31    /// # Deprecated since 2.44
32    ///
33    /// Use a different image loading library for animatable assets
34    ///
35    /// # Returns
36    ///
37    /// delay time in milliseconds (thousandths of a second)
38    fn delay_time(&self) -> Option<Duration> {
39        self.parent_delay_time()
40    }
41
42    /// Gets the current pixbuf which should be displayed.
43    ///
44    /// The pixbuf might not be the same size as the animation itself
45    /// (gdk_pixbuf_animation_get_width(), gdk_pixbuf_animation_get_height()).
46    ///
47    /// This pixbuf should be displayed for gdk_pixbuf_animation_iter_get_delay_time()
48    /// milliseconds.
49    ///
50    /// The caller of this function does not own a reference to the returned
51    /// pixbuf; the returned pixbuf will become invalid when the iterator
52    /// advances to the next frame, which may happen anytime you call
53    /// gdk_pixbuf_animation_iter_advance().
54    ///
55    /// Copy the pixbuf to keep it (don't just add a reference), as it may get
56    /// recycled as you advance the iterator.
57    ///
58    /// # Deprecated since 2.44
59    ///
60    /// Use a different image loading library for animatable assets
61    ///
62    /// # Returns
63    ///
64    /// the pixbuf to be displayed
65    fn pixbuf(&self) -> Pixbuf {
66        self.parent_pixbuf()
67    }
68
69    /// Used to determine how to respond to the area_updated signal on
70    /// #GdkPixbufLoader when loading an animation.
71    ///
72    /// The `::area_updated` signal is emitted for an area of the frame currently
73    /// streaming in to the loader. So if you're on the currently loading frame,
74    /// you will need to redraw the screen for the updated area.
75    ///
76    /// # Deprecated since 2.44
77    ///
78    /// Use a different image loading library for animatable assets
79    ///
80    /// # Returns
81    ///
82    /// `TRUE` if the frame we're on is partially loaded, or the last frame
83    fn on_currently_loading_frame(&self) -> bool {
84        self.parent_on_currently_loading_frame()
85    }
86
87    /// Possibly advances an animation to a new frame.
88    ///
89    /// Chooses the frame based on the start time passed to
90    /// gdk_pixbuf_animation_get_iter().
91    ///
92    /// @current_time would normally come from g_get_current_time(), and
93    /// must be greater than or equal to the time passed to
94    /// gdk_pixbuf_animation_get_iter(), and must increase or remain
95    /// unchanged each time gdk_pixbuf_animation_iter_get_pixbuf() is
96    /// called. That is, you can't go backward in time; animations only
97    /// play forward.
98    ///
99    /// As a shortcut, pass `NULL` for the current time and g_get_current_time()
100    /// will be invoked on your behalf. So you only need to explicitly pass
101    /// @current_time if you're doing something odd like playing the animation
102    /// at double speed.
103    ///
104    /// If this function returns `FALSE`, there's no need to update the animation
105    /// display, assuming the display had been rendered prior to advancing;
106    /// if `TRUE`, you need to call gdk_pixbuf_animation_iter_get_pixbuf()
107    /// and update the display with the new pixbuf.
108    ///
109    /// # Deprecated since 2.44
110    ///
111    /// Use a different image loading library for animatable assets
112    /// ## `current_time`
113    /// current time
114    ///
115    /// # Returns
116    ///
117    /// `TRUE` if the image may need updating
118    fn advance(&self, current_time: SystemTime) -> bool {
119        self.parent_advance(current_time)
120    }
121}
122
123pub trait PixbufAnimationIterImplExt: PixbufAnimationIterImpl {
124    fn parent_delay_time(&self) -> Option<Duration> {
125        unsafe {
126            let data = Self::type_data();
127            let parent_class =
128                data.as_ref().parent_class() as *mut ffi::GdkPixbufAnimationIterClass;
129            let f = (*parent_class)
130                .get_delay_time
131                .expect("No parent class implementation for \"get_delay_time\"");
132
133            let time = f(self
134                .obj()
135                .unsafe_cast_ref::<PixbufAnimationIter>()
136                .to_glib_none()
137                .0);
138            if time < 0 {
139                None
140            } else {
141                Some(Duration::from_millis(time as u64))
142            }
143        }
144    }
145
146    fn parent_pixbuf(&self) -> Pixbuf {
147        unsafe {
148            let data = Self::type_data();
149            let parent_class =
150                data.as_ref().parent_class() as *mut ffi::GdkPixbufAnimationIterClass;
151            let f = (*parent_class)
152                .get_pixbuf
153                .expect("No parent class implementation for \"get_pixbuf\"");
154
155            from_glib_none(f(self
156                .obj()
157                .unsafe_cast_ref::<PixbufAnimationIter>()
158                .to_glib_none()
159                .0))
160        }
161    }
162
163    fn parent_on_currently_loading_frame(&self) -> bool {
164        unsafe {
165            let data = Self::type_data();
166            let parent_class =
167                data.as_ref().parent_class() as *mut ffi::GdkPixbufAnimationIterClass;
168            let f = (*parent_class)
169                .on_currently_loading_frame
170                .expect("No parent class implementation for \"on_currently_loading_frame\"");
171
172            from_glib(f(self
173                .obj()
174                .unsafe_cast_ref::<PixbufAnimationIter>()
175                .to_glib_none()
176                .0))
177        }
178    }
179
180    fn parent_advance(&self, current_time: SystemTime) -> bool {
181        unsafe {
182            let data = Self::type_data();
183            let parent_class =
184                data.as_ref().parent_class() as *mut ffi::GdkPixbufAnimationIterClass;
185            let f = (*parent_class)
186                .advance
187                .expect("No parent class implementation for \"advance\"");
188
189            let diff = current_time
190                .duration_since(SystemTime::UNIX_EPOCH)
191                .expect("failed to convert time");
192            let time = glib::ffi::GTimeVal {
193                tv_sec: diff.as_secs() as _,
194                tv_usec: diff.subsec_micros() as _,
195            };
196            from_glib(f(
197                self.obj()
198                    .unsafe_cast_ref::<PixbufAnimationIter>()
199                    .to_glib_none()
200                    .0,
201                &time,
202            ))
203        }
204    }
205}
206
207impl<T: PixbufAnimationIterImpl> PixbufAnimationIterImplExt for T {}
208
209unsafe impl<T: PixbufAnimationIterImpl> IsSubclassable<T> for PixbufAnimationIter {
210    fn class_init(class: &mut ::glib::Class<Self>) {
211        Self::parent_class_init::<T>(class);
212
213        let klass = class.as_mut();
214        klass.get_delay_time = Some(animation_iter_get_delay_time::<T>);
215        klass.get_pixbuf = Some(animation_iter_get_pixbuf::<T>);
216        klass.on_currently_loading_frame = Some(animation_iter_on_currently_loading_frame::<T>);
217        klass.advance = Some(animation_iter_advance::<T>);
218    }
219}
220
221unsafe extern "C" fn animation_iter_get_delay_time<T: PixbufAnimationIterImpl>(
222    ptr: *mut ffi::GdkPixbufAnimationIter,
223) -> i32 {
224    let instance = &*(ptr as *mut T::Instance);
225    let imp = instance.imp();
226
227    imp.delay_time().map(|t| t.as_millis() as i32).unwrap_or(-1)
228}
229
230unsafe extern "C" fn animation_iter_get_pixbuf<T: PixbufAnimationIterImpl>(
231    ptr: *mut ffi::GdkPixbufAnimationIter,
232) -> *mut ffi::GdkPixbuf {
233    let instance = &*(ptr as *mut T::Instance);
234    let imp = instance.imp();
235
236    let pixbuf = imp.pixbuf();
237    // Ensure that the pixbuf stays alive until the next call
238    let pixbuf_quark = {
239        static QUARK: OnceLock<glib::Quark> = OnceLock::new();
240        *QUARK.get_or_init(|| glib::Quark::from_str("gtk-rs-subclass-pixbuf"))
241    };
242    imp.obj().set_qdata(pixbuf_quark, pixbuf.clone());
243    pixbuf.to_glib_none().0
244}
245
246unsafe extern "C" fn animation_iter_on_currently_loading_frame<T: PixbufAnimationIterImpl>(
247    ptr: *mut ffi::GdkPixbufAnimationIter,
248) -> glib::ffi::gboolean {
249    let instance = &*(ptr as *mut T::Instance);
250    let imp = instance.imp();
251
252    imp.on_currently_loading_frame().into_glib()
253}
254
255unsafe extern "C" fn animation_iter_advance<T: PixbufAnimationIterImpl>(
256    ptr: *mut ffi::GdkPixbufAnimationIter,
257    current_time_ptr: *const glib::ffi::GTimeVal,
258) -> glib::ffi::gboolean {
259    let instance = &*(ptr as *mut T::Instance);
260    let imp = instance.imp();
261
262    let current_time = SystemTime::UNIX_EPOCH
263        + Duration::from_secs((*current_time_ptr).tv_sec.try_into().unwrap())
264        + Duration::from_micros((*current_time_ptr).tv_usec.try_into().unwrap());
265
266    imp.advance(current_time).into_glib()
267}