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}