Skip to content

Commit 32d1079

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

File tree

9 files changed

+288
-217
lines changed

9 files changed

+288
-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: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
}

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)