1+ use adw:: prelude:: * ;
12use glib:: clone;
2- use gtk:: prelude:: * ;
33use gtk:: subclass:: prelude:: * ;
44use gtk:: { gdk, gio, glib, CompositeTemplate } ;
55use image:: io:: Reader as ImageReader ;
66use image:: ImageFormat ;
77use std:: io:: Cursor ;
8- use tdlib:: enums:: MessageContent ;
8+ use tdlib:: enums:: { MessageContent , StickerFormat , StickerFullType } ;
99use tdlib:: types:: File ;
1010
11- use crate :: session:: content:: message_row:: {
12- MessageBase , MessageBaseImpl , MessageIndicators , StickerPicture ,
13- } ;
11+ use crate :: session:: content:: message_row:: { MessageBase , MessageBaseImpl , MessageIndicators } ;
1412use crate :: tdlib:: Message ;
1513use crate :: utils:: spawn;
1614
@@ -19,14 +17,21 @@ use super::base::MessageBaseExt;
1917mod imp {
2018 use super :: * ;
2119 use once_cell:: sync:: Lazy ;
22- use std:: cell:: RefCell ;
20+ use std:: cell:: { Cell , RefCell } ;
2321
2422 #[ derive( Debug , Default , CompositeTemplate ) ]
2523 #[ template( resource = "/com/github/melix99/telegrand/ui/content-message-sticker.ui" ) ]
2624 pub ( crate ) struct MessageSticker {
25+ pub ( super ) format : RefCell < Option < StickerFormat > > ,
26+ pub ( super ) aspect_ratio : Cell < f64 > ,
27+ pub ( super ) is_emoji : Cell < bool > ,
2728 pub ( super ) message : RefCell < Option < Message > > ,
2829 #[ template_child]
29- pub ( super ) picture : TemplateChild < StickerPicture > ,
30+ pub ( super ) overlay : TemplateChild < gtk:: Overlay > ,
31+ #[ template_child]
32+ pub ( super ) click : TemplateChild < gtk:: GestureClick > ,
33+ #[ template_child]
34+ pub ( super ) bin : TemplateChild < adw:: Bin > ,
3035 #[ template_child]
3136 pub ( super ) indicators : TemplateChild < MessageIndicators > ,
3237 }
@@ -72,9 +77,61 @@ mod imp {
7277 _ => unimplemented ! ( ) ,
7378 }
7479 }
80+
81+ fn constructed ( & self ) {
82+ let obj = self . obj ( ) ;
83+ self . click
84+ . connect_released ( clone ! ( @weak obj => move |_, _, _, _| {
85+ let imp = obj. imp( ) ;
86+ if imp. is_emoji. get( ) {
87+ if let Some ( animation) = imp. bin. child( ) {
88+ if let Ok ( animation) = animation. downcast:: <rlt:: Animation >( ) {
89+ if !animation. is_playing( ) {
90+ // TODO: animated emoji needs to play
91+ // effect when someone clicks on it
92+ animation. play( ) ;
93+ }
94+ }
95+ }
96+ }
97+ } ) ) ;
98+ }
7599 }
76100
77- impl WidgetImpl for MessageSticker { }
101+ impl WidgetImpl for MessageSticker {
102+ fn measure ( & self , orientation : gtk:: Orientation , for_size : i32 ) -> ( i32 , i32 , i32 , i32 ) {
103+ const STICKER_SIZE : i32 = 176 ;
104+ const EMOJI_SIZE : i32 = 112 ;
105+ const EMOJI_BOTTOM_MARGIN : i32 = 16 ;
106+
107+ let ( size, bottom_margin) = if self . is_emoji . get ( ) {
108+ ( EMOJI_SIZE , EMOJI_BOTTOM_MARGIN )
109+ } else {
110+ ( STICKER_SIZE , 0 )
111+ } ;
112+
113+ let aspect_ratio = self . aspect_ratio . get ( ) ;
114+ let min_size = self . overlay . measure ( orientation, for_size) . 0 ;
115+ let size = if let gtk:: Orientation :: Horizontal = orientation {
116+ if aspect_ratio >= 1.0 {
117+ size
118+ } else {
119+ ( size as f64 * aspect_ratio) as i32
120+ }
121+ } else if aspect_ratio >= 1.0 {
122+ ( size as f64 / aspect_ratio) as i32 + bottom_margin
123+ } else {
124+ size + bottom_margin
125+ }
126+ . max ( min_size) ;
127+
128+ ( size, size, -1 , -1 )
129+ }
130+
131+ fn size_allocate ( & self , width : i32 , height : i32 , baseline : i32 ) {
132+ self . overlay . allocate ( width, height, baseline, None ) ;
133+ }
134+ }
78135 impl MessageBaseImpl for MessageSticker { }
79136}
80137
@@ -95,35 +152,54 @@ impl MessageBaseExt for MessageSticker {
95152
96153 imp. indicators . set_message ( message. clone ( ) . upcast ( ) ) ;
97154
98- imp. picture . set_texture ( None ) ;
155+ let ( sticker, looped) = match message. content ( ) . 0 {
156+ MessageContent :: MessageSticker ( data) => {
157+ let sticker = data. sticker ;
158+ ( sticker, true )
159+ }
160+ MessageContent :: MessageAnimatedEmoji ( data) => {
161+ self . imp ( ) . is_emoji . set ( true ) ;
162+ let sticker = data. animated_emoji . sticker . unwrap ( ) ;
163+ let looped = matches ! ( sticker. full_type, StickerFullType :: CustomEmoji ( _) ) ;
164+ ( sticker, looped)
165+ }
166+ _ => unreachable ! ( ) ,
167+ } ;
99168
100- if let MessageContent :: MessageSticker ( data) = message. content ( ) . 0 {
101- self . imp ( )
102- . picture
103- . set_aspect_ratio ( data. sticker . width as f64 / data. sticker . height as f64 ) ;
169+ match sticker. full_type {
170+ StickerFullType :: CustomEmoji ( data) if data. needs_repainting => {
171+ self . add_css_class ( "needs-repainting" )
172+ }
173+ _ => self . remove_css_class ( "needs-repainting" ) ,
174+ }
104175
105- if data. sticker . sticker . local . is_downloading_completed {
106- self . load_sticker ( & data. sticker . sticker . local . path ) ;
107- } else {
108- let ( sender, receiver) =
109- glib:: MainContext :: sync_channel :: < File > ( Default :: default ( ) , 5 ) ;
110-
111- receiver. attach (
112- None ,
113- clone ! ( @weak self as obj => @default -return glib:: Continue ( false ) , move |file| {
114- if file. local. is_downloading_completed {
115- obj. load_sticker( & file. local. path) ;
116- }
176+ imp. format . replace ( Some ( sticker. format ) ) ;
117177
118- glib:: Continue ( true )
119- } ) ,
120- ) ;
178+ imp. aspect_ratio
179+ . set ( sticker. width as f64 / sticker. height as f64 ) ;
121180
122- message
123- . chat ( )
124- . session ( )
125- . download_file ( data. sticker . sticker . id , sender) ;
126- }
181+ // TODO: draw sticker outline with cairo
182+
183+ if sticker. sticker . local . is_downloading_completed {
184+ self . load_sticker ( & sticker. sticker . local . path , looped) ;
185+ } else {
186+ let ( sender, receiver) = glib:: MainContext :: sync_channel :: < File > ( Default :: default ( ) , 5 ) ;
187+
188+ receiver. attach (
189+ None ,
190+ clone ! ( @weak self as obj => @default -return glib:: Continue ( false ) , move |file| {
191+ if file. local. is_downloading_completed {
192+ obj. load_sticker( & file. local. path, looped) ;
193+ }
194+
195+ glib:: Continue ( true )
196+ } ) ,
197+ ) ;
198+
199+ message
200+ . chat ( )
201+ . session ( )
202+ . download_file ( sticker. sticker . id , sender) ;
127203 }
128204
129205 imp. message . replace ( Some ( message) ) ;
@@ -132,35 +208,55 @@ impl MessageBaseExt for MessageSticker {
132208}
133209
134210impl MessageSticker {
135- fn load_sticker ( & self , path : & str ) {
136- let picture = & * self . imp ( ) . picture ;
137- let file = gio:: File :: for_path ( path) ;
138- spawn ( clone ! ( @weak picture => async move {
139- match file. load_bytes_future( ) . await {
140- Ok ( ( bytes, _) ) => {
141- let flat_samples = ImageReader :: with_format( Cursor :: new( bytes) , ImageFormat :: WebP )
142- . decode( )
143- . unwrap( )
144- . into_rgba8( )
145- . into_flat_samples( ) ;
146-
147- let ( stride, width, height) = flat_samples. extents( ) ;
148- let gtk_stride = stride * width;
149-
150- let bytes = glib:: Bytes :: from_owned( flat_samples. samples) ;
151- let texture = gdk:: MemoryTexture :: new(
152- width as i32 ,
153- height as i32 ,
154- gdk:: MemoryFormat :: R8g8b8a8 ,
155- & bytes,
156- gtk_stride,
157- ) ;
158- picture. set_texture( Some ( texture. upcast( ) ) ) ;
159- }
160- Err ( e) => {
161- log:: warn!( "Failed to load a sticker: {}" , e) ;
211+ fn load_sticker ( & self , path : & str , looped : bool ) {
212+ let path = path. to_owned ( ) ;
213+ let format = self . imp ( ) . format . borrow ( ) . clone ( ) . unwrap ( ) ;
214+ spawn ( clone ! ( @weak self as obj => async move {
215+ let widget: gtk:: Widget = match format {
216+ StickerFormat :: Tgs => {
217+ let animation = rlt:: Animation :: from_filename( & path) ;
218+ animation. set_loop( looped) ;
219+ animation. use_cache( looped) ;
220+ animation. play( ) ;
221+ animation. upcast( )
222+ }
223+ StickerFormat :: Webp => {
224+ let file = gio:: File :: for_path( & path) ;
225+ match file. load_bytes_future( ) . await {
226+ Ok ( ( bytes, _) ) => {
227+ let flat_samples =
228+ ImageReader :: with_format( Cursor :: new( bytes) , ImageFormat :: WebP )
229+ . decode( )
230+ . unwrap( )
231+ . into_rgba8( )
232+ . into_flat_samples( ) ;
233+
234+ let ( stride, width, height) = flat_samples. extents( ) ;
235+ let gtk_stride = stride * width;
236+
237+ let bytes = glib:: Bytes :: from_owned( flat_samples. samples) ;
238+ let texture = gdk:: MemoryTexture :: new(
239+ width as i32 ,
240+ height as i32 ,
241+ gdk:: MemoryFormat :: R8g8b8a8 ,
242+ & bytes,
243+ gtk_stride,
244+ ) ;
245+
246+ let picture = gtk:: Picture :: new( ) ;
247+ picture. set_paintable( Some ( & texture) ) ;
248+ picture. upcast( )
249+ }
250+ Err ( e) => {
251+ log:: warn!( "Failed to load a sticker: {}" , e) ;
252+ return ;
253+ }
162254 }
163255 }
256+ _ => unimplemented!( ) ,
257+ } ;
258+
259+ obj. imp( ) . bin. set_child( Some ( & widget) ) ;
164260 } ) ) ;
165261 }
166262}
0 commit comments