[fractal] session: Replace listview with listbox in AccountSwitcher



commit e069053cdae78006373db4d60bc906d7c1c3d48b
Author: Julian Sparber <julian sparber net>
Date:   Tue Apr 19 15:07:55 2022 +0200

    session: Replace listview with listbox in AccountSwitcher
    
    This reworks the entiere account switcher.
    
    Fixes: https://gitlab.gnome.org/GNOME/fractal/-/issues/931
    Fixes: https://gitlab.gnome.org/GNOME/fractal/-/issues/898

 data/resources/resources.gresource.xml             |   1 -
 data/resources/style.css                           |  16 +-
 data/resources/ui/add-account-row.ui               |  23 ---
 data/resources/ui/avatar-with-selection.ui         |   6 +-
 data/resources/ui/sidebar-account-switcher.ui      |  45 ++++-
 data/resources/ui/sidebar.ui                       |   5 +-
 data/resources/ui/user-entry-row.ui                |  23 +--
 .../account_switcher/avatar_with_selection.rs      |  15 +-
 src/account_switcher/mod.rs                        | 215 +++++++++++++++++++++
 src/account_switcher/user_entry.rs                 | 145 ++++++++++++++
 src/main.rs                                        |   1 +
 src/session/mod.rs                                 |   8 +-
 .../sidebar/account_switcher/add_account.rs        |  48 -----
 src/session/sidebar/account_switcher/item.rs       | 163 ----------------
 src/session/sidebar/account_switcher/mod.rs        | 141 --------------
 src/session/sidebar/account_switcher/user_entry.rs | 140 --------------
 src/session/sidebar/mod.rs                         |  41 ++--
 src/window.rs                                      |   9 +-
 18 files changed, 477 insertions(+), 568 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index a364c6e5a..308ca019d 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -27,7 +27,6 @@
     <file compressed="true" preprocess="xml-stripblanks" 
