parent
411f26b441
commit
ec3f7cfeae
|
@ -999,6 +999,7 @@ dependencies = [
|
|||
"matrix-sdk",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"qrcode",
|
||||
"rand 0.8.4",
|
||||
|
@ -2603,6 +2604,27 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro2 1.0.30",
|
||||
"quote 1.0.10",
|
||||
"syn 1.0.80",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
|
|
23
Cargo.toml
23
Cargo.toml
|
@ -29,13 +29,18 @@ futures = "0.3"
|
|||
rand = "0.8"
|
||||
indexmap = "1.6.2"
|
||||
qrcode = "0.12.0"
|
||||
ashpd = {git = "https://github.com/bilelmoussaoui/ashpd", rev="66d4dc0020181a7174451150ecc711344082b5ce", features=["feature_gtk4", "feature_pipewire", "log"]}
|
||||
gst = {version = "0.17", package = "gstreamer"}
|
||||
gst_base = {version = "0.17", package = "gstreamer-base"}
|
||||
gst_video = {version = "0.17", package = "gstreamer-video"}
|
||||
image = {version = "0.23", default-features = false, features=["png"]}
|
||||
ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", rev = "66d4dc0020181a7174451150ecc711344082b5ce", features = [
|
||||
"feature_gtk4",
|
||||
"feature_pipewire",
|
||||
"log",
|
||||
] }
|
||||
gst = { version = "0.17", package = "gstreamer" }
|
||||
gst_base = { version = "0.17", package = "gstreamer-base" }
|
||||
gst_video = { version = "0.17", package = "gstreamer-video" }
|
||||
image = { version = "0.23", default-features = false, features = ["png"] }
|
||||
regex = "1.5.4"
|
||||
mime_guess = "2.0.3"
|
||||
num_enum = "0.5.6"
|
||||
|
||||
[dependencies.sourceview]
|
||||
package = "sourceview5"
|
||||
|
@ -52,4 +57,10 @@ version = "0.1.0-alpha-6"
|
|||
[dependencies.matrix-sdk]
|
||||
git = "https://github.com/jsparber/matrix-rust-sdk.git"
|
||||
branch = "messages-api"
|
||||
features = ["socks", "encryption", "sled_cryptostore", "sled_state_store", "markdown"]
|
||||
features = [
|
||||
"socks",
|
||||
"encryption",
|
||||
"sled_cryptostore",
|
||||
"sled_state_store",
|
||||
"markdown",
|
||||
]
|
||||
|
|
|
@ -135,43 +135,80 @@ headerbar.flat {
|
|||
|
||||
/* Sidebar List */
|
||||
.sidebar-list row {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.sidebar-list .category {
|
||||
margin-top: 4px;
|
||||
.sidebar-list row:focus-visible:focus-within {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.sidebar-list row:selected {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.sidebar-list row > * {
|
||||
margin: 0 12px;
|
||||
padding: 12px 6px;
|
||||
border-radius: 6px;
|
||||
transition-property: outline, outline-width, outline-offset, outline-color;
|
||||
transition-duration: 300ms;
|
||||
animation-timing-function: ease-in-out;
|
||||
outline: 0 solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.sidebar-list row:focus-visible:focus-within > * {
|
||||
outline-color: alpha(@accent_color, 0.5);
|
||||
outline-width: 2px;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.sidebar-list:not(.drop-mode) row:hover > * {
|
||||
background-color: alpha(currentColor, 0.07);
|
||||
}
|
||||
|
||||
.sidebar-list row:active > * {
|
||||
background-color: alpha(currentColor, 0.16);
|
||||
}
|
||||
|
||||
.sidebar-list row:selected > * {
|
||||
background-color: alpha(currentColor, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-list:not(.drop-mode) row:selected:hover > *,
|
||||
.sidebar-list row:selected.has-open-popup > * {
|
||||
background-color: alpha(currentColor, 0.13);
|
||||
}
|
||||
|
||||
.sidebar-list row:selected:active > * {
|
||||
background-color: alpha(currentColor, 0.19);
|
||||
}
|
||||
|
||||
.sidebar-list row.entry {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar-list row.category {
|
||||
margin-top: 6px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar-list .entry {
|
||||
margin-top: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar-list .category image.arrow {
|
||||
.sidebar-list row.category image.arrow {
|
||||
transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
}
|
||||
|
||||
.sidebar-list .category .category-row:not(:checked) image.arrow:dir(ltr) {
|
||||
.sidebar-list row.category .category-row:not(:checked) image.arrow:dir(ltr) {
|
||||
transform: rotate(-0.25turn);
|
||||
}
|
||||
|
||||
.sidebar-list .category .category-row:not(:checked) image.arrow:dir(rtl) {
|
||||
.sidebar-list row.category .category-row:not(:checked) image.arrow:dir(rtl) {
|
||||
transform: rotate(0.25turn);
|
||||
}
|
||||
|
||||
.sidebar-list .room {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.sidebar-list .room .bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar-list .room .notification_count {
|
||||
.sidebar-list row.room .notification_count {
|
||||
font-weight: bold;
|
||||
font-size: 0.8em;
|
||||
border-radius: 10px;
|
||||
|
@ -179,10 +216,44 @@ headerbar.flat {
|
|||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.sidebar-list .room .highlight {
|
||||
.sidebar-list row.room .highlight {
|
||||
color: @accent_fg_color;
|
||||
background-color: @accent_bg_color;
|
||||
}
|
||||
|
||||
.sidebar-list sidebar-row.drag {
|
||||
color: @accent_fg_color;
|
||||
background-color: @accent_bg_color;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.sidebar-list sidebar-row.drop-disabled > * {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.sidebar-list sidebar-row.drop-empty {
|
||||
color: @accent_color;
|
||||
}
|
||||
|
||||
.sidebar-list sidebar-row.forget {
|
||||
color: @error_color;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.sidebar-list row.drop-active {
|
||||
background-color: alpha(@accent_color, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-list row.category.drop-active,
|
||||
.sidebar-list row.drop-active sidebar-row.forget {
|
||||
color: @accent_color;
|
||||
}
|
||||
|
||||
.sidebar-list row.drop-active .dim-label,
|
||||
.sidebar-list row.drop-active sidebar-row.drop-empty .dim-label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.room-history {
|
||||
background: @theme_base_color;
|
||||
|
|
|
@ -12,11 +12,7 @@
|
|||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<binding name="label">
|
||||
<lookup name="display-name">
|
||||
<lookup name="category">SidebarCategoryRow</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<property name="label" bind-source="SidebarCategoryRow" bind-property="label" bind-flags="sync-create"/>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
|
|
|
@ -36,7 +36,7 @@ use matrix_sdk::{
|
|||
member::MembershipState, message::RoomMessageEventContent,
|
||||
name::RoomNameEventContent, topic::RoomTopicEventContent,
|
||||
},
|
||||
tag::TagName,
|
||||
tag::{TagInfo, TagName},
|
||||
AnyRoomAccountDataEvent, AnyStateEventContent, AnyStrippedStateEvent,
|
||||
AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, SyncMessageEvent,
|
||||
Unsigned,
|
||||
|
@ -284,7 +284,10 @@ mod imp {
|
|||
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![Signal::builder("order-changed", &[], <()>::static_type().into()).build()]
|
||||
vec![
|
||||
Signal::builder("order-changed", &[], <()>::static_type().into()).build(),
|
||||
Signal::builder("room-forgotten", &[], <()>::static_type().into()).build(),
|
||||
]
|
||||
});
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
|
@ -366,6 +369,56 @@ impl Room {
|
|||
self.load_category();
|
||||
}
|
||||
|
||||
/// Forget a room that is left.
|
||||
pub fn forget(&self) {
|
||||
if self.category() != RoomType::Left {
|
||||
warn!("Cannot forget a room that is not left");
|
||||
return;
|
||||
}
|
||||
|
||||
let matrix_room = self.matrix_room();
|
||||
|
||||
let handle = spawn_tokio!(async move {
|
||||
match matrix_room {
|
||||
MatrixRoom::Left(room) => room.forget().await,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
});
|
||||
|
||||
spawn!(
|
||||
glib::PRIORITY_DEFAULT_IDLE,
|
||||
clone!(@weak self as obj => async move {
|
||||
match handle.await.unwrap() {
|
||||
Ok(_) => {
|
||||
obj.emit_by_name("room-forgotten", &[]).unwrap();
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Couldn’t forget the room: {}", error);
|
||||
let error = Error::new(
|
||||
clone!(@weak obj => @default-return None, move |_| {
|
||||
let error_message = gettext(
|
||||
"Failed to forget <widget>."
|
||||
);
|
||||
let room_pill = Pill::new();
|
||||
room_pill.set_room(Some(obj));
|
||||
let label = LabelWithWidgets::new(&error_message, vec![room_pill]);
|
||||
|
||||
Some(label.upcast())
|
||||
}),
|
||||
);
|
||||
|
||||
if let Some(window) = obj.session().parent_window() {
|
||||
window.append_error(&error);
|
||||
}
|
||||
|
||||
// Load the previous category
|
||||
obj.load_category();
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
pub fn category(&self) -> RoomType {
|
||||
let priv_ = imp::Room::from_instance(self);
|
||||
priv_.category.get()
|
||||
|
@ -386,7 +439,9 @@ impl Room {
|
|||
/// Set the category of this room.
|
||||
///
|
||||
/// This makes the necessary to propagate the category to the homeserver.
|
||||
/// Note: Rooms can't be moved to the invite category and they can't be moved once they are upgraded
|
||||
///
|
||||
/// Note: Rooms can't be moved to the invite category and they can't be
|
||||
/// moved once they are upgraded.
|
||||
pub fn set_category(&self, category: RoomType) {
|
||||
let matrix_room = self.matrix_room();
|
||||
let previous_category = self.category();
|
||||
|
@ -395,78 +450,93 @@ impl Room {
|
|||
return;
|
||||
}
|
||||
|
||||
if category == RoomType::Invited {
|
||||
warn!("Rooms can’t be moved to the invite Category");
|
||||
return;
|
||||
}
|
||||
|
||||
if self.category() == RoomType::Outdated {
|
||||
if previous_category == RoomType::Outdated {
|
||||
warn!("Can't set the category of an upgraded room");
|
||||
return;
|
||||
}
|
||||
|
||||
// Outdated rooms don't need to propagate anything to the server
|
||||
if category == RoomType::Outdated {
|
||||
self.set_category_internal(category);
|
||||
return;
|
||||
match category {
|
||||
RoomType::Invited => {
|
||||
warn!("Rooms can’t be moved to the invite Category");
|
||||
return;
|
||||
}
|
||||
RoomType::Outdated => {
|
||||
// Outdated rooms don't need to propagate anything to the server
|
||||
self.set_category_internal(category);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let handle = spawn_tokio!(async move {
|
||||
match matrix_room {
|
||||
MatrixRoom::Invited(room) => {
|
||||
match category {
|
||||
RoomType::Invited => Ok(()),
|
||||
RoomType::Favorite => {
|
||||
room.accept_invitation().await
|
||||
// TODO: set favorite tag
|
||||
}
|
||||
RoomType::Normal => room.accept_invitation().await,
|
||||
RoomType::LowPriority => {
|
||||
room.accept_invitation().await
|
||||
// TODO: set low priority tag
|
||||
}
|
||||
RoomType::Left => room.reject_invitation().await,
|
||||
RoomType::Outdated => unimplemented!(),
|
||||
MatrixRoom::Invited(room) => match category {
|
||||
RoomType::Invited => Ok(()),
|
||||
RoomType::Favorite => {
|
||||
room.accept_invitation().await
|
||||
// TODO: set favorite tag
|
||||
}
|
||||
}
|
||||
MatrixRoom::Joined(room) => {
|
||||
match category {
|
||||
RoomType::Invited => Ok(()),
|
||||
RoomType::Favorite => {
|
||||
// TODO: set favorite tag
|
||||
Ok(())
|
||||
}
|
||||
RoomType::Normal => {
|
||||
// TODO: remove tags
|
||||
Ok(())
|
||||
}
|
||||
RoomType::LowPriority => {
|
||||
// TODO: set low priority tag
|
||||
Ok(())
|
||||
}
|
||||
RoomType::Left => room.leave().await,
|
||||
RoomType::Outdated => unimplemented!(),
|
||||
RoomType::Normal => {
|
||||
room.accept_invitation().await
|
||||
// TODO: remove tags
|
||||
}
|
||||
}
|
||||
MatrixRoom::Left(room) => {
|
||||
match category {
|
||||
RoomType::Invited => Ok(()),
|
||||
RoomType::Favorite => {
|
||||
room.join().await
|
||||
// TODO: set favorite tag
|
||||
}
|
||||
RoomType::Normal => {
|
||||
room.join().await
|
||||
// TODO: remove tags
|
||||
}
|
||||
RoomType::LowPriority => {
|
||||
room.join().await
|
||||
// TODO: set low priority tag
|
||||
}
|
||||
RoomType::Left => Ok(()),
|
||||
RoomType::Outdated => unimplemented!(),
|
||||
RoomType::LowPriority => {
|
||||
room.accept_invitation().await
|
||||
// TODO: set low priority tag
|
||||
}
|
||||
}
|
||||
RoomType::Left => room.reject_invitation().await,
|
||||
RoomType::Outdated => unimplemented!(),
|
||||
},
|
||||
MatrixRoom::Joined(room) => match category {
|
||||
RoomType::Invited => Ok(()),
|
||||
RoomType::Favorite => {
|
||||
room.set_tag(TagName::Favorite.as_ref(), TagInfo::new())
|
||||
.await?;
|
||||
if previous_category == RoomType::LowPriority {
|
||||
room.remove_tag(TagName::LowPriority.as_ref()).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
RoomType::Normal => {
|
||||
match previous_category {
|
||||
RoomType::Favorite => {
|
||||
room.remove_tag(TagName::Favorite.as_ref()).await?;
|
||||
}
|
||||
RoomType::LowPriority => {
|
||||
room.remove_tag(TagName::LowPriority.as_ref()).await?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
RoomType::LowPriority => {
|
||||
room.set_tag(TagName::LowPriority.as_ref(), TagInfo::new())
|
||||
.await?;
|
||||
if previous_category == RoomType::Favorite {
|
||||
room.remove_tag(TagName::Favorite.as_ref()).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
RoomType::Left => room.leave().await,
|
||||
RoomType::Outdated => unimplemented!(),
|
||||
},
|
||||
MatrixRoom::Left(room) => match category {
|
||||
RoomType::Invited => Ok(()),
|
||||
RoomType::Favorite => {
|
||||
room.join().await
|
||||
// TODO: set favorite tag
|
||||
}
|
||||
RoomType::Normal => {
|
||||
room.join().await
|
||||
// TODO: remove tags
|
||||
}
|
||||
RoomType::LowPriority => {
|
||||
room.join().await
|
||||
// TODO: set low priority tag
|
||||
}
|
||||
RoomType::Left => Ok(()),
|
||||
RoomType::Outdated => unimplemented!(),
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1033,6 +1103,16 @@ impl Room {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
/// Connect to the signal sent when a room was forgotten.
|
||||
pub fn connect_room_forgotten<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("room-forgotten", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn predecessor(&self) -> Option<&RoomId> {
|
||||
let priv_ = imp::Room::from_instance(self);
|
||||
priv_.predecessor.get().map(std::ops::Deref::deref)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
use crate::session::sidebar::CategoryType;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use gtk::glib;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
|
||||
use crate::session::sidebar::CategoryType;
|
||||
|
||||
// TODO: do we also want the category `People` and a custom category support?
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum, IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u32)]
|
||||
#[genum(type_name = "RoomType")]
|
||||
pub enum RoomType {
|
||||
|
@ -14,6 +18,33 @@ pub enum RoomType {
|
|||
Outdated = 5,
|
||||
}
|
||||
|
||||
impl RoomType {
|
||||
/// Check whether this `RoomType` can be changed to `category`.
|
||||
pub fn can_change_to(&self, category: &RoomType) -> bool {
|
||||
match self {
|
||||
Self::Invited => {
|
||||
matches!(
|
||||
category,
|
||||
Self::Favorite | Self::Normal | Self::LowPriority | Self::Left
|
||||
)
|
||||
}
|
||||
Self::Favorite => {
|
||||
matches!(category, Self::Normal | Self::LowPriority | Self::Left)
|
||||
}
|
||||
Self::Normal => {
|
||||
matches!(category, Self::Favorite | Self::LowPriority | Self::Left)
|
||||
}
|
||||
Self::LowPriority => {
|
||||
matches!(category, Self::Favorite | Self::Normal | Self::Left)
|
||||
}
|
||||
Self::Left => {
|
||||
matches!(category, Self::Favorite | Self::Normal | Self::LowPriority)
|
||||
}
|
||||
Self::Outdated => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RoomType {
|
||||
fn default() -> Self {
|
||||
RoomType::Normal
|
||||
|
@ -25,3 +56,30 @@ impl ToString for RoomType {
|
|||
CategoryType::from(self).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CategoryType> for RoomType {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(category_type: CategoryType) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&category_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&CategoryType> for RoomType {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(category_type: &CategoryType) -> Result<Self, Self::Error> {
|
||||
match category_type {
|
||||
CategoryType::None => Err("CategoryType::None cannot be a RoomType"),
|
||||
CategoryType::Invited => Ok(Self::Invited),
|
||||
CategoryType::Favorite => Ok(Self::Favorite),
|
||||
CategoryType::Normal => Ok(Self::Normal),
|
||||
CategoryType::LowPriority => Ok(Self::LowPriority),
|
||||
CategoryType::Left => Ok(Self::Left),
|
||||
CategoryType::Outdated => Ok(Self::Outdated),
|
||||
CategoryType::VerificationRequest => {
|
||||
Err("CategoryType::VerificationRequest cannot be a RoomType")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,6 +226,9 @@ impl RoomList {
|
|||
obj.items_changed(position as u32, 1, 1);
|
||||
}
|
||||
}));
|
||||
room.connect_room_forgotten(clone!(@weak self as obj => move |room| {
|
||||
obj.remove(room.room_id());
|
||||
}));
|
||||
}
|
||||
|
||||
self.items_changed(position as u32, 0, added as u32);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use adw::subclass::prelude::BinImpl;
|
||||
use gettextrs::gettext;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{self, prelude::*};
|
||||
use gtk::{glib, CompositeTemplate};
|
||||
|
||||
use crate::session::sidebar::Category;
|
||||
use crate::session::sidebar::{Category, CategoryType};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
@ -13,8 +14,13 @@ mod imp {
|
|||
#[derive(Debug, Default, CompositeTemplate)]
|
||||
#[template(resource = "/org/gnome/FractalNext/sidebar-category-row.ui")]
|
||||
pub struct CategoryRow {
|
||||
/// The category of this row.
|
||||
pub category: RefCell<Option<Category>>,
|
||||
/// The expanded state of this row.
|
||||
pub expanded: Cell<bool>,
|
||||
/// The `CategoryType` to show a label for during a drag-and-drop
|
||||
/// operation.
|
||||
pub show_label_for_category: Cell<CategoryType>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -51,6 +57,21 @@ mod imp {
|
|||
true,
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
glib::ParamSpec::new_string(
|
||||
"label",
|
||||
"Label",
|
||||
"The label to show for this row",
|
||||
None,
|
||||
glib::ParamFlags::READABLE,
|
||||
),
|
||||
glib::ParamSpec::new_enum(
|
||||
"show-label-for-category",
|
||||
"Show Label for Category",
|
||||
"The CategoryType to show a label for",
|
||||
CategoryType::static_type(),
|
||||
CategoryType::None as i32,
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -65,14 +86,9 @@ mod imp {
|
|||
pspec: &glib::ParamSpec,
|
||||
) {
|
||||
match pspec.name() {
|
||||
"category" => {
|
||||
let category = value.get().unwrap();
|
||||
obj.set_category(category);
|
||||
}
|
||||
"expanded" => {
|
||||
let expanded = value.get().unwrap();
|
||||
obj.set_expanded(expanded);
|
||||
}
|
||||
"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!(),
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +97,8 @@ mod imp {
|
|||
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!(),
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +115,8 @@ glib::wrapper! {
|
|||
|
||||
impl CategoryRow {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new(&[]).expect("Failed to create CategoryRow")
|
||||
glib::Object::new(&[("show-label-for-category", &CategoryType::None)])
|
||||
.expect("Failed to create CategoryRow")
|
||||
}
|
||||
|
||||
pub fn category(&self) -> Option<Category> {
|
||||
|
@ -114,6 +133,7 @@ impl CategoryRow {
|
|||
|
||||
priv_.category.replace(category);
|
||||
self.notify("category");
|
||||
self.notify("label");
|
||||
}
|
||||
|
||||
fn expanded(&self) -> bool {
|
||||
|
@ -137,6 +157,66 @@ impl CategoryRow {
|
|||
priv_.expanded.set(expanded);
|
||||
self.notify("expanded");
|
||||
}
|
||||
|
||||
pub fn label(&self) -> Option<String> {
|
||||
let to_type = self.category()?.type_();
|
||||
let from_type = self.show_label_for_category();
|
||||
|
||||
let label = match from_type {
|
||||
CategoryType::Invited => match to_type {
|
||||
CategoryType::Favorite => gettext("Join Room as Favorite"),
|
||||
CategoryType::Normal => gettext("Join Room"),
|
||||
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("Unmark as Favorite"),
|
||||
CategoryType::LowPriority => gettext("Mark as Low Priority"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::Normal => match to_type {
|
||||
CategoryType::Favorite => gettext("Mark as Favorite"),
|
||||
CategoryType::LowPriority => gettext("Mark as Low Priority"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::LowPriority => match to_type {
|
||||
CategoryType::Favorite => gettext("Mark as Favorite"),
|
||||
CategoryType::Normal => gettext("Unmark as Low Priority"),
|
||||
CategoryType::Left => gettext("Leave Room"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
CategoryType::Left => match to_type {
|
||||
CategoryType::Favorite => gettext("Rejoin Room as Favorite"),
|
||||
CategoryType::Normal => gettext("Rejoin Room"),
|
||||
CategoryType::LowPriority => gettext("Rejoin Room as Low Priority"),
|
||||
_ => to_type.to_string(),
|
||||
},
|
||||
_ => to_type.to_string(),
|
||||
};
|
||||
|
||||
Some(label)
|
||||
}
|
||||
|
||||
pub fn show_label_for_category(&self) -> CategoryType {
|
||||
let priv_ = imp::CategoryRow::from_instance(self);
|
||||
priv_.show_label_for_category.get()
|
||||
}
|
||||
|
||||
pub fn set_show_label_for_category(&self, category: CategoryType) {
|
||||
let priv_ = imp::CategoryRow::from_instance(self);
|
||||
|
||||
if category == self.show_label_for_category() {
|
||||
return;
|
||||
}
|
||||
|
||||
priv_.show_label_for_category.set(category);
|
||||
|
||||
self.notify("show-label-for-category");
|
||||
self.notify("label");
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CategoryRow {
|
||||
|
|
|
@ -3,9 +3,10 @@ use gettextrs::gettext;
|
|||
use gtk::glib;
|
||||
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
|
||||
#[repr(u32)]
|
||||
#[repr(i32)]
|
||||
#[genum(type_name = "CategoryType")]
|
||||
pub enum CategoryType {
|
||||
None = -1,
|
||||
VerificationRequest = 0,
|
||||
Invited = 1,
|
||||
Favorite = 2,
|
||||
|
@ -24,6 +25,7 @@ impl Default for CategoryType {
|
|||
impl ToString for CategoryType {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
CategoryType::None => unimplemented!(),
|
||||
CategoryType::VerificationRequest => gettext("Verifications"),
|
||||
CategoryType::Invited => gettext("Invited"),
|
||||
CategoryType::Favorite => gettext("Favorite"),
|
||||
|
|
|
@ -103,6 +103,7 @@ impl Entry {
|
|||
pub fn icon_name(&self) -> Option<&str> {
|
||||
match self.type_() {
|
||||
EntryType::Explore => Some("explore-symbolic"),
|
||||
EntryType::Forget => Some("user-trash-symbolic"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use gtk::glib;
|
|||
#[genum(type_name = "EntryType")]
|
||||
pub enum EntryType {
|
||||
Explore = 0,
|
||||
Forget = 1,
|
||||
}
|
||||
|
||||
impl Default for EntryType {
|
||||
|
@ -18,6 +19,7 @@ impl ToString for EntryType {
|
|||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
EntryType::Explore => gettext("Explore"),
|
||||
EntryType::Forget => gettext("Forget Room"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
|
||||
|
||||
use crate::session::{
|
||||
room::RoomType,
|
||||
room_list::RoomList,
|
||||
sidebar::CategoryType,
|
||||
sidebar::EntryType,
|
||||
|
@ -17,10 +20,13 @@ mod imp {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ItemList {
|
||||
pub list: OnceCell<[(glib::Object, Cell<bool>); 7]>,
|
||||
pub list: OnceCell<[(glib::Object, Cell<bool>); 8]>,
|
||||
pub room_list: OnceCell<RoomList>,
|
||||
pub verification_list: OnceCell<VerificationList>,
|
||||
pub show_all: Cell<bool>,
|
||||
/// The `CategoryType` to show all compatible categories for.
|
||||
///
|
||||
/// Uses `RoomType::can_change_to` to find compatible categories.
|
||||
pub show_all_for_category: Cell<CategoryType>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -49,11 +55,12 @@ mod imp {
|
|||
VerificationList::static_type(),
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
|
||||
),
|
||||
glib::ParamSpec::new_boolean(
|
||||
"show-all",
|
||||
"Show All",
|
||||
"Whether all room categories should be shown",
|
||||
false,
|
||||
glib::ParamSpec::new_enum(
|
||||
"show-all-for-category",
|
||||
"Show All For Category",
|
||||
"The `CategoryType` to show all compatible categories for",
|
||||
CategoryType::static_type(),
|
||||
CategoryType::None as i32,
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
]
|
||||
|
@ -72,7 +79,7 @@ mod imp {
|
|||
match pspec.name() {
|
||||
"room-list" => obj.set_room_list(value.get().unwrap()),
|
||||
"verification-list" => obj.set_verification_list(value.get().unwrap()),
|
||||
"show-all" => obj.set_show_all(value.get().unwrap()),
|
||||
"show-all-for-category" => obj.set_show_all_for_category(value.get().unwrap()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +88,7 @@ mod imp {
|
|||
match pspec.name() {
|
||||
"room-list" => obj.room_list().to_value(),
|
||||
"verification-list" => obj.verification_list().to_value(),
|
||||
"show-all" => obj.show_all().to_value(),
|
||||
"show-all-for-category" => obj.show_all_for_category().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +108,7 @@ mod imp {
|
|||
Category::new(CategoryType::Normal, room_list).upcast::<glib::Object>(),
|
||||
Category::new(CategoryType::LowPriority, room_list).upcast::<glib::Object>(),
|
||||
Category::new(CategoryType::Left, room_list).upcast::<glib::Object>(),
|
||||
Entry::new(EntryType::Forget).upcast::<glib::Object>(),
|
||||
];
|
||||
|
||||
for (index, item) in list.iter().enumerate() {
|
||||
|
@ -108,7 +116,7 @@ mod imp {
|
|||
category.connect_notify_local(
|
||||
Some("empty"),
|
||||
clone!(@weak obj => move |_, _| {
|
||||
obj.update_category(index);
|
||||
obj.update_item(index);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -118,7 +126,9 @@ mod imp {
|
|||
let visible = if let Some(category) = item.downcast_ref::<Category>() {
|
||||
!category.is_empty()
|
||||
} else {
|
||||
true
|
||||
item.downcast_ref::<Entry>()
|
||||
.filter(|entry| entry.type_() == EntryType::Forget)
|
||||
.is_none()
|
||||
};
|
||||
(item, Cell::new(visible))
|
||||
});
|
||||
|
@ -177,12 +187,30 @@ impl ItemList {
|
|||
.expect("Failed to create ItemList")
|
||||
}
|
||||
|
||||
fn update_category(&self, position: usize) {
|
||||
fn update_item(&self, position: usize) {
|
||||
let priv_ = imp::ItemList::from_instance(self);
|
||||
let (item, old_visible) = priv_.list.get().unwrap().get(position).unwrap();
|
||||
let category = item.downcast_ref::<Category>().unwrap();
|
||||
|
||||
let visible = !category.is_empty() || (self.show_all() && is_show_all_category(category));
|
||||
let visible = if let Some(category) = item.downcast_ref::<Category>() {
|
||||
!category.is_empty()
|
||||
|| RoomType::try_from(self.show_all_for_category())
|
||||
.ok()
|
||||
.and_then(|room_type| {
|
||||
RoomType::try_from(category.type_())
|
||||
.ok()
|
||||
.filter(|category| room_type.can_change_to(category))
|
||||
})
|
||||
.is_some()
|
||||
} else if item
|
||||
.downcast_ref::<Entry>()
|
||||
.filter(|entry| entry.type_() == EntryType::Forget)
|
||||
.is_some()
|
||||
{
|
||||
self.show_all_for_category() == CategoryType::Left
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
if visible != old_visible.get() {
|
||||
old_visible.set(visible);
|
||||
let hidden_before_position = priv_
|
||||
|
@ -201,32 +229,24 @@ impl ItemList {
|
|||
}
|
||||
}
|
||||
|
||||
// Whether all room categories are shown
|
||||
// This doesn't include `CategoryType::Invite` since the user can't move rooms to it.
|
||||
pub fn show_all(&self) -> bool {
|
||||
pub fn show_all_for_category(&self) -> CategoryType {
|
||||
let priv_ = imp::ItemList::from_instance(self);
|
||||
priv_.show_all.get()
|
||||
priv_.show_all_for_category.get()
|
||||
}
|
||||
|
||||
// Set whether all room categories should be shown
|
||||
// This doesn't include `CategoryType::Invite` since the user can't move rooms to it.
|
||||
pub fn set_show_all(&self, show_all: bool) {
|
||||
pub fn set_show_all_for_category(&self, category: CategoryType) {
|
||||
let priv_ = imp::ItemList::from_instance(self);
|
||||
if show_all == self.show_all() {
|
||||
|
||||
if category == self.show_all_for_category() {
|
||||
return;
|
||||
}
|
||||
|
||||
priv_.show_all.set(show_all);
|
||||
|
||||
for (index, (item, _)) in priv_.list.get().unwrap().iter().enumerate() {
|
||||
if let Some(category) = item.downcast_ref::<Category>() {
|
||||
if is_show_all_category(category) {
|
||||
self.update_category(index);
|
||||
}
|
||||
}
|
||||
priv_.show_all_for_category.set(category);
|
||||
for i in 0..priv_.list.get().unwrap().len() {
|
||||
self.update_item(i);
|
||||
}
|
||||
|
||||
self.notify("show-all");
|
||||
self.notify("show-all-for-category");
|
||||
}
|
||||
|
||||
fn set_room_list(&self, room_list: RoomList) {
|
||||
|
@ -249,15 +269,3 @@ impl ItemList {
|
|||
priv_.verification_list.get().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Wheter this category should be shown when `show-all` is `true`
|
||||
// This doesn't include `CategoryType::Invite` since the user can't move rooms to it.
|
||||
fn is_show_all_category(category: &Category) -> bool {
|
||||
matches!(
|
||||
category.type_(),
|
||||
CategoryType::Favorite
|
||||
| CategoryType::Normal
|
||||
| CategoryType::LowPriority
|
||||
| CategoryType::Left
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ use self::row::Row;
|
|||
use self::selection::Selection;
|
||||
use self::verification_row::VerificationRow;
|
||||
|
||||
use adw::subclass::prelude::BinImpl;
|
||||
use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate, SelectionModel};
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::{gio, glib, subclass::prelude::*, CompositeTemplate, SelectionModel};
|
||||
|
||||
use crate::components::Avatar;
|
||||
use crate::session::room::Room;
|
||||
use crate::session::room::{Room, RoomType};
|
||||
use crate::session::verification::IdentityVerification;
|
||||
use crate::session::Session;
|
||||
use crate::session::User;
|
||||
|
@ -37,7 +37,10 @@ mod imp {
|
|||
use super::*;
|
||||
use glib::subclass::InitializingObject;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
convert::TryFrom,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, CompositeTemplate)]
|
||||
#[template(resource = "/org/gnome/FractalNext/sidebar.ui")]
|
||||
|
@ -55,6 +58,9 @@ mod imp {
|
|||
#[template_child]
|
||||
pub room_search: TemplateChild<gtk::SearchBar>,
|
||||
pub user: RefCell<Option<User>>,
|
||||
/// The type of the source that activated drop mode.
|
||||
pub drop_source_type: Cell<Option<RoomType>>,
|
||||
pub drop_binding: RefCell<Option<glib::Binding>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -68,6 +74,35 @@ mod imp {
|
|||
Row::static_type();
|
||||
Avatar::static_type();
|
||||
Self::bind_template(klass);
|
||||
klass.set_css_name("sidebar");
|
||||
|
||||
klass.install_action(
|
||||
"sidebar.set-drop-source-type",
|
||||
Some("u"),
|
||||
move |obj, _, variant| {
|
||||
obj.set_drop_source_type(
|
||||
variant
|
||||
.and_then(|variant| variant.get::<Option<u32>>().flatten())
|
||||
.and_then(|u| RoomType::try_from(u).ok()),
|
||||
);
|
||||
},
|
||||
);
|
||||
klass.install_action("sidebar.update-drop-targets", None, move |obj, _, _| {
|
||||
if obj.drop_source_type().is_some() {
|
||||
obj.update_drop_targets();
|
||||
}
|
||||
});
|
||||
klass.install_action(
|
||||
"sidebar.set-active-drop-category",
|
||||
Some("mu"),
|
||||
move |obj, _, variant| {
|
||||
obj.update_active_drop_targets(
|
||||
variant
|
||||
.and_then(|variant| variant.get::<Option<u32>>().flatten())
|
||||
.and_then(|u| RoomType::try_from(u).ok()),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
|
@ -98,7 +133,7 @@ mod imp {
|
|||
"Item List",
|
||||
"The list of items in the sidebar",
|
||||
ItemList::static_type(),
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
glib::ParamFlags::WRITABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
glib::ParamSpec::new_object(
|
||||
"selected-item",
|
||||
|
@ -107,6 +142,14 @@ mod imp {
|
|||
glib::Object::static_type(),
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
glib::ParamSpec::new_enum(
|
||||
"drop-source-type",
|
||||
"Drop Source Type",
|
||||
"The type of the source that activated drop mode",
|
||||
CategoryType::static_type(),
|
||||
CategoryType::None as i32,
|
||||
glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -144,6 +187,11 @@ mod imp {
|
|||
"compact" => self.compact.get().to_value(),
|
||||
"user" => obj.user().to_value(),
|
||||
"selected-item" => obj.selected_item().to_value(),
|
||||
"drop-source-type" => obj
|
||||
.drop_source_type()
|
||||
.map(CategoryType::from)
|
||||
.unwrap_or(CategoryType::None)
|
||||
.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +199,7 @@ mod imp {
|
|||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
self.listview.get().connect_activate(move |listview, pos| {
|
||||
self.listview.connect_activate(move |listview, pos| {
|
||||
let model: Option<Selection> = listview.model().and_then(|o| o.downcast().ok());
|
||||
let row: Option<gtk::TreeListRow> = model
|
||||
.as_ref()
|
||||
|
@ -200,6 +248,11 @@ impl Sidebar {
|
|||
|
||||
pub fn set_item_list(&self, item_list: Option<ItemList>) {
|
||||
let priv_ = imp::Sidebar::from_instance(self);
|
||||
|
||||
if let Some(binding) = priv_.drop_binding.take() {
|
||||
binding.unbind();
|
||||
}
|
||||
|
||||
let item_list = match item_list {
|
||||
Some(item_list) => item_list,
|
||||
None => {
|
||||
|
@ -208,6 +261,12 @@ impl Sidebar {
|
|||
}
|
||||
};
|
||||
|
||||
priv_.drop_binding.replace(
|
||||
self.bind_property("drop-source-type", &item_list, "show-all-for-category")
|
||||
.flags(glib::BindingFlags::SYNC_CREATE)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let tree_model = gtk::TreeListModel::new(&item_list, false, true, |item| {
|
||||
item.clone().downcast::<gio::ListModel>().ok()
|
||||
});
|
||||
|
@ -280,6 +339,119 @@ impl Sidebar {
|
|||
.account_switcher
|
||||
.set_logged_in_users(sessions_stack_pages, session_root);
|
||||
}
|
||||
|
||||
pub fn drop_source_type(&self) -> Option<RoomType> {
|
||||
let priv_ = imp::Sidebar::from_instance(self);
|
||||
priv_.drop_source_type.get()
|
||||
}
|
||||
|
||||
pub fn set_drop_source_type(&self, source_type: Option<RoomType>) {
|
||||
let priv_ = imp::Sidebar::from_instance(self);
|
||||
|
||||
if self.drop_source_type() == source_type {
|
||||
return;
|
||||
}
|
||||
|
||||
priv_.drop_source_type.set(source_type);
|
||||
|
||||
if source_type.is_some() {
|
||||
priv_.listview.add_css_class("drop-mode");
|
||||
} else {
|
||||
priv_.listview.remove_css_class("drop-mode");
|
||||
}
|
||||
|
||||
self.notify("drop-source-type");
|
||||
self.update_drop_targets();
|
||||
}
|
||||
|
||||
/// Update the disabled or empty state of drop targets.
|
||||
fn update_drop_targets(&self) {
|
||||
let priv_ = imp::Sidebar::from_instance(self);
|
||||
let mut child = priv_.listview.first_child();
|
||||
|
||||
while let Some(widget) = child {
|
||||
if let Some(row) = widget
|
||||
.first_child()
|
||||
.and_then(|widget| widget.downcast::<Row>().ok())
|
||||
{
|
||||
if let Some(source_type) = self.drop_source_type() {
|
||||
if row
|
||||
.room_type()
|
||||
.filter(|row_type| source_type.can_change_to(row_type))
|
||||
.is_some()
|
||||
{
|
||||
row.remove_css_class("drop-disabled");
|
||||
|
||||
if row
|
||||
.item()
|
||||
.and_then(|object| object.downcast::<Category>().ok())
|
||||
.filter(|category| category.is_empty())
|
||||
.is_some()
|
||||
{
|
||||
row.add_css_class("drop-empty");
|
||||
} else {
|
||||
row.remove_css_class("drop-empty");
|
||||
}
|
||||
} else {
|
||||
let is_forget_entry = row
|
||||
.entry_type()
|
||||
.filter(|entry_type| entry_type == &EntryType::Forget)
|
||||
.is_some();
|
||||
if is_forget_entry && source_type == RoomType::Left {
|
||||
row.remove_css_class("drop-disabled");
|
||||
} else {
|
||||
row.add_css_class("drop-disabled");
|
||||
row.remove_css_class("drop-empty");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Clear style
|
||||
row.remove_css_class("drop-disabled");
|
||||
row.remove_css_class("drop-empty");
|
||||
row.parent().unwrap().remove_css_class("drop-active");
|
||||
};
|
||||
|
||||
if let Some(category_row) = row
|
||||
.child()
|
||||
.and_then(|child| child.downcast::<CategoryRow>().ok())
|
||||
{
|
||||
category_row.set_show_label_for_category(
|
||||
self.drop_source_type()
|
||||
.map(CategoryType::from)
|
||||
.unwrap_or(CategoryType::None),
|
||||
);
|
||||
}
|
||||
}
|
||||
child = widget.next_sibling();
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the active state of drop targets.
|
||||
fn update_active_drop_targets(&self, target_type: Option<RoomType>) {
|
||||
let priv_ = imp::Sidebar::from_instance(self);
|
||||
let mut child = priv_.listview.first_child();
|
||||
|
||||
while let Some(widget) = child {
|
||||
if let Some((row, row_type)) = widget
|
||||
.first_child()
|
||||
.and_then(|widget| widget.downcast::<Row>().ok())
|
||||
.and_then(|row| {
|
||||
let row_type = row.room_type()?;
|
||||
Some((row, row_type))
|
||||
})
|
||||
{
|
||||
if target_type
|
||||
.filter(|target_type| target_type == &row_type)
|
||||
.is_some()
|
||||
{
|
||||
row.parent().unwrap().add_css_class("drop-active");
|
||||
} else {
|
||||
row.parent().unwrap().remove_css_class("drop-active");
|
||||
}
|
||||
}
|
||||
child = widget.next_sibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Sidebar {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use adw::subclass::prelude::BinImpl;
|
||||
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
use gtk::{gdk, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
|
||||
use crate::session::room::{HighlightFlags, Room};
|
||||
use crate::session::room::{HighlightFlags, Room, RoomType};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
@ -74,6 +74,27 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
// Allow to drag rooms
|
||||
let drag = gtk::DragSource::builder()
|
||||
.actions(gdk::DragAction::MOVE)
|
||||
.build();
|
||||
drag.connect_prepare(
|
||||
clone!(@weak obj => @default-return None, move |drag, x, y| {
|
||||
obj.drag_prepare(drag, x, y)
|
||||
}),
|
||||
);
|
||||
drag.connect_drag_begin(clone!(@weak obj => move |_, _| {
|
||||
obj.drag_begin();
|
||||
}));
|
||||
drag.connect_drag_end(clone!(@weak obj => move |_, _, _| {
|
||||
obj.drag_end();
|
||||
}));
|
||||
obj.add_controller(&drag);
|
||||
}
|
||||
|
||||
fn dispose(&self, _obj: &Self::Type) {
|
||||
if let Some(room) = self.room.take() {
|
||||
if let Some(id) = self.signal_handler.take() {
|
||||
|
@ -116,6 +137,7 @@ impl RoomRow {
|
|||
if let Some(binding) = priv_.binding.take() {
|
||||
binding.unbind();
|
||||
}
|
||||
priv_.display_name.remove_css_class("dim-label");
|
||||
}
|
||||
|
||||
if let Some(ref room) = room {
|
||||
|
@ -138,6 +160,10 @@ impl RoomRow {
|
|||
}),
|
||||
)));
|
||||
|
||||
if room.category() == RoomType::Left {
|
||||
priv_.display_name.add_css_class("dim-label");
|
||||
}
|
||||
|
||||
self.set_highlight();
|
||||
}
|
||||
priv_.room.replace(room);
|
||||
|
@ -168,6 +194,26 @@ 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()))
|
||||
}
|
||||
|
||||
fn drag_begin(&self) {
|
||||
self.parent().unwrap().add_css_class("drag");
|
||||
let category = Some(u32::from(self.room().unwrap().category()));
|
||||
self.activate_action("sidebar.set-drop-source-type", Some(&category.to_variant()));
|
||||
}
|
||||
|
||||
fn drag_end(&self) {
|
||||
self.activate_action("sidebar.set-drop-source-type", None);
|
||||
self.parent().unwrap().remove_css_class("drag");
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RoomRow {
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::{glib, subclass::prelude::*};
|
||||
use gtk::{gdk, glib, glib::clone, subclass::prelude::*};
|
||||
|
||||
use crate::session::{
|
||||
room::Room,
|
||||
room::{Room, RoomType},
|
||||
sidebar::{Category, CategoryRow, Entry, EntryRow, RoomRow, VerificationRow},
|
||||
verification::IdentityVerification,
|
||||
};
|
||||
|
||||
use super::EntryType;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -23,6 +27,10 @@ mod imp {
|
|||
const NAME: &'static str = "SidebarRow";
|
||||
type Type = super::Row;
|
||||
type ParentType = adw::Bin;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.set_css_name("sidebar-row");
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for Row {
|
||||
|
@ -72,6 +80,28 @@ mod imp {
|
|||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
// Set up drop controller
|
||||
let drop = gtk::DropTarget::builder()
|
||||
.actions(gdk::DragAction::MOVE)
|
||||
.formats(&gdk::ContentFormats::for_type(Room::static_type()))
|
||||
.build();
|
||||
drop.connect_accept(clone!(@weak obj => @default-return false, move |_, drop| {
|
||||
obj.drop_accept(drop)
|
||||
}));
|
||||
drop.connect_leave(clone!(@weak obj => move |_| {
|
||||
obj.drop_leave();
|
||||
}));
|
||||
drop.connect_drop(
|
||||
clone!(@weak obj => @default-return false, move |_, v, _, _| {
|
||||
obj.drop_end(v)
|
||||
}),
|
||||
);
|
||||
obj.add_controller(&drop);
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for Row {}
|
||||
|
@ -162,6 +192,10 @@ impl Row {
|
|||
child
|
||||
};
|
||||
|
||||
if entry.type_() == EntryType::Forget {
|
||||
self.add_css_class("forget");
|
||||
}
|
||||
|
||||
child.set_entry(Some(entry.clone()));
|
||||
|
||||
if let Some(list_item) = self.parent() {
|
||||
|
@ -186,11 +220,84 @@ impl Row {
|
|||
} else {
|
||||
panic!("Wrong row item: {:?}", item);
|
||||
}
|
||||
self.activate_action("sidebar.update-drop-targets", None);
|
||||
}
|
||||
|
||||
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`.
|
||||
pub fn room_type(&self) -> Option<RoomType> {
|
||||
let item = self.item()?;
|
||||
|
||||
if let Some(room) = item.downcast_ref::<Room>() {
|
||||
Some(room.category())
|
||||
} else {
|
||||
item.downcast_ref::<Category>()
|
||||
.and_then(|category| RoomType::try_from(category.type_()).ok())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `EntryType` of this item.
|
||||
///
|
||||
/// If this is not a `Entry`, returns `None`.
|
||||
pub fn entry_type(&self) -> Option<EntryType> {
|
||||
let item = self.item()?;
|
||||
item.downcast_ref::<Entry>().map(|entry| entry.type_())
|
||||
}
|
||||
|
||||
fn drop_accept(&self, drop: &gdk::Drop) -> bool {
|
||||
let room = drop
|
||||
.drag()
|
||||
.and_then(|drag| drag.content())
|
||||
.and_then(|content| content.value(Room::static_type()).ok())
|
||||
.and_then(|value| value.get::<Room>().ok());
|
||||
if let Some(room) = room {
|
||||
if let Some(target_type) = self.room_type() {
|
||||
if room.category().can_change_to(&target_type) {
|
||||
self.activate_action(
|
||||
"sidebar.set-active-drop-category",
|
||||
Some(&Some(u32::from(target_type)).to_variant()),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} else if let Some(entry_type) = self.entry_type() {
|
||||
if room.category() == RoomType::Left && entry_type == EntryType::Forget {
|
||||
self.parent().unwrap().add_css_class("drop-active");
|
||||
self.activate_action("sidebar.set-active-drop-category", None);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn drop_leave(&self) {
|
||||
self.parent().unwrap().remove_css_class("drop-active");
|
||||
self.activate_action("sidebar.set-active-drop-category", None);
|
||||
}
|
||||
|
||||
fn drop_end(&self, value: &glib::Value) -> bool {
|
||||
let mut ret = false;
|
||||
if let Ok(room) = value.get::<Room>() {
|
||||
if let Some(target_type) = self.room_type() {
|
||||
if room.category().can_change_to(&target_type) {
|
||||
room.set_category(target_type);
|
||||
ret = true;
|
||||
}
|
||||
} else if let Some(entry_type) = self.entry_type() {
|
||||
if room.category() == RoomType::Left && entry_type == EntryType::Forget {
|
||||
room.forget();
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.activate_action("sidebar.set-drop-source-type", None);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Row {
|
||||
|
|
Loading…
Reference in New Issue