[fractal] context-menu-bin: Create popup only when needed



commit eae73592858d55134cce833239ea950ff70f7524
Author: Julian Sparber <julian sparber net>
Date:   Wed Apr 6 14:35:24 2022 +0200

    context-menu-bin: Create popup only when needed
    
    This reduces the time needed to build a Sidebar::RoomRow and
    RoomHisotry::MessageRow, by a sinificant amount.

 data/resources/ui/sidebar-room-row.ui        |   3 +-
 src/components/context_menu_bin.rs           | 128 ++++++++-------------------
 src/session/content/room_history/item_row.rs | 109 +++++++----------------
 src/session/sidebar/room_row.rs              |   8 +-
 4 files changed, 79 insertions(+), 169 deletions(-)
---
diff --git a/data/resources/ui/sidebar-room-row.ui b/data/resources/ui/sidebar-room-row.ui
index ba2c0ce84..d8ab375e8 100644
--- a/data/resources/ui/sidebar-room-row.ui
+++ b/data/resources/ui/sidebar-room-row.ui
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <menu id="room-row-menu">
+  <menu id="room_row_menu">
     <section>
       <item>
         <attribute name="label" translatable="yes">_Accept</attribute>
@@ -54,7 +54,6 @@
     </section>
   </menu>
   <template class="SidebarRoomRow" parent="ContextMenuBin">
-    <property name="context-menu">room-row-menu</property>
     <child>
       <object class="GtkBox">
         <property name="spacing">12</property>
diff --git a/src/components/context_menu_bin.rs b/src/components/context_menu_bin.rs
index e20672c90..758f61b27 100644
--- a/src/components/context_menu_bin.rs
+++ b/src/components/context_menu_bin.rs
@@ -1,35 +1,23 @@
 use adw::subclass::prelude::*;
-use gtk::{gdk, gio, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use gtk::{gdk, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
 use log::debug;
 
 mod imp {
+    use std::cell::RefCell;
+
     use glib::subclass::InitializingObject;
 
     use super::*;
+    type FactoryFn = RefCell<Option<Box<dyn Fn(&super::ContextMenuBin, &gtk::PopoverMenu)>>>;
 
-    #[derive(Debug, CompositeTemplate)]
+    #[derive(Default, CompositeTemplate)]
     #[template(resource = "/org/gnome/Fractal/context-menu-bin.ui")]
     pub struct ContextMenuBin {
         #[template_child]
         pub click_gesture: TemplateChild<gtk::GestureClick>,
         #[template_child]
         pub long_press_gesture: TemplateChild<gtk::GestureLongPress>,
-        pub popover: gtk::PopoverMenu,
-    }
-
-    impl Default for ContextMenuBin {
-        fn default() -> Self {
-            Self {
-                click_gesture: Default::default(),
-                long_press_gesture: Default::default(),
-                // WORKAROUND: there is some issue with creating the popover from the template
-                popover: gtk::PopoverMenu::builder()
-                    .position(gtk::PositionType::Bottom)
-                    .has_arrow(false)
-                    .halign(gtk::Align::Start)
-                    .build(),
-            }
-        }
+        pub factory: FactoryFn,
     }
 
     #[glib::object_subclass]
@@ -56,10 +44,6 @@ mod imp {
                 "context-menu.activate",
                 None,
             );
-
-            klass.install_action("context-menu.close", None, move |widget, _, _| {
-                widget.imp().popover.popdown();
-            });
         }
 
         fn instance_init(obj: &InitializingObject<Self>) {
@@ -68,45 +52,7 @@ mod imp {
     }
 
     impl ObjectImpl for ContextMenuBin {
-        fn properties() -> &'static [glib::ParamSpec] {
-            use once_cell::sync::Lazy;
-            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
-                vec![glib::ParamSpecObject::new(
-                    "context-menu",
-                    "Context Menu",
-                    "The context menu",
-                    gio::MenuModel::static_type(),
-                    glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
-                )]
-            });
-
-            PROPERTIES.as_ref()
-        }
-
-        fn set_property(
-            &self,
-            obj: &Self::Type,
-            _id: usize,
-            value: &glib::Value,
-            pspec: &glib::ParamSpec,
-        ) {
-            match pspec.name() {
-                "context-menu" => {
-                    obj.set_context_menu(value.get::<Option<gio::MenuModel>>().unwrap().as_ref())
-                }
-                _ => unimplemented!(),
-            }
-        }
-
-        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
-            match pspec.name() {
-                "context-menu" => obj.context_menu().to_value(),
-                _ => unimplemented!(),
-            }
-        }
-
         fn constructed(&self, obj: &Self::Type) {
-            self.popover.set_parent(obj);
             self.long_press_gesture
                 .connect_pressed(clone!(@weak obj => move |gesture, x, y| {
                     gesture.set_state(gtk::EventSequenceState::Claimed);
@@ -126,10 +72,6 @@ mod imp {
             );
             self.parent_constructed(obj);
         }
-
-        fn dispose(&self, _obj: &Self::Type) {
-            self.popover.unparent();
-        }
     }
 
     impl WidgetImpl for ContextMenuBin {}
@@ -149,46 +91,50 @@ impl ContextMenuBin {
     }
 
     fn open_menu_at(&self, x: i32, y: i32) {
-        let popover = &self.imp().popover;
-
         debug!("Context menu was activated");
+        if let Some(factory) = &*self.imp().factory.borrow() {
+            let popover = gtk::PopoverMenu::builder()
+                .position(gtk::PositionType::Bottom)
+                .has_arrow(false)
+                .halign(gtk::Align::Start)
+                .build();
 
-        if popover.menu_model().is_none() {
-            return;
-        }
+            popover.set_parent(self);
+
+            popover.connect_closed(|popover| {
+                popover.unparent();
+            });
+
+            (factory)(self, &popover);
 
-        popover.set_pointing_to(Some(&gdk::Rectangle::new(x, y, 0, 0)));
-        popover.popup();
+            popover.set_pointing_to(Some(&gdk::Rectangle::new(x, y, 0, 0)));
+            popover.popup();
+        }
     }
 }
 
 pub trait ContextMenuBinExt: 'static {
-    /// Set the `MenuModel` used in the context menu.
-    fn set_context_menu(&self, menu: Option<&gio::MenuModel>);
-
-    /// Get the `MenuModel` used in the context menu.
-    fn context_menu(&self) -> Option<gio::MenuModel>;
+    /// Set the closure used to create the content of the `gtk::PopoverMenu`
+    fn set_factory<F>(&self, factory: F)
+    where
+        F: Fn(&Self, &gtk::PopoverMenu) + 'static;
 
-    /// Get the `PopoverMenu` used in the context menu.
-    fn popover(&self) -> &gtk::PopoverMenu;
+    fn remove_factory(&self);
 }
 
 impl<O: IsA<ContextMenuBin>> ContextMenuBinExt for O {
-    fn set_context_menu(&self, menu: Option<&gio::MenuModel>) {
-        if self.context_menu().as_ref() == menu {
-            return;
-        }
-
-        self.upcast_ref().imp().popover.set_menu_model(menu);
-        self.notify("context-menu");
-    }
-
-    fn context_menu(&self) -> Option<gio::MenuModel> {
-        self.upcast_ref().imp().popover.menu_model()
+    fn set_factory<F>(&self, factory: F)
+    where
+        F: Fn(&O, &gtk::PopoverMenu) + 'static,
+    {
+        let f = move |obj: &ContextMenuBin, popover: &gtk::PopoverMenu| {
+            factory(obj.downcast_ref::<O>().unwrap(), popover);
+        };
+        self.upcast_ref().imp().factory.replace(Some(Box::new(f)));
     }
 
-    fn popover(&self) -> &gtk::PopoverMenu {
-        &self.upcast_ref().imp().popover
+    fn remove_factory(&self) {
+        self.upcast_ref().imp().factory.take();
     }
 }
 
diff --git a/src/session/content/room_history/item_row.rs b/src/session/content/room_history/item_row.rs
index 7e3eb30b5..8c253ae11 100644
--- a/src/session/content/room_history/item_row.rs
+++ b/src/session/content/room_history/item_row.rs
@@ -7,7 +7,7 @@ use crate::{
     components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl, ReactionChooser},
     session::{
         content::room_history::{message_row::MessageRow, DividerRow, StateRow},
-        room::{Event, EventActions, Item, ItemType, ReactionList},
+        room::{Event, EventActions, Item, ItemType},
     },
 };
 
@@ -73,7 +73,7 @@ mod imp {
             }
         }
 
