sidebar: Port to glib::Properties macro
This commit is contained in:
parent
7b8cc7db87
commit
e4daec38d6
|
@ -1,25 +1,39 @@
|
|||
use adw::subclass::prelude::BinImpl;
|
||||
use gettextrs::gettext;
|
||||
use gtk::{self, accessible, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
|
||||
use crate::session::model::{Category, CategoryType};
|
||||
|
||||
mod imp {
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use glib::subclass::InitializingObject;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, CompositeTemplate)]
|
||||
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
|
||||
#[template(resource = "/org/gnome/Fractal/ui/session/view/sidebar/category_row.ui")]
|
||||
#[properties(wrapper_type = super::CategoryRow)]
|
||||
pub struct CategoryRow {
|
||||
/// The category of this row.
|
||||
#[property(get, set = Self::set_category, explicit_notify, nullable)]
|
||||
pub category: RefCell<Option<Category>>,
|
||||
/// The expanded state of this row.
|
||||
#[property(get, set = Self::set_expanded, explicit_notify, construct, default = true)]
|
||||
pub expanded: Cell<bool>,
|
||||
/// The label to show for this row.
|
||||
#[property(get = Self::label)]
|
||||
pub label: PhantomData<Option<String>>,
|
||||
/// The `CategoryType` to show a label for during a drag-and-drop
|
||||
/// operation.
|
||||
///
|
||||
/// This will change the label according to the action that can be
|
||||
/// performed when changing from the `CategoryType` to this
|
||||
/// row's `Category`.
|
||||
#[property(get, set = Self::set_show_label_for_category, explicit_notify, builder(CategoryType::default()))]
|
||||
pub show_label_for_category: Cell<CategoryType>,
|
||||
/// The label showing the category name.
|
||||
#[template_child]
|
||||
|
@ -42,58 +56,112 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for CategoryRow {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
use once_cell::sync::Lazy;
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecObject::builder::<Category>("category")
|
||||
.explicit_notify()
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("expanded")
|
||||
.default_value(true)
|
||||
.explicit_notify()
|
||||
.construct()
|
||||
.build(),
|
||||
glib::ParamSpecString::builder("label").read_only().build(),
|
||||
glib::ParamSpecEnum::builder::<CategoryType>("show-label-for-category")
|
||||
.explicit_notify()
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"category" => obj.set_category(value.get().unwrap()),
|
||||
"expanded" => obj.set_expanded(value.get().unwrap()),
|
||||
"show-label-for-category" => obj.set_show_label_for_category(value.get().unwrap()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"category" => obj.category().to_value(),
|
||||
"expanded" => obj.expanded().to_value(),
|
||||
"label" => obj.label().to_value(),
|
||||
"show-label-for-category" => obj.show_label_for_category().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for CategoryRow {}
|
||||
|
||||
impl WidgetImpl for CategoryRow {}
|
||||
impl BinImpl for CategoryRow {}
|
||||
|
||||
impl CategoryRow {
|
||||
/// Set the category represented by this row.
|
||||
fn set_category(&self, category: Option<Category>) {
|
||||
if self.category.borrow().clone() == category {
|
||||
return;
|
||||
}
|
||||
|
||||
self.category.replace(category);
|
||||
|
||||
let obj = self.obj();
|
||||
obj.notify_category();
|
||||
obj.notify_label();
|
||||
}
|
||||
|
||||
/// The label to show for this row.
|
||||
fn label(&self) -> Option<String> {
|
||||
let to_type = self.category.borrow().as_ref()?.r#type();
|
||||
let from_type = self.show_label_for_category.get();
|
||||
|
||||
let label = match from_type {
|
||||
CategoryType::Invited => match to_type {
|
||||
// Translators: This is an action to join a room and put it in the "Favorites"
|
||||
// section.
|
||||
CategoryType::Favorite => gettext("Join Room as Favorite"),
|
||||
CategoryType::Normal => gettext("Join Room"),
|
||||
// Translators: This is an action to join a room and put it in the "Low
|
||||
// Priority" section.
|
||||
CategoryType::LowPriority => gettext("Join Room as Low Priority"),
|
||||
CategoryType::Left => gettext("Reject Invite"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::Favorite => match to_type {
|
||||
CategoryType::Normal => gettext("Move to Rooms"),
|
||||
CategoryType::LowPriority => gettext("Move to Low Priority"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::Normal => match to_type {
|
||||
CategoryType::Favorite => gettext("Move to Favorites"),
|
||||
CategoryType::LowPriority => gettext("Move to Low Priority"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::LowPriority => match to_type {
|
||||
CategoryType::Favorite => gettext("Move to Favorites"),
|
||||
CategoryType::Normal => gettext("Move to Rooms"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::Left => match to_type {
|
||||
// Translators: This is an action to rejoin a room and put it in the "Favorites"
|
||||
// section.
|
||||
CategoryType::Favorite => gettext("Rejoin Room as Favorite"),
|
||||
CategoryType::Normal => gettext("Rejoin Room"),
|
||||
// Translators: This is an action to rejoin a room and put it in the "Low
|
||||
// Priority" section.
|
||||
CategoryType::LowPriority => gettext("Rejoin Room as Low Priority"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
_ => to_type.to_string(),
|
||||
};
|
||||
|
||||
Some(label)
|
||||
}
|
||||
|
||||
/// Set the expanded state of this row.
|
||||
fn set_expanded(&self, expanded: bool) {
|
||||
if self.expanded.get() == expanded {
|
||||
return;
|
||||
}
|
||||
let obj = self.obj();
|
||||
|
||||
if expanded {
|
||||
obj.set_state_flags(gtk::StateFlags::CHECKED, false);
|
||||
} else {
|
||||
obj.unset_state_flags(gtk::StateFlags::CHECKED);
|
||||
}
|
||||
|
||||
self.expanded.set(expanded);
|
||||
obj.set_expanded_accessibility_state(expanded);
|
||||
obj.notify_expanded();
|
||||
}
|
||||
|
||||
/// Set the `CategoryType` to show a label for.
|
||||
fn set_show_label_for_category(&self, category: CategoryType) {
|
||||
if category == self.show_label_for_category.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.show_label_for_category.set(category);
|
||||
|
||||
let obj = self.obj();
|
||||
obj.notify_show_label_for_category();
|
||||
obj.notify_label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
/// A sidebar row representing a category.
|
||||
pub struct CategoryRow(ObjectSubclass<imp::CategoryRow>)
|
||||
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
|
||||
}
|
||||
|
@ -103,123 +171,15 @@ impl CategoryRow {
|
|||
glib::Object::new()
|
||||
}
|
||||
|
||||
/// The category represented by this row.
|
||||
pub fn category(&self) -> Option<Category> {
|
||||
self.imp().category.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the category represented by this row.
|
||||
pub fn set_category(&self, category: Option<Category>) {
|
||||
if self.category() == category {
|
||||
return;
|
||||
}
|
||||
|
||||
self.imp().category.replace(category);
|
||||
self.notify("category");
|
||||
self.notify("label");
|
||||
}
|
||||
|
||||
/// The expanded state of this row.
|
||||
fn expanded(&self) -> bool {
|
||||
self.imp().expanded.get()
|
||||
}
|
||||
|
||||
/// Set the expanded state of this row.
|
||||
fn set_expanded(&self, expanded: bool) {
|
||||
if self.expanded() == expanded {
|
||||
return;
|
||||
}
|
||||
|
||||
if expanded {
|
||||
self.set_state_flags(gtk::StateFlags::CHECKED, false);
|
||||
} else {
|
||||
self.unset_state_flags(gtk::StateFlags::CHECKED);
|
||||
}
|
||||
|
||||
self.set_expanded_accessibility_state(expanded);
|
||||
self.imp().expanded.set(expanded);
|
||||
self.notify("expanded");
|
||||
}
|
||||
|
||||
/// Set the expanded state of this row for a11y.
|
||||
fn set_expanded_accessibility_state(&self, expanded: bool) {
|
||||
if let Some(row) = self.parent() {
|
||||
row.update_state(&[accessible::State::Expanded(Some(expanded))]);
|
||||
row.update_state(&[gtk::accessible::State::Expanded(Some(expanded))]);
|
||||
}
|
||||
}
|
||||
|
||||
/// The label to show for this row.
|
||||
pub fn label(&self) -> Option<String> {
|
||||
let to_type = self.category()?.r#type();
|
||||
let from_type = self.show_label_for_category();
|
||||
|
||||
let label = match from_type {
|
||||
CategoryType::Invited => match to_type {
|
||||
// Translators: This is an action to join a room and put it in the "Favorites"
|
||||
// section.
|
||||
CategoryType::Favorite => gettext("Join Room as Favorite"),
|
||||
CategoryType::Normal => gettext("Join Room"),
|
||||
// Translators: This is an action to join a room and put it in the "Low Priority"
|
||||
// section.
|
||||
CategoryType::LowPriority => gettext("Join Room as Low Priority"),
|
||||
CategoryType::Left => gettext("Reject Invite"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::Favorite => match to_type {
|
||||
CategoryType::Normal => gettext("Move to Rooms"),
|
||||
CategoryType::LowPriority => gettext("Move to Low Priority"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::Normal => match to_type {
|
||||
CategoryType::Favorite => gettext("Move to Favorites"),
|
||||
CategoryType::LowPriority => gettext("Move to Low Priority"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::LowPriority => match to_type {
|
||||
CategoryType::Favorite => gettext("Move to Favorites"),
|
||||
CategoryType::Normal => gettext("Move to Rooms"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::Left => match to_type {
|
||||
// Translators: This is an action to rejoin a room and put it in the "Favorites"
|
||||
// section.
|
||||
CategoryType::Favorite => gettext("Rejoin Room as Favorite"),
|
||||
CategoryType::Normal => gettext("Rejoin Room"),
|
||||
// Translators: This is an action to rejoin a room and put it in the "Low Priority"
|
||||
// section.
|
||||
CategoryType::LowPriority => gettext("Rejoin Room as Low Priority"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
_ => to_type.to_string(),
|
||||
};
|
||||
|
||||
Some(label)
|
||||
}
|
||||
|
||||
/// The `CategoryType` to show a label for.
|
||||
///
|
||||
/// This will change the label according to the action that can be performed
|
||||
/// when changing from the `CategoryType` to this row's `Category`.
|
||||
pub fn show_label_for_category(&self) -> CategoryType {
|
||||
self.imp().show_label_for_category.get()
|
||||
}
|
||||
|
||||
/// Set the `CategoryType` to show a label for.
|
||||
pub fn set_show_label_for_category(&self, category: CategoryType) {
|
||||
if category == self.show_label_for_category() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.imp().show_label_for_category.set(category);
|
||||
|
||||
self.notify("show-label-for-category");
|
||||
self.notify("label");
|
||||
}
|
||||
|
||||
/// Returns the display name widget.
|
||||
pub fn display_name(&self) -> gtk::Label {
|
||||
self.imp().display_name.clone()
|
||||
/// The descendant that labels this row for a11y.
|
||||
pub fn labelled_by(&self) -> >k::Accessible {
|
||||
self.imp().display_name.upcast_ref()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,12 @@ mod imp {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, CompositeTemplate)]
|
||||
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
|
||||
#[template(resource = "/org/gnome/Fractal/ui/session/view/sidebar/icon_item_row.ui")]
|
||||
#[properties(wrapper_type = super::IconItemRow)]
|
||||
pub struct IconItemRow {
|
||||
/// The [`IconItem`] of this row.
|
||||
#[property(get, set = Self::set_icon_item, explicit_notify, nullable)]
|
||||
pub icon_item: RefCell<Option<IconItem>>,
|
||||
}
|
||||
|
||||
|
@ -32,35 +35,33 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for IconItemRow {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
use once_cell::sync::Lazy;
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![glib::ParamSpecObject::builder::<IconItem>("icon-item")
|
||||
.explicit_notify()
|
||||
.build()]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"icon-item" => self.obj().set_icon_item(value.get().unwrap()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"icon-item" => self.obj().icon_item().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for IconItemRow {}
|
||||
|
||||
impl WidgetImpl for IconItemRow {}
|
||||
impl BinImpl for IconItemRow {}
|
||||
|
||||
impl IconItemRow {
|
||||
/// Set the [`IconItem`] of this row.
|
||||
fn set_icon_item(&self, icon_item: Option<IconItem>) {
|
||||
if self.icon_item.borrow().clone() == icon_item {
|
||||
return;
|
||||
}
|
||||
let obj = self.obj();
|
||||
|
||||
if icon_item
|
||||
.as_ref()
|
||||
.is_some_and(|i| i.r#type() == ItemType::Forget)
|
||||
{
|
||||
obj.add_css_class("forget");
|
||||
} else {
|
||||
obj.remove_css_class("forget");
|
||||
}
|
||||
|
||||
self.icon_item.replace(icon_item);
|
||||
obj.notify_icon_item();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
|
@ -72,28 +73,4 @@ impl IconItemRow {
|
|||
pub fn new() -> Self {
|
||||
glib::Object::new()
|
||||
}
|
||||
|
||||
/// The [`IconItem`] of this row.
|
||||
pub fn icon_item(&self) -> Option<IconItem> {
|
||||
self.imp().icon_item.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the [`IconItem`] of this row.
|
||||
pub fn set_icon_item(&self, icon_item: Option<IconItem>) {
|
||||
if self.icon_item() == icon_item {
|
||||
return;
|
||||
}
|
||||
|
||||
if icon_item
|
||||
.as_ref()
|
||||
.is_some_and(|i| i.r#type() == ItemType::Forget)
|
||||
{
|
||||
self.add_css_class("forget");
|
||||
} else {
|
||||
self.remove_css_class("forget");
|
||||
}
|
||||
|
||||
self.imp().icon_item.replace(icon_item);
|
||||
self.notify("icon-item");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,15 +21,18 @@ use crate::{
|
|||
};
|
||||
|
||||
mod imp {
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::{
|
||||
cell::{Cell, OnceCell, RefCell},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use glib::{signal::SignalHandlerId, subclass::InitializingObject};
|
||||
use once_cell::{sync::Lazy, unsync::OnceCell};
|
||||
use glib::subclass::InitializingObject;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, CompositeTemplate)]
|
||||
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
|
||||
#[template(resource = "/org/gnome/Fractal/ui/session/view/sidebar/mod.ui")]
|
||||
#[properties(wrapper_type = super::Sidebar)]
|
||||
pub struct Sidebar {
|
||||
#[template_child]
|
||||
pub scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
|
@ -44,15 +47,24 @@ mod imp {
|
|||
#[template_child]
|
||||
pub offline_banner: TemplateChild<adw::Banner>,
|
||||
pub room_row_popover: OnceCell<gtk::PopoverMenu>,
|
||||
/// The logged-in user.
|
||||
#[property(get, set = Self::set_user, explicit_notify, nullable)]
|
||||
pub user: RefCell<Option<User>>,
|
||||
/// The type of the source that activated drop mode.
|
||||
pub drop_source_type: Cell<Option<RoomType>>,
|
||||
/// The `CategoryType` of the source that activated drop mode.
|
||||
#[property(get = Self::drop_source_category_type, builder(CategoryType::default()))]
|
||||
pub drop_source_category_type: PhantomData<CategoryType>,
|
||||
/// The type of the drop target that is currently hovered.
|
||||
pub drop_active_target_type: Cell<Option<RoomType>>,
|
||||
/// The `CategoryType` of the drop target that is currently hovered.
|
||||
#[property(get = Self::drop_active_target_category_type, builder(CategoryType::default()))]
|
||||
pub drop_active_target_category_type: PhantomData<CategoryType>,
|
||||
/// The list model of this sidebar.
|
||||
#[property(get, set = Self::set_list_model, explicit_notify, nullable)]
|
||||
pub list_model: glib::WeakRef<SidebarListModel>,
|
||||
pub bindings: RefCell<Vec<glib::Binding>>,
|
||||
pub offline_handler_id: RefCell<Option<SignalHandlerId>>,
|
||||
pub offline_handler_id: RefCell<Option<glib::SignalHandlerId>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -73,58 +85,8 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for Sidebar {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecObject::builder::<User>("user")
|
||||
.explicit_notify()
|
||||
.build(),
|
||||
glib::ParamSpecObject::builder::<SidebarListModel>("list-model")
|
||||
.explicit_notify()
|
||||
.build(),
|
||||
glib::ParamSpecEnum::builder::<CategoryType>("drop-source-type")
|
||||
.read_only()
|
||||
.build(),
|
||||
glib::ParamSpecEnum::builder::<CategoryType>("drop-active-target-type")
|
||||
.read_only()
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"user" => obj.set_user(value.get().unwrap()),
|
||||
"list-model" => obj.set_list_model(value.get().unwrap()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"user" => obj.user().to_value(),
|
||||
"list-model" => obj.list_model().to_value(),
|
||||
"drop-source-type" => obj
|
||||
.drop_source_type()
|
||||
.map(CategoryType::from)
|
||||
.unwrap_or_default()
|
||||
.to_value(),
|
||||
"drop-active-target-type" => obj
|
||||
.drop_active_target_type()
|
||||
.map(CategoryType::from)
|
||||
.unwrap_or_default()
|
||||
.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
let obj = self.obj();
|
||||
|
@ -195,6 +157,89 @@ mod imp {
|
|||
}
|
||||
|
||||
impl NavigationPageImpl for Sidebar {}
|
||||
|
||||
impl Sidebar {
|
||||
/// Set the logged-in user.
|
||||
fn set_user(&self, user: Option<User>) {
|
||||
let prev_user = self.user.borrow().clone();
|
||||
if prev_user == user {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(prev_user) = prev_user {
|
||||
if let Some(handler_id) = self.offline_handler_id.take() {
|
||||
prev_user.session().disconnect(handler_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(user) = &user {
|
||||
let session = user.session();
|
||||
let handler_id =
|
||||
session.connect_offline_notify(clone!(@weak self as imp => move |session| {
|
||||
imp.offline_banner.set_revealed(session.offline());
|
||||
}));
|
||||
self.offline_banner.set_revealed(session.offline());
|
||||
|
||||
self.offline_handler_id.replace(Some(handler_id));
|
||||
}
|
||||
|
||||
self.user.replace(user);
|
||||
self.obj().notify_user();
|
||||
}
|
||||
|
||||
/// Set the list model of the sidebar.
|
||||
fn set_list_model(&self, list_model: Option<SidebarListModel>) {
|
||||
if self.list_model.upgrade() == list_model {
|
||||
return;
|
||||
}
|
||||
let obj = self.obj();
|
||||
|
||||
for binding in self.bindings.take() {
|
||||
binding.unbind();
|
||||
}
|
||||
|
||||
if let Some(list_model) = &list_model {
|
||||
let bindings = vec![
|
||||
obj.bind_property(
|
||||
"drop-source-category-type",
|
||||
&list_model.item_list(),
|
||||
"show-all-for-category",
|
||||
)
|
||||
.sync_create()
|
||||
.build(),
|
||||
list_model
|
||||
.string_filter()
|
||||
.bind_property("search", &*self.room_search_entry, "text")
|
||||
.sync_create()
|
||||
.bidirectional()
|
||||
.build(),
|
||||
];
|
||||
|
||||
self.bindings.replace(bindings);
|
||||
}
|
||||
|
||||
self.listview
|
||||
.set_model(list_model.as_ref().map(|m| m.selection_model()).as_ref());
|
||||
self.list_model.set(list_model.as_ref());
|
||||
obj.notify_list_model();
|
||||
}
|
||||
|
||||
/// The `CategoryType` of the source that activated drop mode.
|
||||
fn drop_source_category_type(&self) -> CategoryType {
|
||||
self.drop_source_type
|
||||
.get()
|
||||
.map(Into::into)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The `CategoryType` of the drop target that is currently hovered.
|
||||
fn drop_active_target_category_type(&self) -> CategoryType {
|
||||
self.drop_active_target_type
|
||||
.get()
|
||||
.map(Into::into)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
|
@ -211,84 +256,6 @@ impl Sidebar {
|
|||
self.imp().room_search.clone()
|
||||
}
|
||||
|
||||
/// The list model of this sidebar.
|
||||
pub fn list_model(&self) -> Option<SidebarListModel> {
|
||||
self.imp().list_model.upgrade()
|
||||
}
|
||||
|
||||
/// Set the list model of the sidebar.
|
||||
pub fn set_list_model(&self, list_model: Option<SidebarListModel>) {
|
||||
if self.list_model() == list_model {
|
||||
return;
|
||||
}
|
||||
|
||||
let imp = self.imp();
|
||||
|
||||
for binding in imp.bindings.take() {
|
||||
binding.unbind();
|
||||
}
|
||||
|
||||
if let Some(list_model) = &list_model {
|
||||
let bindings = vec![
|
||||
self.bind_property(
|
||||
"drop-source-type",
|
||||
&list_model.item_list(),
|
||||
"show-all-for-category",
|
||||
)
|
||||
.sync_create()
|
||||
.build(),
|
||||
list_model
|
||||
.string_filter()
|
||||
.bind_property("search", &*imp.room_search_entry, "text")
|
||||
.sync_create()
|
||||
.bidirectional()
|
||||
.build(),
|
||||
];
|
||||
|
||||
imp.bindings.replace(bindings);
|
||||
}
|
||||
|
||||
imp.listview
|
||||
.set_model(list_model.as_ref().map(|m| m.selection_model()).as_ref());
|
||||
imp.list_model.set(list_model.as_ref());
|
||||
self.notify("list-model");
|
||||
}
|
||||
|
||||
/// The logged-in user.
|
||||
pub fn user(&self) -> Option<User> {
|
||||
self.imp().user.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the logged-in user.
|
||||
fn set_user(&self, user: Option<User>) {
|
||||
let prev_user = self.user();
|
||||
if prev_user == user {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(prev_user) = prev_user {
|
||||
if let Some(handler_id) = self.imp().offline_handler_id.take() {
|
||||
prev_user.session().disconnect(handler_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(user) = user.as_ref() {
|
||||
let session = user.session();
|
||||
let handler_id = session.connect_notify_local(
|
||||
Some("offline"),
|
||||
clone!(@weak self as obj => move |session, _| {
|
||||
obj.imp().offline_banner.set_revealed(session.offline());
|
||||
}),
|
||||
);
|
||||
self.imp().offline_banner.set_revealed(session.offline());
|
||||
|
||||
self.imp().offline_handler_id.replace(Some(handler_id));
|
||||
}
|
||||
|
||||
self.imp().user.replace(user);
|
||||
self.notify("user");
|
||||
}
|
||||
|
||||
/// The type of the source that activated drop mode.
|
||||
pub fn drop_source_type(&self) -> Option<RoomType> {
|
||||
self.imp().drop_source_type.get()
|
||||
|
@ -310,7 +277,7 @@ impl Sidebar {
|
|||
imp.listview.remove_css_class("drop-mode");
|
||||
}
|
||||
|
||||
self.notify("drop-source-type");
|
||||
self.notify_drop_source_category_type();
|
||||
}
|
||||
|
||||
/// The type of the drop target that is currently hovered.
|
||||
|
@ -325,7 +292,7 @@ impl Sidebar {
|
|||
}
|
||||
|
||||
self.imp().drop_active_target_type.set(target_type);
|
||||
self.notify("drop-active-target-type");
|
||||
self.notify_drop_active_target_category_type();
|
||||
}
|
||||
|
||||
pub fn room_row_popover(&self) -> >k::PopoverMenu {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::{accessible::Property, gdk, glib, glib::clone, CompositeTemplate};
|
||||
use gtk::{gdk, glib, glib::clone, CompositeTemplate};
|
||||
|
||||
use super::Row;
|
||||
use crate::{
|
||||
|
@ -15,13 +15,15 @@ mod imp {
|
|||
use std::cell::RefCell;
|
||||
|
||||
use glib::subclass::InitializingObject;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, CompositeTemplate)]
|
||||
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
|
||||
#[template(resource = "/org/gnome/Fractal/ui/session/view/sidebar/room_row.ui")]
|
||||
#[properties(wrapper_type = super::RoomRow)]
|
||||
pub struct RoomRow {
|
||||
/// The room represented by this row.
|
||||
#[property(get, set = Self::set_room, explicit_notify, nullable)]
|
||||
pub room: BoundObject<Room>,
|
||||
pub binding: RefCell<Option<glib::Binding>>,
|
||||
#[template_child]
|
||||
|
@ -94,31 +96,8 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for RoomRow {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![glib::ParamSpecObject::builder::<Room>("room")
|
||||
.explicit_notify()
|
||||
.build()]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"room" => self.obj().set_room(value.get().unwrap()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"room" => self.obj().room().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
let obj = self.obj();
|
||||
|
@ -158,16 +137,70 @@ mod imp {
|
|||
if let Some(sidebar) = obj
|
||||
.parent()
|
||||
.and_downcast_ref::<Row>()
|
||||
.map(|row| row.sidebar())
|
||||
.and_then(|row| row.sidebar())
|
||||
{
|
||||
let popover = sidebar.room_row_popover();
|
||||
obj.set_popover(Some(popover.to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RoomRow {
|
||||
/// Set the room represented by this row.
|
||||
pub fn set_room(&self, room: Option<Room>) {
|
||||
if self.room.obj() == room {
|
||||
return;
|
||||
}
|
||||
let obj = self.obj();
|
||||
|
||||
self.room.disconnect_signals();
|
||||
if let Some(binding) = self.binding.take() {
|
||||
binding.unbind();
|
||||
}
|
||||
self.display_name.remove_css_class("dim-label");
|
||||
|
||||
if let Some(room) = room {
|
||||
self.binding.replace(Some(
|
||||
room.bind_property(
|
||||
"notification-count",
|
||||
&self.notification_count.get(),
|
||||
"visible",
|
||||
)
|
||||
.sync_create()
|
||||
.transform_from(|_, count: u64| Some(count > 0))
|
||||
.build(),
|
||||
));
|
||||
|
||||
let highlight_handler =
|
||||
room.connect_highlight_notify(clone!(@weak obj => move |_| {
|
||||
obj.update_highlight();
|
||||
}));
|
||||
let direct_handler = room.connect_is_direct_notify(clone!(@weak obj => move |_| {
|
||||
obj.update_direct_icon();
|
||||
}));
|
||||
let name_handler = room.connect_display_name_notify(clone!(@weak obj => move |_| {
|
||||
obj.update_accessibility_label();
|
||||
}));
|
||||
if room.category() == RoomType::Left {
|
||||
self.display_name.add_css_class("dim-label");
|
||||
}
|
||||
|
||||
self.room
|
||||
.set(room, vec![highlight_handler, direct_handler, name_handler]);
|
||||
|
||||
obj.update_accessibility_label();
|
||||
}
|
||||
|
||||
obj.update_highlight();
|
||||
obj.update_direct_icon();
|
||||
obj.update_actions();
|
||||
obj.notify_room();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
/// A sidebar row representing a room.
|
||||
pub struct RoomRow(ObjectSubclass<imp::RoomRow>)
|
||||
@extends gtk::Widget, adw::Bin, ContextMenuBin, @implements gtk::Accessible;
|
||||
}
|
||||
|
@ -177,69 +210,6 @@ impl RoomRow {
|
|||
glib::Object::new()
|
||||
}
|
||||
|
||||
/// The room represented by this row.
|
||||
pub fn room(&self) -> Option<Room> {
|
||||
self.imp().room.obj()
|
||||
}
|
||||
|
||||
/// Set the room represented by this row.
|
||||
pub fn set_room(&self, room: Option<Room>) {
|
||||
let imp = self.imp();
|
||||
|
||||
if self.room() == room {
|
||||
return;
|
||||
}
|
||||
|
||||
imp.room.disconnect_signals();
|
||||
if let Some(binding) = imp.binding.take() {
|
||||
binding.unbind();
|
||||
}
|
||||
imp.display_name.remove_css_class("dim-label");
|
||||
|
||||
if let Some(room) = room {
|
||||
imp.binding.replace(Some(
|
||||
room.bind_property(
|
||||
"notification-count",
|
||||
&imp.notification_count.get(),
|
||||
"visible",
|
||||
)
|
||||
.sync_create()
|
||||
.transform_from(|_, count: u64| Some(count > 0))
|
||||
.build(),
|
||||
));
|
||||
|
||||
let highlight_handler = room.connect_notify_local(
|
||||
Some("highlight"),
|
||||
clone!(@weak self as obj => move |_, _| {
|
||||
obj.update_highlight();
|
||||
}),
|
||||
);
|
||||
let direct_handler = room.connect_notify_local(
|
||||
Some("is-direct"),
|
||||
clone!(@weak self as obj => move |_, _| {
|
||||
obj.update_direct_icon();
|
||||
}),
|
||||
);
|
||||
let name_handler =
|
||||
room.connect_display_name_notify(clone!(@weak self as obj => move |_| {
|
||||
obj.update_accessibility_label();
|
||||
}));
|
||||
if room.category() == RoomType::Left {
|
||||
imp.display_name.add_css_class("dim-label");
|
||||
}
|
||||
|
||||
imp.room
|
||||
.set(room, vec![highlight_handler, direct_handler, name_handler]);
|
||||
|
||||
self.update_accessibility_label();
|
||||
}
|
||||
|
||||
self.update_highlight();
|
||||
self.update_direct_icon();
|
||||
self.update_actions();
|
||||
self.notify("room");
|
||||
}
|
||||
|
||||
fn update_highlight(&self) {
|
||||
let imp = self.imp();
|
||||
if let Some(room) = self.room() {
|
||||
|
@ -336,24 +306,41 @@ impl RoomRow {
|
|||
}
|
||||
|
||||
fn drag_prepare(&self, drag: >k::DragSource, x: f64, y: f64) -> Option<gdk::ContentProvider> {
|
||||
let paintable = gtk::WidgetPaintable::new(Some(&self.parent().unwrap()));
|
||||
// FIXME: The hotspot coordinates don't work.
|
||||
// See https://gitlab.gnome.org/GNOME/gtk/-/issues/2341
|
||||
drag.set_icon(Some(&paintable), x as i32, y as i32);
|
||||
self.room()
|
||||
.map(|room| gdk::ContentProvider::for_value(&room.to_value()))
|
||||
let room = self.room()?;
|
||||
|
||||
if let Some(parent) = self.parent() {
|
||||
let paintable = gtk::WidgetPaintable::new(Some(&parent));
|
||||
// FIXME: The hotspot coordinates don't work.
|
||||
// See https://gitlab.gnome.org/GNOME/gtk/-/issues/2341
|
||||
drag.set_icon(Some(&paintable), x as i32, y as i32);
|
||||
}
|
||||
|
||||
Some(gdk::ContentProvider::for_value(&room.to_value()))
|
||||
}
|
||||
|
||||
fn drag_begin(&self) {
|
||||
let row = self.parent().and_downcast::<Row>().unwrap();
|
||||
let Some(room) = self.room() else {
|
||||
return;
|
||||
};
|
||||
let Some(row) = self.parent().and_downcast::<Row>() else {
|
||||
return;
|
||||
};
|
||||
let Some(sidebar) = row.sidebar() else {
|
||||
return;
|
||||
};
|
||||
row.add_css_class("drag");
|
||||
row.sidebar()
|
||||
.set_drop_source_type(Some(self.room().unwrap().category()));
|
||||
|
||||
sidebar.set_drop_source_type(Some(room.category()));
|
||||
}
|
||||
|
||||
fn drag_end(&self) {
|
||||
let row = self.parent().and_downcast::<Row>().unwrap();
|
||||
row.sidebar().set_drop_source_type(None);
|
||||
let Some(row) = self.parent().and_downcast::<Row>() else {
|
||||
return;
|
||||
};
|
||||
let Some(sidebar) = row.sidebar() else {
|
||||
return;
|
||||
};
|
||||
sidebar.set_drop_source_type(None);
|
||||
row.remove_css_class("drag");
|
||||
}
|
||||
|
||||
|
@ -422,15 +409,17 @@ impl RoomRow {
|
|||
}
|
||||
|
||||
fn update_accessibility_label(&self) {
|
||||
self.parent()
|
||||
.unwrap()
|
||||
.update_property(&[Property::Label(&self.accessible_label())]);
|
||||
let Some(parent) = self.parent() else {
|
||||
return;
|
||||
};
|
||||
parent.update_property(&[gtk::accessible::Property::Label(&self.accessible_label())]);
|
||||
}
|
||||
|
||||
fn accessible_label(&self) -> String {
|
||||
let Some(room) = self.room() else {
|
||||
return String::new();
|
||||
};
|
||||
|
||||
if room.is_direct() {
|
||||
gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
|
|
|
@ -13,16 +13,22 @@ use crate::{
|
|||
};
|
||||
|
||||
mod imp {
|
||||
use std::cell::RefCell;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{cell::RefCell, marker::PhantomData};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, glib::Properties)]
|
||||
#[properties(wrapper_type = super::Row)]
|
||||
pub struct Row {
|
||||
/// The ancestor sidebar of this row.
|
||||
#[property(get, set = Self::set_sidebar, construct_only)]
|
||||
pub sidebar: BoundObjectWeakRef<Sidebar>,
|
||||
/// The list row to track for expander state.
|
||||
#[property(get, set = Self::set_list_row, explicit_notify, nullable)]
|
||||
pub list_row: RefCell<Option<gtk::TreeListRow>>,
|
||||
/// The sidebar item of this row.
|
||||
#[property(get = Self::item)]
|
||||
pub item: PhantomData<Option<SidebarItem>>,
|
||||
pub bindings: RefCell<Vec<glib::Binding>>,
|
||||
}
|
||||
|
||||
|
@ -38,46 +44,8 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for Row {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecObject::builder::<glib::Object>("item")
|
||||
.read_only()
|
||||
.build(),
|
||||
glib::ParamSpecObject::builder::<gtk::TreeListRow>("list-row")
|
||||
.explicit_notify()
|
||||
.build(),
|
||||
glib::ParamSpecObject::builder::<Sidebar>("sidebar")
|
||||
.construct_only()
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"list-row" => obj.set_list_row(value.get().unwrap()),
|
||||
"sidebar" => obj.set_sidebar(value.get().ok().as_ref()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
let obj = self.obj();
|
||||
|
||||
match pspec.name() {
|
||||
"item" => obj.item().to_value(),
|
||||
"list-row" => obj.list_row().to_value(),
|
||||
"sidebar" => obj.sidebar().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
let obj = self.obj();
|
||||
|
@ -104,9 +72,115 @@ mod imp {
|
|||
|
||||
impl WidgetImpl for Row {}
|
||||
impl BinImpl for Row {}
|
||||
|
||||
impl Row {
|
||||
/// Set the ancestor sidebar of this row.
|
||||
fn set_sidebar(&self, sidebar: Sidebar) {
|
||||
let obj = self.obj();
|
||||
|
||||
let drop_source_type_handler =
|
||||
sidebar.connect_drop_source_category_type_notify(clone!(@weak obj => move |_| {
|
||||
obj.update_for_drop_source_type();
|
||||
}));
|
||||
|
||||
let drop_active_target_type_handler = sidebar
|
||||
.connect_drop_active_target_category_type_notify(clone!(@weak obj => move |_| {
|
||||
obj.update_for_drop_active_target_type();
|
||||
}));
|
||||
|
||||
self.sidebar.set(
|
||||
&sidebar,
|
||||
vec![drop_source_type_handler, drop_active_target_type_handler],
|
||||
);
|
||||
}
|
||||
|
||||
/// Set the list row to track for expander state.
|
||||
fn set_list_row(&self, list_row: Option<gtk::TreeListRow>) {
|
||||
if self.list_row.borrow().clone() == list_row {
|
||||
return;
|
||||
}
|
||||
let obj = self.obj();
|
||||
|
||||
for binding in self.bindings.take() {
|
||||
binding.unbind();
|
||||
}
|
||||
|
||||
self.list_row.replace(list_row.clone());
|
||||
|
||||
let mut bindings = vec![];
|
||||
if let Some((row, item)) = list_row.zip(self.item()) {
|
||||
if let Some(category) = item.downcast_ref::<Category>() {
|
||||
let child = if let Some(child) = obj.child().and_downcast::<CategoryRow>() {
|
||||
child
|
||||
} else {
|
||||
let child = CategoryRow::new();
|
||||
obj.set_child(Some(&child));
|
||||
obj.update_relation(&[Relation::LabelledBy(&[child.labelled_by()])]);
|
||||
child
|
||||
};
|
||||
child.set_category(Some(category.clone()));
|
||||
|
||||
bindings.push(
|
||||
row.bind_property("expanded", &child, "expanded")
|
||||
.sync_create()
|
||||
.build(),
|
||||
);
|
||||
} else if let Some(room) = item.downcast_ref::<Room>() {
|
||||
let child = if let Some(child) = obj.child().and_downcast::<RoomRow>() {
|
||||
child
|
||||
} else {
|
||||
let child = RoomRow::new();
|
||||
obj.set_child(Some(&child));
|
||||
child
|
||||
};
|
||||
|
||||
child.set_room(Some(room.clone()));
|
||||
} else if let Some(icon_item) = item.downcast_ref::<IconItem>() {
|
||||
let child = if let Some(child) = obj.child().and_downcast::<IconItemRow>() {
|
||||
child
|
||||
} else {
|
||||
let child = IconItemRow::new();
|
||||
obj.set_child(Some(&child));
|
||||
child
|
||||
};
|
||||
|
||||
child.set_icon_item(Some(icon_item.clone()));
|
||||
} else if let Some(verification) = item.downcast_ref::<IdentityVerification>() {
|
||||
let child = if let Some(child) = obj.child().and_downcast::<VerificationRow>() {
|
||||
child
|
||||
} else {
|
||||
let child = VerificationRow::new();
|
||||
obj.set_child(Some(&child));
|
||||
child
|
||||
};
|
||||
|
||||
child.set_identity_verification(Some(verification.clone()));
|
||||
} else {
|
||||
panic!("Wrong row item: {item:?}");
|
||||
}
|
||||
|
||||
obj.update_for_drop_source_type();
|
||||
}
|
||||
|
||||
self.bindings.replace(bindings);
|
||||
|
||||
obj.notify_item();
|
||||
obj.notify_list_row();
|
||||
}
|
||||
|
||||
/// The sidebar item of this row.
|
||||
fn item(&self) -> Option<SidebarItem> {
|
||||
self.list_row
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|r| r.item())
|
||||
.and_downcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
/// A row of the sidebar.
|
||||
pub struct Row(ObjectSubclass<imp::Row>)
|
||||
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
|
||||
}
|
||||
|
@ -119,129 +193,6 @@ impl Row {
|
|||
.build()
|
||||
}
|
||||
|
||||
/// The ancestor sidebar of this row.
|
||||
pub fn sidebar(&self) -> Sidebar {
|
||||
self.imp().sidebar.obj().unwrap()
|
||||
}
|
||||
|
||||
/// Set the ancestor sidebar of this row.
|
||||
fn set_sidebar(&self, sidebar: Option<&Sidebar>) {
|
||||
let Some(sidebar) = sidebar else {
|
||||
return;
|
||||
};
|
||||
|
||||
let drop_source_type_handler = sidebar.connect_notify_local(
|
||||
Some("drop-source-type"),
|
||||
clone!(@weak self as obj => move |_, _| {
|
||||
obj.update_for_drop_source_type();
|
||||
}),
|
||||
);
|
||||
|
||||
let drop_active_target_type_handler = sidebar.connect_notify_local(
|
||||
Some("drop-active-target-type"),
|
||||
clone!(@weak self as obj => move |_, _| {
|
||||
obj.update_for_drop_active_target_type();
|
||||
}),
|
||||
);
|
||||
|
||||
self.imp().sidebar.set(
|
||||
sidebar,
|
||||
vec![drop_source_type_handler, drop_active_target_type_handler],
|
||||
);
|
||||
}
|
||||
|
||||
/// The sidebar item of this row.
|
||||
pub fn item(&self) -> Option<SidebarItem> {
|
||||
self.list_row().and_then(|r| r.item()).and_downcast()
|
||||
}
|
||||
|
||||
/// The list row to track for expander state.
|
||||
pub fn list_row(&self) -> Option<gtk::TreeListRow> {
|
||||
self.imp().list_row.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the list row to track for expander state.
|
||||
pub fn set_list_row(&self, list_row: Option<gtk::TreeListRow>) {
|
||||
let imp = self.imp();
|
||||
|
||||
if self.list_row() == list_row {
|
||||
return;
|
||||
}
|
||||
|
||||
for binding in imp.bindings.take() {
|
||||
binding.unbind();
|
||||
}
|
||||
|
||||
let row = if let Some(row) = list_row.clone() {
|
||||
imp.list_row.replace(list_row);
|
||||
row
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut bindings = vec![];
|
||||
if let Some(item) = self.item() {
|
||||
if let Some(category) = item.downcast_ref::<Category>() {
|
||||
let child = if let Some(child) = self.child().and_downcast::<CategoryRow>() {
|
||||
child
|
||||
} else {
|
||||
let child = CategoryRow::new();
|
||||
self.set_child(Some(&child));
|
||||
self.update_relation(&[Relation::LabelledBy(&[&child
|
||||
.display_name()
|
||||
.upcast()])]);
|
||||
child
|
||||
};
|
||||
child.set_category(Some(category.clone()));
|
||||
|
||||
bindings.push(
|
||||
row.bind_property("expanded", &child, "expanded")
|
||||
.sync_create()
|
||||
.build(),
|
||||
);
|
||||
} else if let Some(room) = item.downcast_ref::<Room>() {
|
||||
let child = if let Some(child) = self.child().and_downcast::<RoomRow>() {
|
||||
child
|
||||
} else {
|
||||
let child = RoomRow::new();
|
||||
self.set_child(Some(&child));
|
||||
child
|
||||
};
|
||||
|
||||
child.set_room(Some(room.clone()));
|
||||
} else if let Some(icon_item) = item.downcast_ref::<IconItem>() {
|
||||
let child = if let Some(child) = self.child().and_downcast::<IconItemRow>() {
|
||||
child
|
||||
} else {
|
||||
let child = IconItemRow::new();
|
||||
self.set_child(Some(&child));
|
||||
child
|
||||
};
|
||||
|
||||
child.set_icon_item(Some(icon_item.clone()));
|
||||
} else if let Some(verification) = item.downcast_ref::<IdentityVerification>() {
|
||||
let child = if let Some(child) = self.child().and_downcast::<VerificationRow>() {
|
||||
child
|
||||
} else {
|
||||
let child = VerificationRow::new();
|
||||
self.set_child(Some(&child));
|
||||
child
|
||||
};
|
||||
|
||||
child.set_identity_verification(Some(verification.clone()));
|
||||
} else {
|
||||
panic!("Wrong row item: {item:?}");
|
||||
}
|
||||
|
||||
self.update_for_drop_source_type();
|
||||
}
|
||||
|
||||
imp.bindings.replace(bindings);
|
||||
|
||||
self.notify("item");
|
||||
self.notify("list-row");
|
||||
}
|
||||
|
||||
/// Get the `RoomType` of this item.
|
||||
///
|
||||
/// If this is not a `Category` or one of its children, returns `None`.
|
||||
|
@ -267,6 +218,10 @@ impl Row {
|
|||
|
||||
/// Handle the drag-n-drop hovering this row.
|
||||
fn drop_accept(&self, drop: &gdk::Drop) -> bool {
|
||||
let Some(sidebar) = self.sidebar() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let room = drop
|
||||
.drag()
|
||||
.map(|drag| drag.content())
|
||||
|
@ -275,14 +230,13 @@ impl Row {
|
|||
if let Some(room) = room {
|
||||
if let Some(target_type) = self.room_type() {
|
||||
if room.category().can_change_to(target_type) {
|
||||
self.sidebar()
|
||||
.set_drop_active_target_type(Some(target_type));
|
||||
sidebar.set_drop_active_target_type(Some(target_type));
|
||||
return true;
|
||||
}
|
||||
} else if let Some(item_type) = self.item_type() {
|
||||
if room.category() == RoomType::Left && item_type == ItemType::Forget {
|
||||
self.add_css_class("drop-active");
|
||||
self.sidebar().set_drop_active_target_type(None);
|
||||
sidebar.set_drop_active_target_type(None);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -293,7 +247,9 @@ impl Row {
|
|||
/// Handle the drag-n-drop leaving this row.
|
||||
fn drop_leave(&self) {
|
||||
self.remove_css_class("drop-active");
|
||||
self.sidebar().set_drop_active_target_type(None);
|
||||
if let Some(sidebar) = self.sidebar() {
|
||||
sidebar.set_drop_active_target_type(None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the drop on this row.
|
||||
|
@ -316,7 +272,9 @@ impl Row {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.sidebar().set_drop_source_type(None);
|
||||
if let Some(sidebar) = self.sidebar() {
|
||||
sidebar.set_drop_source_type(None);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -359,7 +317,7 @@ impl Row {
|
|||
|
||||
/// Update the disabled or empty state of this drop target.
|
||||
fn update_for_drop_source_type(&self) {
|
||||
let source_type = self.sidebar().drop_source_type();
|
||||
let source_type = self.sidebar().and_then(|s| s.drop_source_type());
|
||||
|
||||
if let Some(source_type) = source_type {
|
||||
if self
|
||||
|
@ -407,9 +365,9 @@ impl Row {
|
|||
let Some(room_type) = self.room_type() else {
|
||||
return;
|
||||
};
|
||||
let target_type = self.sidebar().drop_active_target_type();
|
||||
let target_type = self.sidebar().and_then(|s| s.drop_active_target_type());
|
||||
|
||||
if target_type.map_or(false, |target_type| target_type == room_type) {
|
||||
if target_type.is_some_and(|target_type| target_type == room_type) {
|
||||
self.add_css_class("drop-active");
|
||||
} else {
|
||||
self.remove_css_class("drop-active");
|
||||
|
|
|
@ -7,14 +7,16 @@ mod imp {
|
|||
use std::cell::RefCell;
|
||||
|
||||
use glib::subclass::InitializingObject;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, CompositeTemplate)]
|
||||
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
|
||||
#[template(resource = "/org/gnome/Fractal/ui/session/view/sidebar/verification_row.ui")]
|
||||
#[properties(wrapper_type = super::VerificationRow)]
|
||||
pub struct VerificationRow {
|
||||
pub verification: RefCell<Option<IdentityVerification>>,
|
||||
/// The identity verification represented by this row.
|
||||
#[property(get, set = Self::set_identity_verification, explicit_notify, nullable)]
|
||||
pub identity_verification: RefCell<Option<IdentityVerification>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -32,41 +34,27 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for VerificationRow {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![glib::ParamSpecObject::builder::<IdentityVerification>(
|
||||
"identity-verification",
|
||||
)
|
||||
.explicit_notify()
|
||||
.build()]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"identity-verification" => {
|
||||
self.obj().set_identity_verification(value.get().unwrap())
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"identity-verification" => self.obj().identity_verification().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for VerificationRow {}
|
||||
|
||||
impl WidgetImpl for VerificationRow {}
|
||||
impl BinImpl for VerificationRow {}
|
||||
|
||||
impl VerificationRow {
|
||||
/// Set the identity verification represented by this row.
|
||||
fn set_identity_verification(&self, verification: Option<IdentityVerification>) {
|
||||
if self.identity_verification.borrow().clone() == verification {
|
||||
return;
|
||||
}
|
||||
|
||||
self.identity_verification.replace(verification);
|
||||
self.obj().notify_identity_verification();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
/// A sidebar row representing an identity verification.
|
||||
pub struct VerificationRow(ObjectSubclass<imp::VerificationRow>)
|
||||
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
|
||||
}
|
||||
|
@ -75,19 +63,4 @@ impl VerificationRow {
|
|||
pub fn new() -> Self {
|
||||
glib::Object::new()
|
||||
}
|
||||
|
||||
/// The identity verification represented by this row.
|
||||
pub fn identity_verification(&self) -> Option<IdentityVerification> {
|
||||
self.imp().verification.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the identity verification represented by this row.
|
||||
pub fn set_identity_verification(&self, verification: Option<IdentityVerification>) {
|
||||
if self.identity_verification() == verification {
|
||||
return;
|
||||
}
|
||||
|
||||
self.imp().verification.replace(verification);
|
||||
self.notify("identity-verification");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue