[fractal/fractal-next] room-details: Add menu for members
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] room-details: Add menu for members
- Date: Wed, 19 Jan 2022 12:31:40 +0000 (UTC)
commit f26413bf4d62873991e2a37851f2df453cdb7fd4
Author: Julian Sparber <julian sparber net>
Date: Fri Dec 10 15:14:23 2021 +0100
room-details: Add menu for members
data/resources/resources.gresource.xml | 1 +
data/resources/ui/content-member-row.ui | 6 +
data/resources/ui/member-menu.ui | 34 +++++
src/meson.build | 1 +
.../room_details/member_page/member_menu.rs | 164 +++++++++++++++++++++
.../content/room_details/member_page/member_row.rs | 34 ++++-
.../content/room_details/member_page/mod.rs | 47 +++++-
src/session/content/room_details/mod.rs | 10 +-
8 files changed, 287 insertions(+), 10 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index be75fc61..f754a62c 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -32,6 +32,7 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="event-source-dialog.ui">ui/event-source-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="media-viewer.ui">ui/media-viewer.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks" alias="member-menu.ui">ui/member-menu.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="session.ui">ui/session.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="sidebar.ui">ui/sidebar.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-account-switcher.ui">ui/sidebar-account-switcher.ui</file>
diff --git a/data/resources/ui/content-member-row.ui b/data/resources/ui/content-member-row.ui
index 2e745ee5..209779b8 100644
--- a/data/resources/ui/content-member-row.ui
+++ b/data/resources/ui/content-member-row.ui
@@ -71,6 +71,12 @@
</child>
</object>
</child>
+ <child>
+ <object class="GtkToggleButton" id="menu_btn">
+ <property name="has-frame">False</property>
+ <property name="icon-name">view-more-symbolic</property>
+ </object>
+ </child>
</object>
</property>
</template>
diff --git a/data/resources/ui/member-menu.ui b/data/resources/ui/member-menu.ui
new file mode 100644
index 00000000..e64e1511
--- /dev/null
+++ b/data/resources/ui/member-menu.ui
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <menu id="menu_model">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Verify</attribute>
+ <attribute name="action">member.verify</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Make _Mod</attribute>
+ <attribute name="action">member.make-mod</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Make _Admin</attribute>
+ <attribute name="action">member.make-admin</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Kick</attribute>
+ <attribute name="action">member.kick</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
+
diff --git a/src/meson.build b/src/meson.build
index 97cafe3b..72ba437d 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -79,6 +79,7 @@ sources = files(
'session/content/room_details/invite_subpage/invitee_row.rs',
'session/content/room_details/member_page/mod.rs',
'session/content/room_details/member_page/member_row.rs',
+ 'session/content/room_details/member_page/member_menu.rs',
'session/content/room_details/mod.rs',
'session/content/verification/emoji.rs',
'session/content/verification/mod.rs',
diff --git a/src/session/content/room_details/member_page/member_menu.rs
b/src/session/content/room_details/member_page/member_menu.rs
new file mode 100644
index 00000000..6e9e05ce
--- /dev/null
+++ b/src/session/content/room_details/member_page/member_menu.rs
@@ -0,0 +1,164 @@
+use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
+
+use crate::session::room::Member;
+
+mod imp {
+ use super::*;
+ use once_cell::sync::Lazy;
+ use once_cell::unsync::OnceCell;
+ use std::cell::RefCell;
+
+ #[derive(Debug, Default)]
+ pub struct MemberMenu {
+ pub member: RefCell<Option<Member>>,
+ pub popover: OnceCell<gtk::PopoverMenu>,
+ pub destroy_handler: RefCell<Option<glib::signal::SignalHandlerId>>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for MemberMenu {
+ const NAME: &'static str = "ContentMemberMenu";
+ type Type = super::MemberMenu;
+ type ParentType = glib::Object;
+ }
+
+ impl ObjectImpl for MemberMenu {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpec::new_object(
+ "member",
+ "Member",
+ "The member this row is showing",
+ Member::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() {
+ "member" => obj.set_member(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "member" => obj.member().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+
+ obj.popover_menu()
+ .connect_closed(clone!(@weak obj => move |_| {
+ obj.close_popover();
+ }));
+ }
+ }
+}
+
+glib::wrapper! {
+ pub struct MemberMenu(ObjectSubclass<imp::MemberMenu>);
+}
+
+impl MemberMenu {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create MemberMenu")
+ }
+
+ pub fn member(&self) -> Option<Member> {
+ let priv_ = imp::MemberMenu::from_instance(self);
+ priv_.member.borrow().clone()
+ }
+
+ pub fn set_member(&self, member: Option<Member>) {
+ let priv_ = imp::MemberMenu::from_instance(self);
+
+ if self.member() == member {
+ return;
+ }
+
+ priv_.member.replace(member);
+ self.notify("member");
+ }
+
+ fn popover_menu(&self) -> >k::PopoverMenu {
+ let priv_ = imp::MemberMenu::from_instance(self);
+ priv_.popover.get_or_init(|| {
+ gtk::PopoverMenu::from_model(Some(
+ >k::Builder::from_resource("/org/gnome/FractalNext/member-menu.ui")
+ .object::<gio::MenuModel>("menu_model")
+ .unwrap(),
+ ))
+ })
+ }
+
+ /// Show the menu on the specific button
+ ///
+ /// For convenience it allows to set the member for which the popover is shown
+ pub fn present_popover(&self, button: >k::ToggleButton, member: Option<Member>) {
+ let priv_ = imp::MemberMenu::from_instance(self);
+ let popover = self.popover_menu();
+ let _guard = popover.freeze_notify();
+
+ self.close_popover();
+ self.unparent_popover();
+
+ self.set_member(member);
+
+ let handler = button.connect_destroy(clone!(@weak self as obj => move |_| {
+ obj.unparent_popover();
+ }));
+
+ priv_.destroy_handler.replace(Some(handler));
+
+ popover.set_parent(button);
+ popover.show();
+ }
+
+ fn unparent_popover(&self) {
+ let priv_ = imp::MemberMenu::from_instance(self);
+ let popover = self.popover_menu();
+
+ if let Some(parent) = popover.parent() {
+ if let Some(handler) = priv_.destroy_handler.take() {
+ parent.disconnect(handler);
+ }
+
+ popover.unparent();
+ }
+ }
+
+ /// Closes the popover
+ pub fn close_popover(&self) {
+ let popover = self.popover_menu();
+ let _guard = popover.freeze_notify();
+
+ if let Some(button) = popover.parent() {
+ if popover.is_visible() {
+ popover.hide();
+ }
+ button
+ .downcast::<gtk::ToggleButton>()
+ .expect("The parent of a MemberMenu needs to ba a gtk::ToggleButton")
+ .set_active(false);
+ }
+ }
+}
+
+impl Default for MemberMenu {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/src/session/content/room_details/member_page/member_row.rs
b/src/session/content/room_details/member_page/member_row.rs
index b87e871c..f3509f83 100644
--- a/src/session/content/room_details/member_page/member_row.rs
+++ b/src/session/content/room_details/member_page/member_row.rs
@@ -1,5 +1,6 @@
-use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use crate::session::content::RoomDetails;
use crate::session::room::Member;
use adw::subclass::prelude::BinImpl;
@@ -13,6 +14,8 @@ mod imp {
#[template(resource = "/org/gnome/FractalNext/content-member-row.ui")]
pub struct MemberRow {
pub member: RefCell<Option<Member>>,
+ #[template_child]
+ pub menu_btn: TemplateChild<gtk::ToggleButton>,
}
#[glib::object_subclass]
@@ -66,6 +69,21 @@ mod imp {
_ => unimplemented!(),
}
}
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+
+ self.menu_btn
+ .connect_toggled(clone!(@weak obj => move |btn| {
+ if let Some(details) = obj.details() {
+ let page = details.member_page();
+ let menu = page.member_menu();
+ if btn.is_active() {
+ menu.present_popover(btn, obj.member());
+ }
+ }
+ }));
+ }
}
impl WidgetImpl for MemberRow {}
impl BinImpl for MemberRow {}
@@ -93,7 +111,21 @@ impl MemberRow {
return;
}
+ // We need to update the member of the menu if it's shown for this row
+ if priv_.menu_btn.is_active() {
+ if let Some(details) = self.details() {
+ let page = details.member_page();
+ let menu = page.member_menu();
+
+ menu.set_member(member.clone());
+ }
+ }
+
priv_.member.replace(member);
self.notify("member");
}
+
+ fn details(&self) -> Option<RoomDetails> {
+ Some(self.root()?.downcast::<RoomDetails>().unwrap())
+ }
}
diff --git a/src/session/content/room_details/member_page/mod.rs
b/src/session/content/room_details/member_page/mod.rs
index f84f79f3..70c27883 100644
--- a/src/session/content/room_details/member_page/mod.rs
+++ b/src/session/content/room_details/member_page/mod.rs
@@ -5,13 +5,16 @@ use gtk::glib::{self, clone};
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
+mod member_menu;
mod member_row;
+use self::member_menu::MemberMenu;
use self::member_row::MemberRow;
use crate::components::{Avatar, Badge};
use crate::prelude::*;
use crate::session::content::RoomDetails;
use crate::session::room::{Member, RoomAction};
use crate::session::Room;
+use log::warn;
mod imp {
use super::*;
@@ -31,6 +34,7 @@ mod imp {
pub members_search_entry: TemplateChild<gtk::SearchEntry>,
#[template_child]
pub members_list_view: TemplateChild<gtk::ListView>,
+ pub member_menu: OnceCell<MemberMenu>,
}
#[glib::object_subclass]
@@ -44,6 +48,14 @@ mod imp {
Badge::static_type();
MemberRow::static_type();
Self::bind_template(klass);
+
+ klass.install_action("member.verify", None, move |widget, _, _| {
+ if let Some(member) = widget.member_menu().member() {
+ widget.verify_member(member);
+ } else {
+ warn!("No member was selected to be verified");
+ }
+ });
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -54,13 +66,22 @@ mod imp {
impl ObjectImpl for MemberPage {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
- vec![glib::ParamSpec::new_object(
- "room",
- "Room",
- "The room backing all details of the member page",
- Room::static_type(),
- glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
- )]
+ vec![
+ glib::ParamSpec::new_object(
+ "room",
+ "Room",
+ "The room backing all details of the member page",
+ Room::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ glib::ParamSpec::new_object(
+ "member-menu",
+ "Member Menu",
+ "The object holding information needed for the menu of each MemberRow",
+ MemberMenu::static_type(),
+ glib::ParamFlags::READABLE,
+ ),
+ ]
});
PROPERTIES.as_ref()
@@ -79,9 +100,10 @@ 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() {
"room" => self.room.get().to_value(),
+ "member-menu" => obj.member_menu().to_value(),
_ => unimplemented!(),
}
}
@@ -210,4 +232,13 @@ impl MemberPage {
window.present_invite_subpage();
}));
}
+
+ pub fn member_menu(&self) -> &MemberMenu {
+ let priv_ = imp::MemberPage::from_instance(self);
+ priv_.member_menu.get_or_init(|| MemberMenu::new())
+ }
+
+ fn verify_member(&self, _member: Member) {
+ todo!("Show member verification");
+ }
}
diff --git a/src/session/content/room_details/mod.rs b/src/session/content/room_details/mod.rs
index 14d85bce..b35191bf 100644
--- a/src/session/content/room_details/mod.rs
+++ b/src/session/content/room_details/mod.rs
@@ -39,6 +39,7 @@ mod imp {
pub room_name_entry: TemplateChild<gtk::Entry>,
#[template_child]
pub room_topic_text_view: TemplateChild<gtk::TextView>,
+ pub member_page: OnceCell<MemberPage>,
}
#[glib::object_subclass]
@@ -102,7 +103,9 @@ mod imp {
fn constructed(&self, obj: &Self::Type) {
self.parent_constructed(obj);
- obj.add(&MemberPage::new(obj.room()));
+ let member_page = MemberPage::new(obj.room());
+ obj.add(&member_page);
+ self.member_page.set(member_page).unwrap();
obj.init_avatar();
obj.init_edit_toggle();
@@ -258,4 +261,9 @@ impl RoomDetails {
self.set_title(Some(&gettext("Room Details")));
self.close_subpage();
}
+
+ pub fn member_page(&self) -> &MemberPage {
+ let priv_ = imp::RoomDetails::from_instance(self);
+ priv_.member_page.get().unwrap()
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]