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