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