Skip to content

Commit 2d3d01d

Browse files
committed
rlottie: Add support of animated emojis
1 parent a5ab736 commit 2d3d01d

9 files changed

Lines changed: 223 additions & 144 deletions

File tree

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ trim_trailing_whitespace = true
77
insert_final_newline = true
88
charset = utf-8
99

10-
[*.{build,yml,ui,yaml}]
10+
[*.{build,yml,ui,yaml,xml}]
1111
indent_size = 2

data/resources/resources.gresource.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-info-window.ui</file>
2121
<file compressed="true" preprocess="xml-stripblanks">ui/content-event-row.ui</file>
2222
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-document.ui</file>
23+
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-animated-emoji.ui</file>
2324
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-photo.ui</file>
2425
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-sticker.ui</file>
2526
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-text.ui</file>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<interface>
3+
<template class="ContentMessageAnimatedEmoji" parent="ContentMessageBase">
4+
<child>
5+
<object class="GtkBox" id="gtk_box">
6+
<property name="orientation">vertical</property>
7+
<property name="vexpand">true</property>
8+
<child>
9+
<object class="GtkGestureClick" id="click">
10+
<property name="button">1</property>
11+
</object>
12+
</child>
13+
<child>
14+
<object class="AdwBin" id="bin">
15+
<property name="vexpand">true</property>
16+
</object>
17+
</child>
18+
<child>
19+
<object class="MessageIndicators" id="indicators">
20+
<property name="halign">end</property>
21+
<property name="valign">end</property>
22+
</object>
23+
</child>
24+
</object>
25+
</child>
26+
</template>
27+
</interface>

src/session/content/chat_action_bar.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ impl ChatActionBar {
250250
let chooser = gtk::EmojiChooser::new();
251251
chooser.set_parent(parent);
252252
chooser.connect_emoji_picked(clone!(@weak self as obj => move |_, emoji| {
253-
obj.imp().message_entry.insert_at_cursor(emoji);
253+
obj.imp().message_entry.insert_at_cursor(emoji.trim_end_matches('\u{fe0f}'));
254254
}));
255255
chooser.connect_hide(clone!(@weak self as obj => move |_| {
256256
obj.imp().message_entry.grab_focus();
@@ -303,9 +303,10 @@ impl ChatActionBar {
303303
client_id,
304304
)
305305
.await;
306+
306307
if let Err(e) = result {
307-
log::warn!("Error sending a message: {:?}", e);
308-
}
308+
log::warn!("Error sending a message: {:?}", e)
309+
};
309310

310311
self.reset();
311312
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
use adw::prelude::*;
2+
use glib::clone;
3+
use gtk::subclass::prelude::*;
4+
use gtk::{glib, CompositeTemplate};
5+
use tdlib::enums::MessageContent;
6+
use tdlib::types::File;
7+
8+
use crate::session::components::StickerPreview;
9+
use crate::session::content::message_row::{MessageBase, MessageBaseImpl, MessageIndicators};
10+
use crate::tdlib::Message;
11+
12+
use super::base::MessageBaseExt;
13+
14+
mod imp {
15+
use super::*;
16+
use once_cell::sync::Lazy;
17+
use std::cell::RefCell;
18+
19+
#[derive(Debug, Default, CompositeTemplate)]
20+
#[template(resource = "/com/github/melix99/telegrand/ui/content-message-animated-emoji.ui")]
21+
pub(crate) struct MessageAnimatedEmoji {
22+
pub(super) message: RefCell<Option<Message>>,
23+
#[template_child]
24+
pub(super) gtk_box: TemplateChild<gtk::Box>,
25+
#[template_child]
26+
pub(super) click: TemplateChild<gtk::GestureClick>,
27+
#[template_child]
28+
pub(super) bin: TemplateChild<adw::Bin>,
29+
#[template_child]
30+
pub(super) indicators: TemplateChild<MessageIndicators>,
31+
}
32+
33+
#[glib::object_subclass]
34+
impl ObjectSubclass for MessageAnimatedEmoji {
35+
const NAME: &'static str = "ContentMessageAnimatedEmoji";
36+
type Type = super::MessageAnimatedEmoji;
37+
type ParentType = MessageBase;
38+
39+
fn class_init(klass: &mut Self::Class) {
40+
Self::bind_template(klass);
41+
klass.set_css_name("messagesticker");
42+
}
43+
44+
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
45+
obj.init_template();
46+
}
47+
}
48+
49+
impl ObjectImpl for MessageAnimatedEmoji {
50+
fn properties() -> &'static [glib::ParamSpec] {
51+
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
52+
vec![glib::ParamSpecObject::new(
53+
"message",
54+
"Message",
55+
"The message represented by this row",
56+
Message::static_type(),
57+
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
58+
)]
59+
});
60+
PROPERTIES.as_ref()
61+
}
62+
63+
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
64+
match pspec.name() {
65+
"message" => self.obj().set_message(value.get().unwrap()),
66+
_ => unimplemented!(),
67+
}
68+
}
69+
70+
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
71+
match pspec.name() {
72+
"message" => self.message.borrow().to_value(),
73+
_ => unimplemented!(),
74+
}
75+
}
76+
77+
fn constructed(&self) {
78+
self.click
79+
.connect_released(clone!(@to-owned self as imp => move |_, _, _, _| {
80+
if let Some(animation) = imp.bin.child() {
81+
if let Ok(animation) = animation.downcast::<rlt::Animation>() {
82+
if !animation.is_playing() {
83+
animation.play();
84+
}
85+
}
86+
}
87+
}));
88+
}
89+
}
90+
91+
impl WidgetImpl for MessageAnimatedEmoji {
92+
fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
93+
const SIZE: i32 = 120;
94+
let min = self.gtk_box.measure(orientation, for_size).0;
95+
if orientation == gtk::Orientation::Horizontal {
96+
(SIZE, SIZE, -1, -1)
97+
} else {
98+
(SIZE + min, SIZE + min, -1, -1)
99+
}
100+
}
101+
102+
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
103+
self.gtk_box.allocate(width, height, baseline, None);
104+
}
105+
}
106+
impl MessageBaseImpl for MessageAnimatedEmoji {}
107+
}
108+
109+
glib::wrapper! {
110+
pub(crate) struct MessageAnimatedEmoji(ObjectSubclass<imp::MessageAnimatedEmoji>)
111+
@extends gtk::Widget, MessageBase;
112+
}
113+
114+
impl MessageBaseExt for MessageAnimatedEmoji {
115+
type Message = Message;
116+
117+
fn set_message(&self, message: Self::Message) {
118+
let imp = self.imp();
119+
120+
if imp.message.borrow().as_ref() == Some(&message) {
121+
return;
122+
}
123+
124+
imp.indicators.set_message(message.clone().upcast());
125+
126+
if let MessageContent::MessageAnimatedEmoji(data) = message.content().0 {
127+
let data = data.animated_emoji;
128+
129+
let sticker = data.sticker.unwrap();
130+
131+
let preview = StickerPreview::new(sticker.outline.clone());
132+
self.imp().bin.set_child(Some(&preview));
133+
134+
let file = sticker.sticker;
135+
136+
let looped = matches!(
137+
sticker.full_type,
138+
tdlib::enums::StickerFullType::CustomEmoji(_)
139+
);
140+
141+
if file.local.is_downloading_completed {
142+
self.load_sticker(&file.local.path, looped)
143+
} else {
144+
let (sender, receiver) =
145+
glib::MainContext::sync_channel::<File>(Default::default(), 5);
146+
147+
receiver.attach(
148+
None,
149+
clone!(@weak self as obj => @default-return glib::Continue(false), move |file| {
150+
if file.local.is_downloading_completed {
151+
obj.load_sticker(&file.local.path, looped);
152+
}
153+
154+
glib::Continue(true)
155+
}),
156+
);
157+
158+
message.chat().session().download_file(file.id, sender);
159+
}
160+
}
161+
162+
imp.message.replace(Some(message));
163+
164+
self.notify("message");
165+
}
166+
}
167+
168+
impl MessageAnimatedEmoji {
169+
fn load_sticker(&self, path: &str, looped: bool) {
170+
let path = path.to_owned();
171+
172+
let animation = rlt::Animation::from_filename(&path);
173+
animation.set_loop(looped);
174+
animation.use_cache(looped);
175+
animation.play();
176+
177+
self.imp().bin.set_child(Some(&animation));
178+
}
179+
}

src/session/content/message_row/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod animated_emoji;
12
mod base;
23
mod bubble;
34
mod document;
@@ -7,10 +8,10 @@ mod label;
78
mod media_picture;
89
mod photo;
910
mod sticker;
10-
mod sticker_picture;
1111
mod text;
1212
mod video;
1313

14+
use self::animated_emoji::MessageAnimatedEmoji;
1415
use self::base::{MessageBase, MessageBaseExt, MessageBaseImpl};
1516
use self::bubble::MessageBubble;
1617
use self::document::MessageDocument;
@@ -19,7 +20,6 @@ use self::label::MessageLabel;
1920
use self::media_picture::MediaPicture;
2021
use self::photo::MessagePhoto;
2122
use self::sticker::MessageSticker;
22-
use self::sticker_picture::StickerPicture;
2323
use self::text::MessageText;
2424
use self::video::MessageVideo;
2525

@@ -316,6 +316,12 @@ impl MessageRow {
316316
MessageContent::MessageAnimation(_) /*| MessageContent::MessageVideo(_)*/ => {
317317
self.update_specific_content::<_, MessageVideo>(message_.clone());
318318
}
319+
MessageContent::MessageAnimatedEmoji(data)
320+
if data.animated_emoji.sticker.clone().map(
321+
|s| matches!(s.format, StickerFormat::Tgs)
322+
).unwrap_or_default() => {
323+
self.update_specific_content::<_, MessageAnimatedEmoji>(message_.clone());
324+
}
319325
MessageContent::MessagePhoto(_) => {
320326
self.update_specific_content::<_, MessagePhoto>(message_.clone());
321327
}

0 commit comments

Comments
 (0)