|
| 1 | +use adw::prelude::*; |
| 2 | +use adw::subclass::prelude::*; |
| 3 | +use glib::clone; |
| 4 | +use gtk::{gio, glib}; |
| 5 | + |
| 6 | +use tdlib::enums::StickerFormat; |
| 7 | +use tdlib::types::Sticker as TdSticker; |
| 8 | + |
| 9 | +use crate::session::Session; |
| 10 | +use crate::utils::{decode_image_from_path, spawn}; |
| 11 | + |
| 12 | +mod imp { |
| 13 | + use super::*; |
| 14 | + use std::cell::{Cell, RefCell}; |
| 15 | + |
| 16 | + #[derive(Debug, Default, glib::Properties)] |
| 17 | + #[properties(wrapper_type = super::Sticker)] |
| 18 | + pub(crate) struct Sticker { |
| 19 | + pub(super) message_id: Cell<i64>, |
| 20 | + pub(super) aspect_ratio: Cell<f64>, |
| 21 | + pub(super) child: RefCell<Option<gtk::Widget>>, |
| 22 | + |
| 23 | + #[property(get, set = Self::set_longer_side_size)] |
| 24 | + pub(super) longer_side_size: Cell<i32>, |
| 25 | + } |
| 26 | + |
| 27 | + #[glib::object_subclass] |
| 28 | + impl ObjectSubclass for Sticker { |
| 29 | + const NAME: &'static str = "ComponentsSticker"; |
| 30 | + type Type = super::Sticker; |
| 31 | + type ParentType = gtk::Widget; |
| 32 | + } |
| 33 | + |
| 34 | + impl ObjectImpl for Sticker { |
| 35 | + fn properties() -> &'static [glib::ParamSpec] { |
| 36 | + Self::derived_properties() |
| 37 | + } |
| 38 | + |
| 39 | + fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { |
| 40 | + Self::derived_set_property(self, id, value, pspec) |
| 41 | + } |
| 42 | + |
| 43 | + fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value { |
| 44 | + Self::derived_property(self, id, pspec) |
| 45 | + } |
| 46 | + |
| 47 | + fn dispose(&self) { |
| 48 | + if let Some(child) = self.child.replace(None) { |
| 49 | + child.unparent() |
| 50 | + } |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + impl WidgetImpl for Sticker { |
| 55 | + fn measure(&self, orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) { |
| 56 | + let size = self.longer_side_size.get(); |
| 57 | + let aspect_ratio = self.aspect_ratio.get(); |
| 58 | + |
| 59 | + let min_size = 1; |
| 60 | + |
| 61 | + let size = if let gtk::Orientation::Horizontal = orientation { |
| 62 | + if aspect_ratio >= 1.0 { |
| 63 | + size |
| 64 | + } else { |
| 65 | + (size as f64 * aspect_ratio) as i32 |
| 66 | + } |
| 67 | + } else if aspect_ratio >= 1.0 { |
| 68 | + (size as f64 / aspect_ratio) as i32 |
| 69 | + } else { |
| 70 | + size |
| 71 | + } |
| 72 | + .max(min_size); |
| 73 | + |
| 74 | + (size, size, -1, -1) |
| 75 | + } |
| 76 | + |
| 77 | + fn size_allocate(&self, width: i32, height: i32, baseline: i32) { |
| 78 | + if let Some(child) = &*self.child.borrow() { |
| 79 | + child.allocate(width, height, baseline, None); |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + impl Sticker { |
| 85 | + fn set_longer_side_size(&self, size: i32) { |
| 86 | + self.longer_side_size.set(size); |
| 87 | + self.obj().queue_resize(); |
| 88 | + } |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +glib::wrapper! { |
| 93 | + pub(crate) struct Sticker(ObjectSubclass<imp::Sticker>) |
| 94 | + @extends gtk::Widget; |
| 95 | +} |
| 96 | + |
| 97 | +impl Sticker { |
| 98 | + pub(crate) fn set_sticker( |
| 99 | + &self, |
| 100 | + sticker: TdSticker, |
| 101 | + looped: bool, |
| 102 | + session: Session, |
| 103 | + message_id: i64, |
| 104 | + ) { |
| 105 | + // TODO: draw sticker outline with cairo |
| 106 | + self.set_child(None); |
| 107 | + |
| 108 | + let imp = self.imp(); |
| 109 | + |
| 110 | + let aspect_ratio = sticker.width as f64 / sticker.height as f64; |
| 111 | + imp.aspect_ratio.set(aspect_ratio); |
| 112 | + |
| 113 | + imp.message_id.set(message_id); |
| 114 | + |
| 115 | + let format = sticker.format; |
| 116 | + |
| 117 | + spawn(clone!(@weak self as obj, @weak session => async move { |
| 118 | + if sticker.sticker.local.is_downloading_completed { |
| 119 | + obj.load_sticker(&sticker.sticker.local.path, looped, format).await; |
| 120 | + } else { |
| 121 | + let file_id = sticker.sticker.id; |
| 122 | + obj.download_sticker(file_id, &session, looped, format).await |
| 123 | + } |
| 124 | + })); |
| 125 | + } |
| 126 | + |
| 127 | + pub(crate) fn play_animation(&self) { |
| 128 | + if let Some(animation) = &*self.imp().child.borrow() { |
| 129 | + if let Some(animation) = animation.downcast_ref::<rlt::Animation>() { |
| 130 | + if !animation.is_playing() { |
| 131 | + animation.play(); |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + async fn download_sticker( |
| 138 | + &self, |
| 139 | + file_id: i32, |
| 140 | + session: &Session, |
| 141 | + looped: bool, |
| 142 | + format: StickerFormat, |
| 143 | + ) { |
| 144 | + match session.download_file(file_id).await { |
| 145 | + Ok(file) => { |
| 146 | + self.load_sticker(&file.local.path, looped, format).await; |
| 147 | + } |
| 148 | + Err(e) => { |
| 149 | + log::warn!("Failed to download a sticker: {e:?}"); |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + async fn load_sticker(&self, path: &str, looped: bool, format: StickerFormat) { |
| 155 | + let path = path.to_owned(); |
| 156 | + let message_id = self.imp().message_id.get(); |
| 157 | + |
| 158 | + let widget: gtk::Widget = match format { |
| 159 | + StickerFormat::Tgs => { |
| 160 | + let animation = rlt::Animation::from_filename(&path); |
| 161 | + animation.set_loop(looped); |
| 162 | + animation.use_cache(looped); |
| 163 | + animation.play(); |
| 164 | + animation.upcast() |
| 165 | + } |
| 166 | + StickerFormat::Webp => { |
| 167 | + let result = gio::spawn_blocking(move || decode_image_from_path(&path)) |
| 168 | + .await |
| 169 | + .unwrap(); |
| 170 | + |
| 171 | + match result { |
| 172 | + Ok(texture) => { |
| 173 | + let picture = gtk::Picture::new(); |
| 174 | + picture.set_paintable(Some(&texture)); |
| 175 | + picture.upcast() |
| 176 | + } |
| 177 | + Err(e) => { |
| 178 | + log::warn!("Error decoding a sticker: {e:?}"); |
| 179 | + return; |
| 180 | + } |
| 181 | + } |
| 182 | + } |
| 183 | + _ => unimplemented!(), |
| 184 | + }; |
| 185 | + |
| 186 | + if self.imp().message_id.get() != message_id { |
| 187 | + return; |
| 188 | + } |
| 189 | + |
| 190 | + self.set_child(Some(widget)); |
| 191 | + } |
| 192 | + |
| 193 | + fn set_child(&self, child: Option<gtk::Widget>) { |
| 194 | + let imp = self.imp(); |
| 195 | + |
| 196 | + if let Some(ref child) = child { |
| 197 | + child.set_parent(self); |
| 198 | + } |
| 199 | + |
| 200 | + if let Some(old) = imp.child.replace(child) { |
| 201 | + old.unparent() |
| 202 | + } |
| 203 | + } |
| 204 | +} |
0 commit comments