alias="account-settings-devices-page.ui">ui/account-settings-devices-page.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="account-settings-user-page.ui">ui/account-settings-user-page.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="account-settings.ui">ui/account-settings.ui</file>
-    <file compressed="true" preprocess="xml-stripblanks" 
alias="add-account-row.ui">ui/add-account-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="attachment-dialog.ui">ui/attachment-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="avatar-with-selection.ui">ui/avatar-with-selection.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="components-action-button.ui">ui/components-action-button.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index ee2cb0867..436166817 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -207,12 +207,18 @@ login {
 
 /* Account switcher */
 
-#account-switcher row {
+.account-switcher .account-switcher-row {
   border-radius: 10px;
-  margin-top: 2px;
-  margin-bottom: 2px;
-  padding-top: 7px;
-  padding-bottom: 7px;
+  margin: 3px 0px;
+  padding: 6px 12px;
+}
+
+.account-switcher .account-switcher-row:first-child {
+  margin-top: 0px;
+}
+
+.account-switcher .account-switcher-row:last-child {
+  margin-bottom: 0px;
 }
 
 #new-login-icon {
diff --git a/data/resources/ui/avatar-with-selection.ui b/data/resources/ui/avatar-with-selection.ui
index 5ae059530..b94d668cc 100644
--- a/data/resources/ui/avatar-with-selection.ui
+++ b/data/resources/ui/avatar-with-selection.ui
@@ -8,13 +8,13 @@
         </child>
         <child type="overlay">
           <object class="GtkImage" id="checkmark">
-            <style>
-              <class name="blue-checkmark" />
-            </style>
             <property name="visible">false</property>
             <property name="halign">end</property>
             <property name="valign">end</property>
             <property name="icon-name">emblem-ok-symbolic</property>
+            <style>
+              <class name="blue-checkmark" />
+            </style>
           </object>
         </child>
       </object>
diff --git a/data/resources/ui/sidebar-account-switcher.ui b/data/resources/ui/sidebar-account-switcher.ui
index 74dace8d0..14f9f4044 100644
--- a/data/resources/ui/sidebar-account-switcher.ui
+++ b/data/resources/ui/sidebar-account-switcher.ui
@@ -1,11 +1,50 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="AccountSwitcher" parent="GtkPopover">
-    <property name="name">account-switcher</property>
     <child>
-      <object class="GtkListView" id="entries">
-        <property name="single-click-activate">true</property>
+      <object class="GtkListBox" id="entries">
+        <property name="activate_on_single_click">true</property>
+        <child>
+          <object class="GtkListBoxRow">
+            <property name="selectable">false</property>
+            <property name="activatable">false</property>
+            <property name="child">
+              <object class="GtkSeparator"/>
+            </property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkListBoxRow">
+            <property name="selectable">false</property>
+            <property name="action-name">app.new-session</property>
+            <property name="child">
+              <object class="GtkBox">
+                <property name="spacing">10</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="name">new-login-icon</property>
+                    <property name="icon-name">list-add-symbolic</property>
+                    <property name="pixel-size">16</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="use-underline">true</property>
+                    <property name="label">_Add Account</property>
+                  </object>
+                </child>
+              </object>
+            </property>
+            <style>
+              <class name="account-switcher-row"/>
+            </style>
+          </object>
+        </child>
       </object>
     </child>
+    <style>
+      <class name="account-switcher"/>
+    </style>
   </template>
 </interface>
+
diff --git a/data/resources/ui/sidebar.ui b/data/resources/ui/sidebar.ui
index 2fe1a64ef..9a3d73fa3 100644
--- a/data/resources/ui/sidebar.ui
+++ b/data/resources/ui/sidebar.ui
@@ -86,10 +86,7 @@
             </property>
             <property name="show-end-title-buttons" bind-source="Sidebar" bind-property="compact" 
bind-flags="sync-create"/>
             <child type="start">
-              <object class="GtkMenuButton" id="accounts_button">
-                <property name="popover">
-                  <object class="AccountSwitcher" id="account_switcher"/>
-                </property>
+              <object class="GtkMenuButton" id="account_switcher_button">
                 <accessibility>
                   <property name="label" translatable="yes">Switch Accounts</property>
                 </accessibility>
diff --git a/data/resources/ui/user-entry-row.ui b/data/resources/ui/user-entry-row.ui
index a7a7b619a..b44237402 100644
--- a/data/resources/ui/user-entry-row.ui
+++ b/data/resources/ui/user-entry-row.ui
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <template class="UserEntryRow" parent="AdwBin">
+  <template class="UserEntryRow" parent="GtkListBoxRow">
+    <property name="selectable">false</property>
+    <style>
+      <class name="account-switcher-row"/>
+    </style>
     <child>
       <object class="GtkBox">
         <property name="spacing">10</property>
@@ -10,9 +14,7 @@
             <binding name="item">
               <lookup name="avatar" type="User">
                 <lookup name="user" type="Session">
-                  <lookup name="child" type="GtkStackPage">
-                    <lookup name="session-page">UserEntryRow</lookup>
-                  </lookup>
+                  <lookup name="session">UserEntryRow</lookup>
                 </lookup>
               </lookup>
             </binding>
@@ -25,12 +27,11 @@
             <child>
               <object class="GtkLabel" id="display_name">
                 <property name="xalign">0.0</property>
+                <property name="hexpand">True</property>
                 <binding name="label">
                   <lookup name="display-name" type="User">
                     <lookup name="user" type="Session">
-                      <lookup name="child" type="GtkStackPage">
-                        <lookup name="session-page">UserEntryRow</lookup>
-                      </lookup>
+                      <lookup name="session">UserEntryRow</lookup>
                     </lookup>
                   </lookup>
                 </binding>
@@ -39,12 +40,11 @@
             <child>
               <object class="GtkLabel" id="user_id">
                 <property name="xalign">0.0</property>
+                <property name="hexpand">True</property>
                 <binding name="label">
                   <lookup name="user-id" type="User">
                     <lookup name="user" type="Session">
-                      <lookup name="child" type="GtkStackPage">
-                        <lookup name="session-page">UserEntryRow</lookup>
-                      </lookup>
+                      <lookup name="session">UserEntryRow</lookup>
                     </lookup>
                   </lookup>
                 </binding>
@@ -59,9 +59,9 @@
         <child>
           <object class="GtkButton">
             <property name="icon-name">applications-system-symbolic</property>
-            <property name="action-name">user-entry-row.open-account-settings</property>
             <property name="valign">center</property>
             <property name="halign">center</property>
+            <signal name="clicked" handler="show_account_settings" swapped="true"/>
             <style>
               <class name="circular"/>
             </style>
@@ -71,3 +71,4 @@
     </child>
   </template>
 </interface>
+
diff --git a/src/session/sidebar/account_switcher/avatar_with_selection.rs 
b/src/account_switcher/avatar_with_selection.rs
similarity index 89%
rename from src/session/sidebar/account_switcher/avatar_with_selection.rs
rename to src/account_switcher/avatar_with_selection.rs
index 45f55fcea..ed133cb6f 100644
--- a/src/session/sidebar/account_switcher/avatar_with_selection.rs
+++ b/src/account_switcher/avatar_with_selection.rs
@@ -59,7 +59,7 @@ mod imp {
                         "Selected",
                         "Style helper for the inner Avatar",
                         false,
-                        glib::ParamFlags::WRITABLE,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
                 ]
             });
@@ -82,10 +82,11 @@ mod imp {
             }
         }
 
-        fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
                 "item" => self.child_avatar.item().to_value(),
                 "size" => self.child_avatar.size().to_value(),
+                "selected" => obj.is_selected().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -109,6 +110,10 @@ impl AvatarWithSelection {
     pub fn set_selected(&self, selected: bool) {
         let priv_ = self.imp();
 
+        if self.is_selected() == selected {
+            return;
+        }
+
         priv_.checkmark.set_visible(selected);
 
         if selected {
@@ -116,6 +121,12 @@ impl AvatarWithSelection {
         } else {
             priv_.child_avatar.remove_css_class("selected-avatar");
         }
+
+        self.notify("selected");
+    }
+
+    pub fn is_selected(&self) -> bool {
+        self.imp().checkmark.get_visible()
     }
 
     pub fn avatar(&self) -> &Avatar {
diff --git a/src/account_switcher/mod.rs b/src/account_switcher/mod.rs
new file mode 100644
index 000000000..477ed72b4
--- /dev/null
+++ b/src/account_switcher/mod.rs
@@ -0,0 +1,215 @@
+use gtk::{
+    glib::{self, clone},
+    prelude::*,
+    subclass::prelude::*,
+    CompositeTemplate, SelectionModel,
+};
+
+use crate::session::Session;
+
+mod avatar_with_selection;
+mod user_entry;
+
+use user_entry::UserEntryRow;
+
+mod imp {
+    use std::cell::RefCell;
+
+    use glib::subclass::InitializingObject;
+    use once_cell::sync::Lazy;
+
+    use super::*;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/Fractal/sidebar-account-switcher.ui")]
+    pub struct AccountSwitcher {
+        #[template_child]
+        pub entries: TemplateChild<gtk::ListBox>,
+        pub pages: RefCell<Option<gtk::SelectionModel>>,
+        pub pages_handler: RefCell<Option<glib::SignalHandlerId>>,
+        pub selection_handler: RefCell<Option<glib::SignalHandlerId>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for AccountSwitcher {
+        const NAME: &'static str = "AccountSwitcher";
+        type Type = super::AccountSwitcher;
+        type ParentType = gtk::Popover;
+
+        fn class_init(klass: &mut Self::Class) {
+            Self::bind_template(klass);
+
+            klass.install_action("account-switcher.close", None, move |item, _, _| {
+                item.popdown();
+            });
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for AccountSwitcher {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpecObject::new(
+                    "pages",
+                    "Pages",
+                    "A model containing the stack pages for each logged in account",
+                    gtk::SelectionModel::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() {
+                "pages" => obj.set_pages(value.get().unwrap()),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "pages" => obj.pages().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn constructed(&self, obj: &Self::Type) {
+            self.parent_constructed(obj);
+
+            self.entries.connect_row_activated(move |_, row| {
+                row.activate_action("account-switcher.close", None).unwrap();
+
+                if let Some(session) = row
+                    .downcast_ref::<UserEntryRow>()
+                    .and_then(|row| row.session())
+                {
+                    session
+                        .parent()
+                        .unwrap()
+                        .downcast::<gtk::Stack>()
+                        .unwrap()
+                        .set_visible_child(&session);
+                }
+            });
+        }
+    }
+
+    impl WidgetImpl for AccountSwitcher {}
+    impl PopoverImpl for AccountSwitcher {}
+}
+
+glib::wrapper! {
+    pub struct AccountSwitcher(ObjectSubclass<imp::AccountSwitcher>)
+        @extends gtk::Widget, gtk::Popover, @implements gtk::Accessible;
+}
+
+impl AccountSwitcher {
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create UserEntryRow")
+    }
+
+    pub fn set_pages(&self, pages: Option<gtk::SelectionModel>) {
+        let priv_ = self.imp();
+        let prev_pages = self.pages();
+
+        if pages == prev_pages {
+            return;
+        }
+        if let Some(prev_pages) = prev_pages {
+            if let Some(handler) = priv_.pages_handler.take() {
+                prev_pages.disconnect(handler);
+            }
+
+            if let Some(handler) = priv_.selection_handler.take() {
+                prev_pages.disconnect(handler);
+            }
+        }
+
+        if let Some(ref pages) = pages {
+            let handler = pages.connect_items_changed(
+                clone!(@weak self as obj => move |model, position, removed, added| {
+                    obj.update_rows(model, position, removed, added);
+                }),
+            );
+
+            priv_.pages_handler.replace(Some(handler));
+
+            let handler = pages.connect_selection_changed(
+                clone!(@weak self as obj => move |_, position, n_items| {
+                    obj.update_selection(position, n_items);
+                }),
+            );
+
+            priv_.selection_handler.replace(Some(handler));
+
+            self.update_rows(pages, 0, 0, pages.n_items());
+        }
+
+        self.imp().pages.replace(pages);
+        self.notify("pages");
+    }
+
+    pub fn pages(&self) -> Option<gtk::SelectionModel> {
+        self.imp().pages.borrow().clone()
+    }
+
+    fn update_rows(&self, model: &SelectionModel, position: u32, removed: u32, added: u32) {
+        let listbox = self.imp().entries.get();
+        for _ in 0..removed {
+            if let Some(row) = listbox.row_at_index(position as i32) {
+                listbox.remove(&row);
+            }
+        }
+        for i in position..(position + added) {
+            let row = UserEntryRow::new(
+                &model
+                    .item(i)
+                    .unwrap()
+                    .downcast::<gtk::StackPage>()
+                    .unwrap()
+                    .child()
+                    .downcast::<Session>()
+                    .unwrap(),
+            );
+            row.set_selected(model.is_selected(i));
+            listbox.insert(&row, i as i32);
+        }
+    }
+
+    fn update_selection(&self, position: u32, n_items: u32) {
+        let priv_ = self.imp();
+        let pages = priv_.pages.borrow();
+        let pages = if let Some(pages) = &*pages {
+            pages
+        } else {
+            return;
+        };
+
+        for i in position..(position + n_items) {
+            if let Some(row) = priv_
+                .entries
+                .row_at_index(i as i32)
+                .and_then(|row| row.downcast::<UserEntryRow>().ok())
+            {
+                row.set_selected(pages.is_selected(i));
+            }
+        }
+    }
+}
+
+impl Default for AccountSwitcher {
+    fn default() -> Self {
+        Self::new()
+    }
+}
diff --git a/src/account_switcher/user_entry.rs b/src/account_switcher/user_entry.rs
new file mode 100644
index 000000000..bceb1bc9a
--- /dev/null
+++ b/src/account_switcher/user_entry.rs
@@ -0,0 +1,145 @@
+use adw::subclass::prelude::BinImpl;
+use gtk::{self, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+use super::avatar_with_selection::AvatarWithSelection;
+use crate::session::Session;
+
+mod imp {
+    use glib::subclass::InitializingObject;
+    use once_cell::sync::Lazy;
+
+    use super::*;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/Fractal/user-entry-row.ui")]
+    pub struct UserEntryRow {
+        #[template_child]
+        pub account_avatar: TemplateChild<AvatarWithSelection>,
+        #[template_child]
+        pub display_name: TemplateChild<gtk::Label>,
+        #[template_child]
+        pub user_id: TemplateChild<gtk::Label>,
+        pub session: glib::WeakRef<Session>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for UserEntryRow {
+        const NAME: &'static str = "UserEntryRow";
+        type Type = super::UserEntryRow;
+        type ParentType = gtk::ListBoxRow;
+
+        fn class_init(klass: &mut Self::Class) {
+            AvatarWithSelection::static_type();
+            Self::bind_template(klass);
+            Self::Type::bind_template_callbacks(klass);
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for UserEntryRow {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![
+                    glib::ParamSpecObject::new(
+                        "session",
+                        "Session",
+                        "The session this entry represents",
+                        Session::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
+                    glib::ParamSpecBoolean::new(
+                        "selected",
+                        "Selected",
+                        "Whether this session is selected",
+                        false,
+                        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() {
+                "session" => obj.set_session(value.get().unwrap()),
+                "selected" => obj.set_selected(value.get().unwrap()),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "session" => obj.session().to_value(),
+                "selected" => obj.is_selected().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+
+    impl WidgetImpl for UserEntryRow {}
+    impl BinImpl for UserEntryRow {}
+    impl ListBoxRowImpl for UserEntryRow {}
+}
+
+glib::wrapper! {
+    pub struct UserEntryRow(ObjectSubclass<imp::UserEntryRow>)
+        @extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible;
+}
+
+#[gtk::template_callbacks]
+impl UserEntryRow {
+    pub fn new(session: &Session) -> Self {
+        glib::Object::new(&[("session", session)]).expect("Failed to create UserEntryRow")
+    }
+
+    pub fn set_selected(&self, selected: bool) {
+        let priv_ = self.imp();
+
+        if priv_.account_avatar.is_selected() == selected {
+            return;
+        }
+
+        priv_.account_avatar.set_selected(selected);
+
+        if selected {
+            priv_.display_name.add_css_class("bold");
+        } else {
+            priv_.display_name.remove_css_class("bold");
+        }
+
+        self.notify("selected");
+    }
+
+    pub fn is_selected(&self) -> bool {
+        self.imp().account_avatar.is_selected()
+    }
+
+    #[template_callback]
+    pub fn show_account_settings(&self) {
+        if let Some(session) = self.session() {
+            self.activate_action("account-switcher.close", None)
+                .unwrap();
+            session
+                .activate_action("session.open-account-settings", None)
+                .unwrap();
+        }
+    }
+
+    pub fn session(&self) -> Option<Session> {
+        self.imp().session.upgrade()
+    }
+
+    pub fn set_session(&self, session: Option<&Session>) {
+        self.imp().session.set(session);
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index 3efd8ca87..5030ee645 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,6 +8,7 @@ mod application;
 mod config;
 mod prelude;
 
+mod account_switcher;
 mod components;
 mod contrib;
 mod error_page;
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 30b152b33..2b8e61e96 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -20,7 +20,7 @@ use gtk::{
     glib::{clone, source::SourceId, SyncSender},
     prelude::*,
     subclass::prelude::*,
-    CompositeTemplate, SelectionModel,
+    CompositeTemplate,
 };
 use log::{debug, error, warn};
 use matrix_sdk::{
@@ -710,12 +710,6 @@ impl Session {
         }
     }
 
-    pub fn set_logged_in_users(&self, sessions_stack_pages: &SelectionModel) {
-        self.imp()
-            .sidebar
-            .set_logged_in_users(sessions_stack_pages, self);
-    }
-
     /// Returns the parent GtkWindow containing this widget.
     fn parent_window(&self) -> Option<Window> {
         self.root()?.downcast().ok()
diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs
index 199ac91f1..3686e77c7 100644
--- a/src/session/sidebar/mod.rs
+++ b/src/session/sidebar/mod.rs
@@ -1,4 +1,3 @@
-mod account_switcher;
 mod category;
 mod category_row;
 mod category_type;
@@ -12,13 +11,12 @@ mod selection;
 mod sidebar_item;
 mod verification_row;
 
-use account_switcher::AccountSwitcher;
 use adw::{prelude::*, subclass::prelude::*};
 use gtk::{
     gio, glib,
     glib::{clone, closure},
     subclass::prelude::*,
-    CompositeTemplate, SelectionModel,
+    CompositeTemplate,
 };
 
 pub use self::{
@@ -38,8 +36,9 @@ use crate::{
     session::{
         room::{Room, RoomType},
         verification::IdentityVerification,
-        Session, User,
+        User,
     },
+    Window,
 };
 
 mod imp {
@@ -61,14 +60,14 @@ mod imp {
         #[template_child]
         pub headerbar: TemplateChild<adw::HeaderBar>,
         #[template_child]
-        pub account_switcher: TemplateChild<AccountSwitcher>,
-        #[template_child]
         pub listview: TemplateChild<gtk::ListView>,
         #[template_child]
         pub room_search_entry: TemplateChild<gtk::SearchEntry>,
         #[template_child]
         pub room_search: TemplateChild<gtk::SearchBar>,
         #[template_child]
+        pub account_switcher_button: TemplateChild<gtk::MenuButton>,
+        #[template_child]
         pub room_row_menu: TemplateChild<gio::MenuModel>,
         pub room_row_popover: OnceCell<gtk::PopoverMenu>,
         pub user: RefCell<Option<User>>,
@@ -242,6 +241,21 @@ mod imp {
                     _ => {}
                 }
             });
+
+            self.account_switcher_button.set_create_popup_func(clone!(@weak obj => move |btn| {
+                if let Some(window) = obj.parent_window() {
+                    let account_switcher = window.account_switcher();
+                    // We need to remove the popover from the previous MenuButton, if any
+                    if let Some(prev_parent) = account_switcher.parent().and_then(|btn| 
btn.downcast::<gtk::MenuButton>().ok()) {
+                        if &prev_parent == btn {
+                            return;
+                        } else {
+                            prev_parent.set_popover(gtk::Widget::NONE);
+                        }
+                    }
+                    btn.set_popover(Some(account_switcher));
+                }
+            }));
         }
     }
 
@@ -362,16 +376,6 @@ impl Sidebar {
         self.notify("user");
     }
 
-    pub fn set_logged_in_users(
-        &self,
-        sessions_stack_pages: &SelectionModel,
-        session_root: &Session,
-    ) {
-        self.imp()
-            .account_switcher
-            .set_logged_in_users(sessions_stack_pages, session_root);
-    }
-
     pub fn drop_source_type(&self) -> Option<RoomType> {
         self.imp().drop_source_type.get()
     }
@@ -488,6 +492,11 @@ impl Sidebar {
             .room_row_popover
             .get_or_init(|| gtk::PopoverMenu::from_model(Some(&*priv_.room_row_menu)))
     }
+
+    /// Returns the parent `Window` containing the `Sidebar`
+    fn parent_window(&self) -> Option<Window> {
+        self.root()?.downcast().ok()
+    }
 }
 
 impl Default for Sidebar {
diff --git a/src/window.rs b/src/window.rs
index a53fe7b5f..3f911e4bc 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -5,6 +5,7 @@ use gtk::{self, gio, glib, glib::clone, prelude::*, subclass::prelude::*, Compos
 use log::warn;
 
 use crate::{
+    account_switcher::AccountSwitcher,
     components::{InAppNotification, Toast},
     config::{APP_ID, PROFILE},
     secret::{self, SecretError},
@@ -34,6 +35,7 @@ mod imp {
         pub sessions: TemplateChild<gtk::Stack>,
         #[template_child]
         pub error_list: TemplateChild<gio::ListStore>,
+        pub account_switcher: AccountSwitcher,
     }
 
     #[glib::object_subclass]
@@ -103,6 +105,8 @@ mod imp {
             spawn!(clone!(@weak obj => async move {
                 obj.restore_sessions().await;
             }));
+
+            self.account_switcher.set_pages(Some(self.sessions.pages()));
         }
     }
 
@@ -136,7 +140,6 @@ impl Window {
         let priv_ = &self.imp();
         let prev_has_sessions = self.has_sessions();
 
-        session.set_logged_in_users(&priv_.sessions.pages());
         priv_.sessions.add_child(session);
         priv_.sessions.set_visible_child(session);
         // We need to grab the focus so that keyboard shortcuts work
@@ -290,4 +293,8 @@ impl Window {
     pub fn add_toast(&self, toast: &Toast) {
         self.imp().error_list.append(toast);
     }
+
+    pub fn account_switcher(&self) -> &AccountSwitcher {
+        &self.imp().account_switcher
+    }
 }


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