Skip to content

Commit c9de911

Browse files
committed
rlottie: Add support of animated emojis
1 parent c33a6da commit c9de911

8 files changed

Lines changed: 259 additions & 213 deletions

File tree

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,48 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<gresources>
3-
<gresource prefix="/com/github/melix99/telegrand/">
4-
<file preprocess="xml-stripblanks">icons/scalable/actions/big-x-symbolic.svg</file>
5-
<file preprocess="xml-stripblanks">icons/scalable/actions/clear-symbolic.svg</file>
6-
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
7-
<file preprocess="xml-stripblanks">icons/scalable/status/message-failed-symbolic.svg</file>
8-
<file preprocess="xml-stripblanks">icons/scalable/status/message-pending-symbolic.svg</file>
9-
<file preprocess="xml-stripblanks">icons/scalable/status/message-read-symbolic.svg</file>
10-
<file preprocess="xml-stripblanks">icons/scalable/status/message-unread-left-symbolic.svg</file>
11-
<file preprocess="xml-stripblanks">icons/scalable/status/message-unread-right-symbolic.svg</file>
3+
<gresource prefix="/com/github/melix99/telegrand/">
4+
<file preprocess="xml-stripblanks">icons/scalable/actions/big-x-symbolic.svg</file>
5+
<file preprocess="xml-stripblanks">icons/scalable/actions/clear-symbolic.svg</file>
6+
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
7+
<file preprocess="xml-stripblanks">icons/scalable/status/message-failed-symbolic.svg</file>
8+
<file preprocess="xml-stripblanks">icons/scalable/status/message-pending-symbolic.svg</file>
9+
<file preprocess="xml-stripblanks">icons/scalable/status/message-read-symbolic.svg</file>
10+
<file preprocess="xml-stripblanks">icons/scalable/status/message-unread-left-symbolic.svg</file>
11+
<file preprocess="xml-stripblanks">icons/scalable/status/message-unread-right-symbolic.svg</file>
1212

13-
<file compressed="true" preprocess="xml-stripblanks">ui/add-account-row.ui</file>
14-
<file compressed="true" preprocess="xml-stripblanks">ui/avatar-with-selection.ui</file>
15-
<file compressed="true" preprocess="xml-stripblanks">ui/components-avatar.ui</file>
16-
<file compressed="true" preprocess="xml-stripblanks">ui/components-message-entry.ui</file>
17-
<file compressed="true" preprocess="xml-stripblanks">ui/content.ui</file>
18-
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-action-bar.ui</file>
19-
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-history.ui</file>
20-
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-info-window.ui</file>
21-
<file compressed="true" preprocess="xml-stripblanks">ui/content-event-row.ui</file>
22-
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-document.ui</file>
23-
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-photo.ui</file>
24-
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-sticker.ui</file>
25-
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-text.ui</file>
26-
<file compressed="true" preprocess="xml-stripblanks">ui/content-send-photo-dialog.ui</file>
27-
<file compressed="true" preprocess="xml-stripblanks">ui/login.ui</file>
28-
<file compressed="true" preprocess="xml-stripblanks">ui/message-menu.ui</file>
29-
<file compressed="true" preprocess="xml-stripblanks">ui/phone-number-input.ui</file>
30-
<file compressed="true" preprocess="xml-stripblanks">ui/preferences-window.ui</file>
31-
<file compressed="true" preprocess="xml-stripblanks">ui/session-entry-row.ui</file>
32-
<file compressed="true" preprocess="xml-stripblanks">ui/session-manager.ui</file>
33-
<file compressed="true" preprocess="xml-stripblanks">ui/session.ui</file>
34-
<file compressed="true" preprocess="xml-stripblanks" alias="gtk/help-overlay.ui">ui/shortcuts.ui</file>
35-
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-avatar.ui</file>
36-
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-row.ui</file>
37-
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-row-menu.ui</file>
38-
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-search.ui</file>
39-
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-session-switcher.ui</file>
40-
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar.ui</file>
41-
<file compressed="true" preprocess="xml-stripblanks">ui/window.ui</file>
13+
<file compressed="true" preprocess="xml-stripblanks">ui/add-account-row.ui</file>
14+
<file compressed="true" preprocess="xml-stripblanks">ui/avatar-with-selection.ui</file>
15+
<file compressed="true" preprocess="xml-stripblanks">ui/components-avatar.ui</file>
16+
<file compressed="true" preprocess="xml-stripblanks">ui/components-message-entry.ui</file>
17+
<file compressed="true" preprocess="xml-stripblanks">ui/content.ui</file>
18+
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-action-bar.ui</file>
19+
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-history.ui</file>
20+
<file compressed="true" preprocess="xml-stripblanks">ui/content-chat-info-window.ui</file>
21+
<file compressed="true" preprocess="xml-stripblanks">ui/content-event-row.ui</file>
22+
<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>
24+
<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>
26+
<file compressed="true" preprocess="xml-stripblanks">ui/content-message-text.ui</file>
27+
<file compressed="true" preprocess="xml-stripblanks">ui/content-send-photo-dialog.ui</file>
28+
<file compressed="true" preprocess="xml-stripblanks">ui/login.ui</file>
29+
<file compressed="true" preprocess="xml-stripblanks">ui/message-menu.ui</file>
30+
<file compressed="true" preprocess="xml-stripblanks">ui/phone-number-input.ui</file>
31+
<file compressed="true" preprocess="xml-stripblanks">ui/preferences-window.ui</file>
32+
<file compressed="true" preprocess="xml-stripblanks">ui/session-entry-row.ui</file>
33+
<file compressed="true" preprocess="xml-stripblanks">ui/session-manager.ui</file>
34+
<file compressed="true" preprocess="xml-stripblanks">ui/session.ui</file>
35+
<file compressed="true" preprocess="xml-stripblanks" alias="gtk/help-overlay.ui">ui/shortcuts.ui</file>
36+
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-avatar.ui</file>
37+
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-row.ui</file>
38+
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-row-menu.ui</file>
39+
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-search.ui</file>
40+
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar-session-switcher.ui</file>
41+
<file compressed="true" preprocess="xml-stripblanks">ui/sidebar.ui</file>
42+
<file compressed="true" preprocess="xml-stripblanks">ui/window.ui</file>
4243

43-
<file compressed="true">style.css</file>
44-
<file compressed="true">style-dark.css</file>
45-
<file compressed="true">style-hc.css</file>
46-
</gresource>
44+
<file compressed="true">style.css</file>
45+
<file compressed="true">style-dark.css</file>
46+
<file compressed="true">style-hc.css</file>
47+
</gresource>
4748
</gresources>
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: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
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+
animation.play();
83+
}
84+
}
85+
}));
86+
}
87+
}
88+
89+
impl WidgetImpl for MessageAnimatedEmoji {
90+
fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
91+
const SIZE: i32 = 120;
92+
let min = self.gtk_box.measure(orientation, for_size).0;
93+
if orientation == gtk::Orientation::Horizontal {
94+
(SIZE, SIZE, -1, -1)
95+
} else {
96+
(SIZE + min, SIZE + min, -1, -1)
97+
}
98+
}
99+
100+
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
101+
self.gtk_box.allocate(width, height, baseline, None);
102+
}
103+
}
104+
impl MessageBaseImpl for MessageAnimatedEmoji {}
105+
}
106+
107+
glib::wrapper! {
108+
pub(crate) struct MessageAnimatedEmoji(ObjectSubclass<imp::MessageAnimatedEmoji>)
109+
@extends gtk::Widget, MessageBase;
110+
}
111+
112+
impl MessageBaseExt for MessageAnimatedEmoji {
113+
type Message = Message;
114+
115+
fn set_message(&self, message: Self::Message) {
116+
let imp = self.imp();
117+
118+
if imp.message.borrow().as_ref() == Some(&message) {
119+
return;
120+
}
121+
122+
imp.indicators.set_message(message.clone().upcast());
123+
124+
if let MessageContent::MessageAnimatedEmoji(data) = message.content().0 {
125+
let data = data.animated_emoji;
126+
127+
let sticker = data.sticker.unwrap();
128+
129+
let preview = StickerPreview::new(sticker.outline.clone());
130+
self.imp().bin.set_child(Some(&preview));
131+
132+
let file = sticker.sticker;
133+
134+
let looped = matches!(
135+
sticker.full_type,
136+
tdlib::enums::StickerFullType::CustomEmoji(_)
137+
);
138+
139+
if file.local.is_downloading_completed {
140+
self.load_sticker(&file.local.path, looped)
141+
} else {
142+
let (sender, receiver) =
143+
glib::MainContext::sync_channel::<File>(Default::default(), 5);
144+
145+
receiver.attach(
146+
None,
147+
clone!(@weak self as obj => @default-return glib::Continue(false), move |file| {
148+
if file.local.is_downloading_completed {
149+
obj.load_sticker(&file.local.path, looped);
150+
}
151+
152+
glib::Continue(true)
153+
}),
154+
);
155+
156+
message.chat().session().download_file(file.id, sender);
157+
}
158+
}
159+
160+
imp.message.replace(Some(message));
161+
162+
self.notify("message");
163+
}
164+
}
165+
166+
impl MessageAnimatedEmoji {
167+
fn load_sticker(&self, path: &str, looped: bool) {
168+
let path = path.to_owned();
169+
170+
let animation = rlt::Animation::from_filename(&path);
171+
animation.set_loop(looped);
172+
animation.use_cache(looped);
173+
animation.play();
174+
175+
self.imp().bin.set_child(Some(&animation));
176+
}
177+
}

src/session/content/message_row/mod.rs

Lines changed: 5 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,9 @@ impl MessageRow {
316316
MessageContent::MessageAnimation(_) /*| MessageContent::MessageVideo(_)*/ => {
317317
self.update_specific_content::<_, MessageVideo>(message_.clone());
318318
}
319+
MessageContent::MessageAnimatedEmoji(_) => {
320+
self.update_specific_content::<_, MessageAnimatedEmoji>(message_.clone());
321+
}
319322
MessageContent::MessagePhoto(_) => {
320323
self.update_specific_content::<_, MessagePhoto>(message_.clone());
321324
}

0 commit comments

Comments
 (0)