[fractal/fractal-next] message-row: Reuse MessageMedia when possible



commit 337989da944558b54e444590b1b1494ebd919e4b
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Mon Dec 20 09:09:19 2021 +0100

    message-row: Reuse MessageMedia when possible

 src/components/video_player.rs                     |  26 ++-
 .../content/room_history/message_row/media.rs      | 184 +++++++++++----------
 .../content/room_history/message_row/mod.rs        |  36 +++-
 3 files changed, 141 insertions(+), 105 deletions(-)
---
diff --git a/src/components/video_player.rs b/src/components/video_player.rs
index 7cd04b67..e417d8b7 100644
--- a/src/components/video_player.rs
+++ b/src/components/video_player.rs
@@ -9,7 +9,7 @@ mod imp {
     #[derive(Debug, Default, CompositeTemplate)]
     #[template(resource = "/org/gnome/FractalNext/components-video-player.ui")]
     pub struct VideoPlayer {
-        pub media_file: RefCell<Option<gtk::MediaFile>>,
+        pub duration_handler: RefCell<Option<glib::SignalHandlerId>>,
         #[template_child]
         pub video: TemplateChild<gtk::Picture>,
         #[template_child]
@@ -45,20 +45,28 @@ glib::wrapper! {
 }
 
 impl VideoPlayer {
-    pub fn new(media_file: &gtk::MediaFile) -> Self {
-        let self_: Self = glib::Object::new(&[]).expect("Failed to create VideoPlayer");
-        self_.build(media_file);
-        self_
+    /// Create a new video player.
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create VideoPlayer")
     }
 
-    pub fn build(&self, media_file: &gtk::MediaFile) {
+    /// Set the media_file to display.
+    pub fn set_media_file(&self, media_file: &gtk::MediaFile) {
         let priv_ = imp::VideoPlayer::from_instance(self);
 
+        if let Some(handler_id) = priv_.duration_handler.take() {
+            if let Some(paintable) = priv_.video.paintable() {
+                paintable.disconnect(handler_id);
+            }
+        }
+
         priv_.video.set_paintable(Some(media_file));
         let timestamp = &*priv_.timestamp;
-        media_file.connect_duration_notify(clone!(@weak timestamp => move |media_file| {
-            timestamp.set_label(&duration(media_file));
-        }));
+        let handler_id =
+            media_file.connect_duration_notify(clone!(@weak timestamp => move |media_file| {
+                timestamp.set_label(&duration(media_file));
+            }));
+        priv_.duration_handler.replace(Some(handler_id));
     }
 }
 
diff --git a/src/session/content/room_history/message_row/media.rs 
b/src/session/content/room_history/message_row/media.rs
index 75e6a0af..996f32d3 100644
--- a/src/session/content/room_history/message_row/media.rs
+++ b/src/session/content/room_history/message_row/media.rs
@@ -42,12 +42,6 @@ pub enum MediaType {
     Video = 2,
 }
 
-impl Default for MediaType {
-    fn default() -> Self {
-        Self::Image
-    }
-}
-
 #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
 #[repr(u32)]
 #[genum(type_name = "MediaState")]
@@ -65,7 +59,7 @@ impl Default for MediaState {
 }
 
 mod imp {
-    use std::cell::{Cell, RefCell};
+    use std::cell::Cell;
 
     use glib::subclass::InitializingObject;
     use once_cell::sync::Lazy;
@@ -75,14 +69,10 @@ mod imp {
     #[derive(Debug, Default, CompositeTemplate)]
     #[template(resource = "/org/gnome/FractalNext/content-message-media.ui")]
     pub struct MessageMedia {
-        /// The type of media previewed with this image.
-        pub media_type: Cell<MediaType>,
         /// The intended display width of the full image.
         pub width: Cell<i32>,
         /// The intended display height of the full image.
         pub height: Cell<i32>,
-        /// The "body" of the image to show as a tooltip. Only used for stickers.
-        pub body: RefCell<Option<String>>,
         /// The state of the media.
         pub state: Cell<MediaState>,
         #[template_child]
@@ -112,14 +102,6 @@ mod imp {
         fn properties() -> &'static [glib::ParamSpec] {
             static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
                 vec![
-                    glib::ParamSpec::new_enum(
-                        "media-type",
-                        "Media Type",
-                        "The type of media previewed",
-                        MediaType::static_type(),
-                        MediaType::default() as i32,
-                        glib::ParamFlags::READWRITE,
-                    ),
                     glib::ParamSpec::new_int(
                         "width",
                         "Width",
@@ -127,7 +109,7 @@ mod imp {
                         -1,
                         i32::MAX,
                         -1,
-                        glib::ParamFlags::READWRITE,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
                     glib::ParamSpec::new_int(
                         "height",
@@ -136,14 +118,7 @@ mod imp {
                         -1,
                         i32::MAX,
                         -1,
-                        glib::ParamFlags::READWRITE,
-                    ),
-                    glib::ParamSpec::new_string(
-                        "body",
-                        "Body",
-                        "The 'body' of the media to show as a tooltip",
-                        None,
-                        glib::ParamFlags::READWRITE,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
                     glib::ParamSpec::new_enum(
                         "state",
@@ -167,17 +142,11 @@ mod imp {
             pspec: &glib::ParamSpec,
         ) {
             match pspec.name() {
-                "media-type" => {
-                    self.media_type.set(value.get().unwrap());
-                }
                 "width" => {
-                    self.width.set(value.get().unwrap());
+                    obj.set_width(value.get().unwrap());
                 }
                 "height" => {
-                    self.height.set(value.get().unwrap());
-                }
-                "body" => {
-                    self.body.replace(value.get().unwrap());
+                    obj.set_height(value.get().unwrap());
                 }
                 "state" => {
                     obj.set_state(value.get().unwrap());
@@ -188,10 +157,8 @@ mod imp {
 
         fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
-                "media-type" => obj.media_type().to_value(),
-                "width" => self.width.get().to_value(),
-                "height" => self.height.get().to_value(),
-                "body" => obj.body().to_value(),
+                "width" => obj.width().to_value(),
+                "height" => obj.height().to_value(),
                 "state" => obj.state().to_value(),
                 _ => unimplemented!(),
             }
@@ -281,57 +248,41 @@ glib::wrapper! {
 }
 
 impl MessageMedia {
-    pub fn image(image: ImageMessageEventContent, session: &Session) -> Self {
-        let info = image.info.as_deref();
-        let width = uint_to_i32(info.and_then(|info| info.width));
-        let height = uint_to_i32(info.and_then(|info| info.height));
-
-        let self_: Self = glib::Object::new(&[("width", &width), ("height", &height)])
-            .expect("Failed to create MessageMedia");
-        self_.build(image, session);
-        self_
+    /// Create a new media message.
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create MessageMedia")
     }
 
-    pub fn sticker(sticker: StickerEventContent, session: &Session) -> Self {
-        let info = &sticker.info;
-        let width = uint_to_i32(info.width);
-        let height = uint_to_i32(info.height);
-
-        let self_: Self = glib::Object::new(&[
-            ("media-type", &MediaType::Sticker),
-            ("width", &width),
-            ("height", &height),
-            ("body", &sticker.body),
-        ])
-        .expect("Failed to create MessageMedia");
-        self_.build(sticker, session);
-        self_
+    pub fn width(&self) -> i32 {
+        let priv_ = imp::MessageMedia::from_instance(self);
+        priv_.width.get()
     }
 
-    pub fn video(video: VideoMessageEventContent, session: &Session) -> Self {
-        let info = &video.info.as_deref();
-        let width = uint_to_i32(info.and_then(|info| info.width));
-        let height = uint_to_i32(info.and_then(|info| info.height));
+    fn set_width(&self, width: i32) {
+        let priv_ = imp::MessageMedia::from_instance(self);
 
-        let self_: Self = glib::Object::new(&[
-            ("media-type", &MediaType::Video),
-            ("width", &width),
-            ("height", &height),
-            ("body", &video.body),
-        ])
-        .expect("Failed to create MessageMedia");
-        self_.build(video, session);
-        self_
+        if self.width() == width {
+            return;
+        }
+
+        priv_.width.set(width);
+        self.notify("width");
     }
 
-    pub fn media_type(&self) -> MediaType {
+    pub fn height(&self) -> i32 {
         let priv_ = imp::MessageMedia::from_instance(self);
-        priv_.media_type.get()
+        priv_.height.get()
     }
 
-    pub fn body(&self) -> Option<String> {
+    fn set_height(&self, height: i32) {
         let priv_ = imp::MessageMedia::from_instance(self);
-        priv_.body.borrow().clone()
+
+        if self.height() == height {
+            return;
+        }
+
+        priv_.height.set(height);
+        self.notify("height");
     }
 
     pub fn state(&self) -> MediaState {
@@ -365,13 +316,47 @@ impl MessageMedia {
         self.notify("state");
     }
 
-    fn build<C>(&self, content: C, session: &Session)
+    /// Display the given `image`.
+    pub fn image(&self, image: ImageMessageEventContent, session: &Session) {
+        let info = image.info.as_deref();
+        let width = uint_to_i32(info.and_then(|info| info.width));
+        let height = uint_to_i32(info.and_then(|info| info.height));
+
+        self.set_width(width);
+        self.set_height(height);
+        self.build(image, None, MediaType::Image, session);
+    }
+
+    /// Display the given `sticker`.
+    pub fn sticker(&self, sticker: StickerEventContent, session: &Session) {
+        let info = &sticker.info;
+        let width = uint_to_i32(info.width);
+        let height = uint_to_i32(info.height);
+        let body = Some(sticker.body.clone());
+
+        self.set_width(width);
+        self.set_height(height);
+        self.build(sticker, body, MediaType::Sticker, session);
+    }
+
+    /// Display the given `video`.
+    pub fn video(&self, video: VideoMessageEventContent, session: &Session) {
+        let info = &video.info.as_deref();
+        let width = uint_to_i32(info.and_then(|info| info.width));
+        let height = uint_to_i32(info.and_then(|info| info.height));
+        let body = Some(video.body.clone());
+
+        self.set_width(width);
+        self.set_height(height);
+        self.build(video, body, MediaType::Video, session);
+    }
+
+    fn build<C>(&self, content: C, body: Option<String>, media_type: MediaType, session: &Session)
     where
         C: MediaEventContent + Send + Sync + Clone + 'static,
     {
         self.set_state(MediaState::Loading);
 
-        let media_type = self.media_type();
         let client = session.client();
         let handle = spawn_tokio!(async move {
             let thumbnail = if media_type != MediaType::Video && content.thumbnail().is_some() {
@@ -406,26 +391,37 @@ impl MessageMedia {
 
                 match handle.await.unwrap() {
                     Ok(Some(data)) => {
-                        let child: gtk::Widget = match media_type {
+                        match media_type {
                             MediaType::Image | MediaType::Sticker => {
                                 let stream = gio::MemoryInputStream::from_bytes(&glib::Bytes::from(&data));
                                 let texture = Pixbuf::from_stream(&stream, gio::NONE_CANCELLABLE)
                                     .ok()
                                     .map(|pixbuf| gdk::Texture::for_pixbuf(&pixbuf));
-                                let child = gtk::Picture::for_paintable(texture.as_ref());
 
-                                if media_type == MediaType::Sticker {
-                                    child.set_tooltip_text(obj.body().as_deref());
+                                let child = if let Some(Ok(child)) =
+                                    priv_.media.child().map(|w| w.downcast::<gtk::Picture>())
+                                {
+                                    child
+                                } else {
+                                    let child = gtk::Picture::new();
+                                    priv_.media.set_child(Some(&child));
+                                    child
+                                };
+                                child.set_paintable(texture.as_ref());
+
+                                child.set_tooltip_text(body.as_deref());
+                                if media_type == MediaType::Sticker && 
priv_.media.has_css_class("thumbnail") {
                                     priv_.media.remove_css_class("thumbnail");
+                                } else if !priv_.media.has_css_class("thumbnail") {
+                                    priv_.media.add_css_class("thumbnail");
                                 }
-                                child.upcast()
                             }
                             MediaType::Video => {
                                 // The GStreamer backend of GtkVideo doesn't work with input streams so
                                 // we need to store the file.
                                 // See: https://gitlab.gnome.org/GNOME/gtk/-/issues/4062
                                 let mut path = cache_dir();
-                                path.push(obj.body().unwrap());
+                                path.push(body.unwrap());
                                 let file = gio::File::for_path(path);
                                 file.replace_contents(
                                     &data,
@@ -439,11 +435,19 @@ impl MessageMedia {
                                 media_file.set_muted(true);
                                 media_file.connect_prepared_notify(|media_file| media_file.play());
 
-                                VideoPlayer::new(&media_file).upcast()
+                                let child = if let Some(Ok(child)) =
+                                    priv_.media.child().map(|w| w.downcast::<VideoPlayer>())
+                                {
+                                    child
+                                } else {
+                                    let child = VideoPlayer::new();
+                                    priv_.media.set_child(Some(&child));
+                                    child
+                                };
+                                child.set_media_file(&media_file)
                             }
                         };
 
-                        priv_.media.set_child(Some(&child));
                         obj.set_state(MediaState::Ready);
                     }
                     Ok(None) => {
diff --git a/src/session/content/room_history/message_row/mod.rs 
b/src/session/content/room_history/message_row/mod.rs
index d3a618b0..c6359641 100644
--- a/src/session/content/room_history/message_row/mod.rs
+++ b/src/session/content/room_history/message_row/mod.rs
@@ -231,8 +231,16 @@ impl MessageRow {
                         child.set_filename(Some(filename));
                     }
                     MessageType::Image(message) => {
-                        let child = MessageMedia::image(message, &event.room().session());
-                        priv_.content.set_child(Some(&child));
+                        let child = if let Some(Ok(child)) =
+                            priv_.content.child().map(|w| w.downcast::<MessageMedia>())
+                        {
+                            child
+                        } else {
+                            let child = MessageMedia::new();
+                            priv_.content.set_child(Some(&child));
+                            child
+                        };
+                        child.image(message, &event.room().session());
                     }
                     MessageType::Location(_message) => {}
                     MessageType::Notice(message) => {
@@ -272,8 +280,16 @@ impl MessageRow {
                         child.markup(message.formatted, message.body);
                     }
                     MessageType::Video(message) => {
-                        let child = MessageMedia::video(message, &event.room().session());
-                        priv_.content.set_child(Some(&child));
+                        let child = if let Some(Ok(child)) =
+                            priv_.content.child().map(|w| w.downcast::<MessageMedia>())
+                        {
+                            child
+                        } else {
+                            let child = MessageMedia::new();
+                            priv_.content.set_child(Some(&child));
+                            child
+                        };
+                        child.video(message, &event.room().session());
                     }
                     MessageType::VerificationRequest(_message) => {}
                     _ => {
@@ -282,8 +298,16 @@ impl MessageRow {
                 }
             }
             Some(AnyMessageEventContent::Sticker(content)) => {
-                let child = MessageMedia::sticker(content, &event.room().session());
-                priv_.content.set_child(Some(&child));
+                let child = if let Some(Ok(child)) =
+                    priv_.content.child().map(|w| w.downcast::<MessageMedia>())
+                {
+                    child
+                } else {
+                    let child = MessageMedia::new();
+                    priv_.content.set_child(Some(&child));
+                    child
+                };
+                child.sticker(content, &event.room().session());
             }
             Some(AnyMessageEventContent::RoomEncrypted(content)) => {
                 warn!("Couldn't decrypt event {:?}", content);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]