-        fn dispose(&self, obj: &Self::Type) {
+        fn dispose(&self, _obj: &Self::Type) {
             if let Some(ItemType::Event(event)) =
                 self.item.borrow().as_ref().map(|item| item.type_())
             {
@@ -81,8 +81,6 @@ mod imp {
                     event.disconnect(handler);
                 }
             }
-
-            obj.remove_reaction_chooser();
         }
     }
 
@@ -126,21 +124,25 @@ impl ItemRow {
         if let Some(ref item) = item {
             match item.type_() {
                 ItemType::Event(event) => {
-                    let action_group = self.set_event_actions(Some(event));
-
                     if event.message_content().is_some() {
-                        self.set_context_menu(Some(Self::event_message_menu_model()));
-                        self.set_reaction_chooser(event.reactions());
-
-                        // Open emoji chooser
-                        let more_reactions = gio::SimpleAction::new("more-reactions", None);
-                        more_reactions.connect_activate(clone!(@weak self as obj => move |_, _| {
-                            obj.show_emoji_chooser();
+                        let action_group = self.set_event_actions(Some(event)).unwrap();
+                        self.set_factory(clone!(@weak event => move |obj, popover| {
+                            popover.set_menu_model(Some(Self::event_message_menu_model()));
+                            let reaction_chooser = ReactionChooser::new();
+                            reaction_chooser.set_reactions(Some(event.reactions().to_owned()));
+                            popover.add_child(&reaction_chooser, "reaction-chooser");
+
+                            // Open emoji chooser
+                            let more_reactions = gio::SimpleAction::new("more-reactions", None);
+                            more_reactions.connect_activate(clone!(@weak obj, @weak popover => move |_, _| {
+                                obj.show_emoji_chooser(&popover);
+                            }));
+                            action_group.add_action(&more_reactions);
                         }));
-                        action_group.unwrap().add_action(&more_reactions);
                     } else {
-                        self.set_context_menu(Some(Self::event_state_menu_model()));
-                        self.remove_reaction_chooser();
+                        self.set_factory(|_, popover| {
+                            popover.set_menu_model(Some(Self::event_state_menu_model()));
+                        });
                     }
 
                     let event_notify_handler = event.connect_notify_local(
@@ -158,11 +160,8 @@ impl ItemRow {
                     self.set_event_widget(event);
                 }
                 ItemType::DayDivider(date) => {
-                    if self.context_menu().is_some() {
-                        self.set_context_menu(None);
-                        self.set_event_actions(None);
-                        self.remove_reaction_chooser();
-                    }
+                    self.remove_factory();
+                    self.set_event_actions(None);
 
                     let fmt = if date.year() == glib::DateTime::now_local().unwrap().year() {
                         // Translators: This is a date format in the day divider without the year
@@ -181,11 +180,8 @@ impl ItemRow {
                     };
                 }
                 ItemType::NewMessageDivider => {
-                    if self.context_menu().is_some() {
-                        self.set_context_menu(None);
-                        self.set_event_actions(None);
-                        self.remove_reaction_chooser();
-                    }
+                    self.remove_factory();
+                    self.set_event_actions(None);
 
                     let label = gettext("New Messages");
 
@@ -241,59 +237,22 @@ impl ItemRow {
         }
     }
 
-    /// Set the reaction chooser for the given `reactions`.
-    ///
-    /// If it doesn't exist, it is created
-    fn set_reaction_chooser(&self, reactions: &ReactionList) {
-        let priv_ = self.imp();
-
-        if priv_.reaction_chooser.borrow().is_none() {
-            let reaction_chooser = ReactionChooser::new();
-            self.popover()
-                .add_child(&reaction_chooser, "reaction-chooser");
-            priv_.reaction_chooser.replace(Some(reaction_chooser));
-        }
-
-        priv_
-            .reaction_chooser
-            .borrow()
-            .as_ref()
-            .unwrap()
-            .set_reactions(Some(reactions.to_owned()));
-    }
-
-    /// Remove the reaction chooser and the emoji chooser, if they exist.
-    fn remove_reaction_chooser(&self) {
-        let priv_ = self.imp();
-
-        if let Some(reaction_chooser) = priv_.reaction_chooser.take() {
-            reaction_chooser.unparent();
-        }
-
-        if let Some(emoji_chooser) = priv_.emoji_chooser.take() {
+    fn show_emoji_chooser(&self, popover: &gtk::PopoverMenu) {
+        let emoji_chooser = gtk::EmojiChooser::builder().has_arrow(false).build();
+        emoji_chooser.connect_emoji_picked(|emoji_chooser, emoji| {
+            emoji_chooser
+                .activate_action("event.toggle-reaction", Some(&emoji.to_variant()))
+                .unwrap();
+        });
+        emoji_chooser.set_parent(self);
+        emoji_chooser.connect_closed(|emoji_chooser| {
             emoji_chooser.unparent();
-        }
-    }
-
-    fn show_emoji_chooser(&self) {
-        let priv_ = self.imp();
-
-        if priv_.emoji_chooser.borrow().is_none() {
-            let emoji_chooser = gtk::EmojiChooser::builder().has_arrow(false).build();
-            emoji_chooser.connect_emoji_picked(|emoji_chooser, emoji| {
-                emoji_chooser
-                    .activate_action("event.toggle-reaction", Some(&emoji.to_variant()))
-                    .unwrap();
-            });
-            emoji_chooser.set_parent(self);
-            priv_.emoji_chooser.replace(Some(emoji_chooser));
-        }
+        });
 
-        let emoji_chooser = priv_.emoji_chooser.borrow().clone().unwrap();
-        let (_, rectangle) = self.popover().pointing_to();
+        let (_, rectangle) = popover.pointing_to();
         emoji_chooser.set_pointing_to(Some(&rectangle));
 
-        self.popover().popdown();
+        popover.popdown();
         emoji_chooser.popup();
     }
 }
diff --git a/src/session/sidebar/room_row.rs b/src/session/sidebar/room_row.rs
index f7a93f8ef..1e7d20da0 100644
--- a/src/session/sidebar/room_row.rs
+++ b/src/session/sidebar/room_row.rs
@@ -2,7 +2,7 @@ use adw::subclass::prelude::BinImpl;
 use gtk::{gdk, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
 
 use crate::{
-    components::{ContextMenuBin, ContextMenuBinImpl},
+    components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl},
     session::room::{HighlightFlags, Room, RoomType},
 };
 
@@ -24,6 +24,8 @@ mod imp {
         pub display_name: TemplateChild<gtk::Label>,
         #[template_child]
         pub notification_count: TemplateChild<gtk::Label>,
+        #[template_child]
+        pub room_row_menu: TemplateChild<gtk::gio::MenuModel>,
     }
 
     #[glib::object_subclass]
@@ -109,6 +111,10 @@ mod imp {
         fn constructed(&self, obj: &Self::Type) {
             self.parent_constructed(obj);
 
+            obj.set_factory(|obj, popover| {
+                popover.set_menu_model(Some(&obj.imp().room_row_menu.get()));
+            });
+
             // Allow to drag rooms
             let drag = gtk::DragSource::builder()
                 .actions(gdk::DragAction::MOVE)


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