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