Skip to content

Commit 1594cd5

Browse files
committed
rlottie: Add support of animated stickers and emojis
1 parent 9f2dfab commit 1594cd5

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: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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) { child.unparent() }
49+
}
50+
}
51+
52+
impl WidgetImpl for Sticker {
53+
fn measure(&self, orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) {
54+
let size = self.longer_side_size.get();
55+
let aspect_ratio = self.aspect_ratio.get();
56+
57+
let min_size = 1;
58+
59+
let size = if let gtk::Orientation::Horizontal = orientation {
60+
if aspect_ratio >= 1.0 {
61+
size
62+
} else {
63+
(size as f64 * aspect_ratio) as i32
64+
}
65+
} else if aspect_ratio >= 1.0 {
66+
(size as f64 / aspect_ratio) as i32
67+
} else {
68+
size
69+
}
70+
.max(min_size);
71+
72+
(size, size, -1, -1)
73+
}
74+
75+
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
76+
if let Some(child) = &*self.child.borrow() {
77+
child.allocate(width, height, baseline, None);
78+
}
79+
}
80+
}
81+
82+
impl Sticker {
83+
fn set_longer_side_size(&self, size: i32) {
84+
self.longer_side_size.set(size);
85+
self.obj().queue_resize();
86+
}
87+
}
88+
}
89+
90+
glib::wrapper! {
91+
pub(crate) struct Sticker(ObjectSubclass<imp::Sticker>)
92+
@extends gtk::Widget;
93+
}
94+
95+
impl Sticker {
96+
pub(crate) fn set_sticker(
97+
&self,
98+
sticker: TdSticker,
99+
looped: bool,
100+
session: Session,
101+
message_id: i64,
102+
) {
103+
// TODO: draw sticker outline with cairo
104+
self.set_child(None);
105+
106+
let imp = self.imp();
107+
108+
let aspect_ratio = sticker.width as f64 / sticker.height as f64;
109+
imp.aspect_ratio.set(aspect_ratio);
110+
111+
imp.message_id.set(message_id);
112+
113+
let format = sticker.format;
114+
115+
spawn(clone!(@weak self as obj, @weak session => async move {
116+
if sticker.sticker.local.is_downloading_completed {
117+
obj.load_sticker(&sticker.sticker.local.path, looped, format).await;
118+
} else {
119+
let file_id = sticker.sticker.id;
120+
obj.download_sticker(file_id, &session, looped, format).await
121+
}
122+
}));
123+
}
124+
125+
pub(crate) fn play_animation(&self) {
126+
if let Some(animation) = &*self.imp().child.borrow() {
127+
if let Some(animation) = animation.downcast_ref::<rlt::Animation>() {
128+
if !animation.is_playing() {
129+
animation.play();
130+
}
131+
}
132+
}
133+
}
134+
135+
async fn download_sticker(
136+
&self,
137+
file_id: i32,
138+
session: &Session,
139+
looped: bool,
140+
format: StickerFormat,
141+
) {
142+
match session.download_file(file_id).await {
143+
Ok(file) => {
144+
self.load_sticker(&file.local.path, looped, format).await;
145+
}
146+
Err(e) => {
147+
log::warn!("Failed to download a sticker: {e:?}");
148+
}
149+
}
150+
}
151+
152+
async fn load_sticker(&self, path: &str, looped: bool, format: StickerFormat) {
153+
let path = path.to_owned();
154+
let message_id = self.imp().message_id.get();
155+
156+
let widget: gtk::Widget = match format {
157+
StickerFormat::Tgs => {
158+
let animation = rlt::Animation::from_filename(&path);
159+
animation.set_loop(looped);
160+
animation.use_cache(looped);
161+
animation.play();
162+
animation.upcast()
163+
}
164+
StickerFormat::Webp => {
165+
let result = gio::spawn_blocking(move || decode_image_from_path(&path))
166+
.await
167+
.unwrap();
168+
169+
match result {
170+
Ok(texture) => {
171+
let picture = gtk::Picture::new();
172+
picture.set_paintable(Some(&texture));
173+
picture.upcast()
174+
}
175+
Err(e) => {
176+
log::warn!("Error decoding a sticker: {e:?}");
177+
return;
178+
}
179+
}
180+
}
181+
_ => unimplemented!(),
182+
};
183+
184+
if self.imp().message_id.get() != message_id {
185+
return;
186+
}
187+
188+
self.set_child(Some(widget));
189+
}
190+
191+
fn set_child(&self, child: Option<gtk::Widget>) {
192+
let imp = self.imp();
193+
194+
if let Some(ref child) = child {
195+
child.set_parent(self);
196+
}
197+
198+
if let Some(old) = imp.child.replace(child) { old.unparent() }
199+
}
200+
}

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)