Skip to content

Commit 439a5de

Browse files
committed
rlottie: Add support of animated stickers and emojis
1 parent c3fbe2e commit 439a5de

File tree

9 files changed

+284
-217
lines changed

9 files changed

+284
-217
lines changed

data/resources/meson.build

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ blueprints = custom_target('blueprints',
1717
'ui/sidebar-row-menu.blp',
1818
'ui/sidebar-session-switcher.blp',
1919
'ui/window.blp',
20-
'ui/content-message-sticker.blp',
2120
'ui/content-message-text.blp',
2221
'ui/content-message-document.blp',
2322
'ui/components-message-entry.blp',

data/resources/resources.gresource.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
<file compressed="true" preprocess="xml-stripblanks">ui/content-event-row.ui</file>
2323
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-document.ui</file>
2424
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-photo.ui</file>
25-
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-sticker.ui</file>
2625
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-text.ui</file>
2726
<file compressed="true" preprocess="xml-stripblanks">ui/content-send-photo-dialog.ui</file>
2827
<file compressed="true" preprocess="xml-stripblanks">ui/login.ui</file>

data/resources/style-dark.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
background-color: @dark_2;
33
}
44

5+
/* sticker must be repainted to a text color in messages */
6+
messagesticker.needs-repainting > overlay > widget > widget {
7+
filter: invert(1);
8+
}
9+
510
.chat-list row .unread-count-muted {
611
background-color: @dark_2;
712
}

data/resources/ui/content-message-sticker.blp

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/components/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
mod avatar;
22
mod message_entry;
33
mod snow;
4+
mod sticker;
45

56
pub(crate) use self::avatar::Avatar;
67
pub(crate) use self::message_entry::MessageEntry;
78
pub(crate) use self::snow::Snow;
9+
pub(crate) use self::sticker::Sticker;

src/components/sticker.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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) file_id: Cell<i32>,
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 update_sticker(&self, sticker: TdSticker, looped: bool, session: Session) {
99+
let imp = self.imp();
100+
101+
let file_id = sticker.sticker.id;
102+
if self.imp().file_id.replace(file_id) == file_id {
103+
return;
104+
}
105+
106+
// TODO: draw sticker outline with cairo
107+
self.set_child(None);
108+
109+
let aspect_ratio = sticker.width as f64 / sticker.height as f64;
110+
imp.aspect_ratio.set(aspect_ratio);
111+
112+
let format = sticker.format;
113+
114+
spawn(clone!(@weak self as obj, @weak session => async move {
115+
if sticker.sticker.local.is_downloading_completed {
116+
obj.load_sticker(&sticker.sticker.local.path, file_id, looped, format).await;
117+
} else {
118+
obj.download_sticker(file_id, &session, looped, format).await
119+
}
120+
}));
121+
}
122+
123+
pub(crate) fn play_animation(&self) {
124+
if let Some(animation) = &*self.imp().child.borrow() {
125+
if let Some(animation) = animation.downcast_ref::<rlt::Animation>() {
126+
if !animation.is_playing() {
127+
animation.play();
128+
}
129+
}
130+
}
131+
}
132+
133+
async fn download_sticker(
134+
&self,
135+
file_id: i32,
136+
session: &Session,
137+
looped: bool,
138+
format: StickerFormat,
139+
) {
140+
match session.download_file(file_id).await {
141+
Ok(file) => {
142+
self.load_sticker(&file.local.path, file_id, looped, format)
143+
.await;
144+
}
145+
Err(e) => {
146+
log::warn!("Failed to download a sticker: {e:?}");
147+
}
148+
}
149+
}
150+
151+
async fn load_sticker(&self, path: &str, file_id: i32, looped: bool, format: StickerFormat) {
152+
let path = path.to_owned();
153+
154+
let widget: gtk::Widget = match format {
155+
StickerFormat::Tgs => {
156+
let animation = rlt::Animation::from_filename(&path);
157+
animation.set_loop(looped);
158+
animation.use_cache(looped);
159+
animation.play();
160+
animation.upcast()
161+
}
162+
StickerFormat::Webp => {
163+
let result = gio::spawn_blocking(move || decode_image_from_path(&path))
164+
.await
165+
.unwrap();
166+
167+
match result {
168+
Ok(texture) => {
169+
let picture = gtk::Picture::new();
170+
picture.set_paintable(Some(&texture));
171+
picture.upcast()
172+
}
173+
Err(e) => {
174+
log::warn!("Error decoding a sticker: {e:?}");
175+
return;
176+
}
177+
}
178+
}
179+
_ => unimplemented!(),
180+
};
181+
182+
// Skip if widget was recycled by ListView
183+
if self.imp().file_id.get() == file_id {
184+
self.set_child(Some(widget));
185+
}
186+
}
187+
188+
fn set_child(&self, child: Option<gtk::Widget>) {
189+
let imp = self.imp();
190+
191+
if let Some(ref child) = child {
192+
child.set_parent(self);
193+
}
194+
195+
if let Some(old) = imp.child.replace(child) {
196+
old.unparent()
197+
}
198+
}
199+
}

src/session/content/message_row/mod.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ mod media_picture;
77
mod photo;
88
mod reply;
99
mod sticker;
10-
mod sticker_picture;
1110
mod text;
1211
mod video;
1312

@@ -20,7 +19,6 @@ use self::media_picture::MediaPicture;
2019
use self::photo::MessagePhoto;
2120
use self::reply::MessageReply;
2221
use self::sticker::MessageSticker;
23-
use self::sticker_picture::StickerPicture;
2422
use self::text::MessageText;
2523
use self::video::MessageVideo;
2624

@@ -333,11 +331,17 @@ impl MessageRow {
333331
MessageContent::MessageAnimation(_) /*| MessageContent::MessageVideo(_)*/ => {
334332
self.update_specific_content::<_, MessageVideo>(message_.clone());
335333
}
334+
MessageContent::MessageAnimatedEmoji(data)
335+
if data.animated_emoji.sticker.clone().map(
336+
|s| matches!(s.format, StickerFormat::Webp | StickerFormat::Tgs)
337+
).unwrap_or_default() => {
338+
self.update_specific_content::<_, MessageSticker>(message_.clone());
339+
}
336340
MessageContent::MessagePhoto(_) => {
337341
self.update_specific_content::<_, MessagePhoto>(message_.clone());
338342
}
339343
MessageContent::MessageSticker(data)
340-
if data.sticker.format == StickerFormat::Webp =>
344+
if matches!(data.sticker.format, StickerFormat::Webp | StickerFormat::Tgs) =>
341345
{
342346
self.update_specific_content::<_, MessageSticker>(message_.clone());
343347
}

0 commit comments

Comments
 (0)