From bd13ae4ef233c6065972818725ba0fb6d95d401b Mon Sep 17 00:00:00 2001 From: Julian Sparber Date: Thu, 3 Jun 2021 19:22:59 +0200 Subject: [PATCH] sidebar: Add Explore entry --- .../scalable/status/explore-symbolic.svg | 151 +++++++++++++++++ data/resources/resources.gresource.xml | 2 + data/resources/style.css | 5 + data/resources/ui/session.ui | 2 + data/resources/ui/sidebar-entry-row.ui | 36 +++++ po/POTFILES.in | 2 + src/session/content/content.rs | 61 +++++-- src/session/content/content_type.rs | 27 ++++ src/session/content/mod.rs | 2 + src/session/mod.rs | 43 ++++- src/session/sidebar/entry.rs | 109 +++++++++++++ src/session/sidebar/entry_row.rs | 101 ++++++++++++ src/session/sidebar/item_list.rs | 24 +-- src/session/sidebar/mod.rs | 4 + src/session/sidebar/row.rs | 17 +- src/session/sidebar/selection.rs | 152 +++++++++++++----- src/session/sidebar/sidebar.rs | 44 ++++- 17 files changed, 714 insertions(+), 68 deletions(-) create mode 100644 data/resources/icons/scalable/status/explore-symbolic.svg create mode 100644 data/resources/ui/sidebar-entry-row.ui create mode 100644 src/session/content/content_type.rs create mode 100644 src/session/sidebar/entry.rs create mode 100644 src/session/sidebar/entry_row.rs diff --git a/data/resources/icons/scalable/status/explore-symbolic.svg b/data/resources/icons/scalable/status/explore-symbolic.svg new file mode 100644 index 00000000..3dac6a5e --- /dev/null +++ b/data/resources/icons/scalable/status/explore-symbolic.svg @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index 4e2683c3..ebcba03e 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -17,6 +17,7 @@ ui/sidebar.ui ui/sidebar-item.ui ui/sidebar-category-row.ui + ui/sidebar-entry-row.ui ui/sidebar-room-row.ui ui/window.ui ui/context-menu-bin.ui @@ -27,6 +28,7 @@ style.css icons/scalable/actions/send-symbolic.svg icons/scalable/status/welcome.svg + icons/scalable/status/explore-symbolic.svg diff --git a/data/resources/style.css b/data/resources/style.css index 91f3b54d..ee1a5c59 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -46,6 +46,11 @@ headerbar.flat { font-weight: bold; } +.sidebar .entry { + margin-top: 4px; + font-weight: bold; +} + .sidebar .category image.arrow { transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); } diff --git a/data/resources/ui/session.ui b/data/resources/ui/session.ui index 590cf2cc..363738c9 100644 --- a/data/resources/ui/session.ui +++ b/data/resources/ui/session.ui @@ -51,12 +51,14 @@ + + error_list diff --git a/data/resources/ui/sidebar-entry-row.ui b/data/resources/ui/sidebar-entry-row.ui new file mode 100644 index 00000000..4082eea1 --- /dev/null +++ b/data/resources/ui/sidebar-entry-row.ui @@ -0,0 +1,36 @@ + + + + + diff --git a/po/POTFILES.in b/po/POTFILES.in index 28cd433e..41d6c374 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -22,6 +22,7 @@ data/resources/ui/in-app-notification.ui data/resources/ui/session.ui data/resources/ui/shortcuts.ui data/resources/ui/sidebar-category-row.ui +data/resources/ui/sidebar-entry-row.ui data/resources/ui/sidebar-item.ui data/resources/ui/sidebar-room-row.ui data/resources/ui/sidebar.ui @@ -64,6 +65,7 @@ src/session/room/mod.rs src/session/room/room.rs src/session/room/timeline.rs src/session/sidebar/category_row.rs +src/session/sidebar/entry.rs src/session/sidebar/mod.rs src/session/sidebar/room_row.rs src/session/sidebar/row.rs diff --git a/src/session/content/content.rs b/src/session/content/content.rs index d7dd8166..ac16a977 100644 --- a/src/session/content/content.rs +++ b/src/session/content/content.rs @@ -1,4 +1,5 @@ use crate::session::{ + content::ContentType, content::Invite, content::RoomHistory, room::{Room, RoomType}, @@ -16,6 +17,7 @@ mod imp { pub struct Content { pub compact: Cell, pub room: RefCell>, + pub content_type: Cell, pub error_list: RefCell>, pub category_handler: RefCell>, #[template_child] @@ -39,7 +41,7 @@ mod imp { klass.set_accessible_role(gtk::AccessibleRole::Group); klass.install_action("content.go-back", None, move |widget, _, _| { - widget.set_room(None); + widget.set_content_type(ContentType::None); }); } @@ -74,6 +76,14 @@ mod imp { gio::ListStore::static_type(), glib::ParamFlags::READWRITE, ), + glib::ParamSpec::new_enum( + "content-type", + "Content Type", + "The type of content currently displayed", + ContentType::static_type(), + ContentType::default() as i32, + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), ] }); @@ -99,6 +109,7 @@ mod imp { "error-list" => { self.error_list.replace(value.get().unwrap()); } + "content-type" => obj.set_content_type(value.get().unwrap()), _ => unimplemented!(), } } @@ -108,6 +119,7 @@ mod imp { "compact" => self.compact.get().to_value(), "room" => obj.room().to_value(), "error-list" => self.error_list.borrow().to_value(), + "content-type" => obj.content_type().to_value(), _ => unimplemented!(), } } @@ -127,6 +139,24 @@ impl Content { glib::Object::new(&[]).expect("Failed to create Content") } + pub fn content_type(&self) -> ContentType { + let priv_ = imp::Content::from_instance(self); + priv_.content_type.get() + } + + pub fn set_content_type(&self, content_type: ContentType) { + let priv_ = imp::Content::from_instance(self); + + if self.content_type() == content_type { + return; + } + + priv_.content_type.set(content_type); + self.set_visible_child(); + + self.notify("content-type"); + } + pub fn set_room(&self, room: Option) { let priv_ = imp::Content::from_instance(self); @@ -143,17 +173,16 @@ impl Content { if let Some(ref room) = room { let handler_id = room.connect_notify_local( Some("category"), - clone!(@weak self as obj => move |room, _| { - obj.set_visible_child(room); + clone!(@weak self as obj => move |_, _| { + obj.set_visible_child(); }), ); - self.set_visible_child(&room); priv_.category_handler.replace(Some(handler_id)); } priv_.room.replace(room); - + self.set_visible_child(); self.notify("room"); } @@ -162,13 +191,25 @@ impl Content { priv_.room.borrow().clone() } - fn set_visible_child(&self, room: &Room) { + fn set_visible_child(&self) { let priv_ = imp::Content::from_instance(self); - if room.category() == RoomType::Invited { - priv_.stack.set_visible_child(&*priv_.invite); - } else { - priv_.stack.set_visible_child(&*priv_.room_history); + match self.content_type() { + ContentType::None => { + //TODO: display an empty state + } + ContentType::Room => { + if let Some(room) = &*priv_.room.borrow() { + if room.category() == RoomType::Invited { + priv_.stack.set_visible_child(&*priv_.invite); + } else { + priv_.stack.set_visible_child(&*priv_.room_history); + } + } + } + ContentType::Explore => { + todo!("Display explore"); + } } } } diff --git a/src/session/content/content_type.rs b/src/session/content/content_type.rs new file mode 100644 index 00000000..50eff2a5 --- /dev/null +++ b/src/session/content/content_type.rs @@ -0,0 +1,27 @@ +use gettextrs::gettext; +use gtk::glib; + +#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)] +#[repr(u32)] +#[genum(type_name = "ContentType")] +pub enum ContentType { + None = 0, + Explore = 1, + Room = 2, +} + +impl Default for ContentType { + fn default() -> Self { + ContentType::None + } +} + +impl ToString for ContentType { + fn to_string(&self) -> String { + match self { + ContentType::None => gettext("No selection"), + ContentType::Explore => gettext("Explore"), + ContentType::Room => gettext("Room"), + } + } +} diff --git a/src/session/content/mod.rs b/src/session/content/mod.rs index 3109a2ed..0de3f250 100644 --- a/src/session/content/mod.rs +++ b/src/session/content/mod.rs @@ -1,4 +1,5 @@ mod content; +mod content_type; mod divider_row; mod invite; mod item_row; @@ -8,6 +9,7 @@ mod room_history; mod state_row; pub use self::content::Content; +pub use self::content_type::ContentType; use self::divider_row::DividerRow; use self::invite::Invite; use self::item_row::ItemRow; diff --git a/src/session/mod.rs b/src/session/mod.rs index 470a5006..07c13f56 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -20,6 +20,7 @@ use crate::utils::do_async; use crate::Error; use crate::RUNTIME; +use crate::session::content::ContentType; use adw; use adw::subclass::prelude::BinImpl; use gtk::subclass::prelude::*; @@ -41,7 +42,7 @@ mod imp { use super::*; use glib::subclass::{InitializingObject, Signal}; use once_cell::sync::{Lazy, OnceCell}; - use std::cell::RefCell; + use std::cell::{Cell, RefCell}; #[derive(Debug, Default, CompositeTemplate)] #[template(resource = "/org/gnome/FractalNext/session.ui")] @@ -58,6 +59,7 @@ mod imp { pub room_list: OnceCell, pub user: OnceCell, pub selected_room: RefCell>, + pub selected_content_type: Cell, pub is_ready: OnceCell, } @@ -98,6 +100,14 @@ mod imp { Room::static_type(), glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, ), + glib::ParamSpec::new_enum( + "selected-content-type", + "Selected Content Type", + "The current content type selected", + ContentType::static_type(), + ContentType::default() as i32, + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), glib::ParamSpec::new_object( "user", "User", @@ -123,6 +133,7 @@ mod imp { let selected_room = value.get().unwrap(); obj.set_selected_room(selected_room); } + "selected-content-type" => obj.set_selected_content_type(value.get().unwrap()), _ => unimplemented!(), } } @@ -132,6 +143,7 @@ mod imp { "room-list" => obj.room_list().to_value(), "selected-room" => obj.selected_room().to_value(), "user" => obj.user().to_value(), + "selected-content-type" => obj.selected_content_type().to_value(), _ => unimplemented!(), } } @@ -157,6 +169,29 @@ impl Session { glib::Object::new(&[]).expect("Failed to create Session") } + pub fn selected_content_type(&self) -> ContentType { + let priv_ = imp::Session::from_instance(self); + priv_.selected_content_type.get() + } + + pub fn set_selected_content_type(&self, selected_type: ContentType) { + let priv_ = imp::Session::from_instance(self); + + if self.selected_content_type() == selected_type { + return; + } + + if selected_type == ContentType::None { + priv_.content.navigate(adw::NavigationDirection::Back); + } else { + priv_.content.navigate(adw::NavigationDirection::Forward); + } + + priv_.selected_content_type.set(selected_type); + + self.notify("selected-content-type"); + } + pub fn selected_room(&self) -> Option { let priv_ = imp::Session::from_instance(self); priv_.selected_room.borrow().clone() @@ -169,12 +204,6 @@ impl Session { return; } - if selected_room.is_some() { - priv_.content.navigate(adw::NavigationDirection::Forward); - } else { - priv_.content.navigate(adw::NavigationDirection::Back); - } - priv_.selected_room.replace(selected_room); self.notify("selected-room"); diff --git a/src/session/sidebar/entry.rs b/src/session/sidebar/entry.rs new file mode 100644 index 00000000..0aea94ad --- /dev/null +++ b/src/session/sidebar/entry.rs @@ -0,0 +1,109 @@ +use gtk::{glib, prelude::*, subclass::prelude::*}; + +use crate::session::content::ContentType; + +mod imp { + use std::cell::{Cell, RefCell}; + + use super::*; + + #[derive(Debug, Default)] + pub struct Entry { + pub type_: Cell, + pub display_name: RefCell>, + pub icon_name: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for Entry { + const NAME: &'static str = "Entry"; + type Type = super::Entry; + type ParentType = glib::Object; + } + + impl ObjectImpl for Entry { + fn properties() -> &'static [glib::ParamSpec] { + use once_cell::sync::Lazy; + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpec::new_enum( + "type", + "Type", + "The type of this category", + ContentType::static_type(), + ContentType::default() as i32, + glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY, + ), + glib::ParamSpec::new_string( + "display-name", + "Display Name", + "The display name of this Entry", + None, + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), + glib::ParamSpec::new_string( + "icon-name", + "Icon Name", + "The icon name used for this Entry", + None, + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property( + &self, + _obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.name() { + "type" => { + self.type_.set(value.get().unwrap()); + } + "display-name" => { + let _ = self.display_name.replace(value.get().unwrap()); + } + "icon-name" => { + let _ = self.icon_name.replace(value.get().unwrap()); + } + _ => unimplemented!(), + } + } + + fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "type" => obj.type_().to_value(), + "display-name" => obj.type_().to_string().to_value(), + "icon-name" => obj.icon_name().to_value(), + _ => unimplemented!(), + } + } + } +} + +glib::wrapper! { + pub struct Entry(ObjectSubclass); +} + +impl Entry { + pub fn new(type_: ContentType) -> Self { + glib::Object::new(&[("type", &type_)]).expect("Failed to create Entry") + } + + pub fn type_(&self) -> ContentType { + let priv_ = imp::Entry::from_instance(self); + priv_.type_.get() + } + + pub fn icon_name(&self) -> Option<&str> { + match self.type_() { + ContentType::Explore => Some("explore-symbolic"), + _ => None, + } + } +} diff --git a/src/session/sidebar/entry_row.rs b/src/session/sidebar/entry_row.rs new file mode 100644 index 00000000..bf7faa0c --- /dev/null +++ b/src/session/sidebar/entry_row.rs @@ -0,0 +1,101 @@ +use adw; +use adw::subclass::prelude::BinImpl; +use gtk::subclass::prelude::*; +use gtk::{self, prelude::*}; +use gtk::{glib, CompositeTemplate}; + +use crate::session::sidebar::Entry; + +mod imp { + use super::*; + use glib::subclass::InitializingObject; + use std::cell::RefCell; + + #[derive(Debug, Default, CompositeTemplate)] + #[template(resource = "/org/gnome/FractalNext/sidebar-entry-row.ui")] + pub struct EntryRow { + pub entry: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for EntryRow { + const NAME: &'static str = "SidebarEntryRow"; + type Type = super::EntryRow; + type ParentType = adw::Bin; + + fn class_init(klass: &mut Self::Class) { + Self::bind_template(klass); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for EntryRow { + fn properties() -> &'static [glib::ParamSpec] { + use once_cell::sync::Lazy; + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![glib::ParamSpec::new_object( + "entry", + "Entry", + "The entry of this row", + Entry::static_type(), + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + )] + }); + + PROPERTIES.as_ref() + } + + fn set_property( + &self, + obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.name() { + "entry" => obj.set_entry(value.get().unwrap()), + _ => unimplemented!(), + } + } + + fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "entry" => obj.entry().to_value(), + _ => unimplemented!(), + } + } + } + + impl WidgetImpl for EntryRow {} + impl BinImpl for EntryRow {} +} + +glib::wrapper! { + pub struct EntryRow(ObjectSubclass) + @extends gtk::Widget, adw::Bin, @implements gtk::Accessible; +} + +impl EntryRow { + pub fn new() -> Self { + glib::Object::new(&[]).expect("Failed to create EntryRow") + } + + pub fn entry(&self) -> Option { + let priv_ = imp::EntryRow::from_instance(&self); + priv_.entry.borrow().clone() + } + + pub fn set_entry(&self, entry: Option) { + let priv_ = imp::EntryRow::from_instance(&self); + + if self.entry() == entry { + return; + } + + priv_.entry.replace(entry); + self.notify("entry"); + } +} diff --git a/src/session/sidebar/item_list.rs b/src/session/sidebar/item_list.rs index 0cd4a89d..1c0675b6 100644 --- a/src/session/sidebar/item_list.rs +++ b/src/session/sidebar/item_list.rs @@ -1,6 +1,11 @@ use gtk::{gio, glib, prelude::*, subclass::prelude::*}; -use crate::session::{room::RoomType, room_list::RoomList, sidebar::Category}; +use crate::session::{ + content::ContentType, + room::RoomType, + room_list::RoomList, + sidebar::{Category, Entry}, +}; mod imp { use once_cell::unsync::OnceCell; @@ -9,7 +14,7 @@ mod imp { #[derive(Debug, Default)] pub struct ItemList { - pub list: OnceCell<[Category; 5]>, + pub list: OnceCell<[glib::Object; 6]>, } #[glib::object_subclass] @@ -24,7 +29,7 @@ mod imp { impl ListModelImpl for ItemList { fn item_type(&self, _list_model: &Self::Type) -> glib::Type { - Category::static_type() + glib::Object::static_type() } fn n_items(&self, _list_model: &Self::Type) -> u32 { self.list.get().map(|l| l.len()).unwrap_or(0) as u32 @@ -61,14 +66,15 @@ impl ItemList { priv_ .list .set([ - Category::new(RoomType::Invited, room_list), - Category::new(RoomType::Favorite, room_list), - Category::new(RoomType::Normal, room_list), - Category::new(RoomType::LowPriority, room_list), - Category::new(RoomType::Left, room_list), + Entry::new(ContentType::Explore).upcast::(), + Category::new(RoomType::Invited, room_list).upcast::(), + Category::new(RoomType::Favorite, room_list).upcast::(), + Category::new(RoomType::Normal, room_list).upcast::(), + Category::new(RoomType::LowPriority, room_list).upcast::(), + Category::new(RoomType::Left, room_list).upcast::(), ]) .unwrap(); - self.items_changed(0, 0, 5); + self.items_changed(0, 0, 6); } } diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs index 0b298088..e9cc2def 100644 --- a/src/session/sidebar/mod.rs +++ b/src/session/sidebar/mod.rs @@ -1,5 +1,7 @@ mod category; mod category_row; +mod entry; +mod entry_row; mod item_list; mod room_row; mod row; @@ -8,6 +10,8 @@ mod sidebar; pub use self::category::Category; use self::category_row::CategoryRow; +pub use self::entry::Entry; +use self::entry_row::EntryRow; pub use self::item_list::ItemList; use self::room_row::RoomRow; use self::row::Row; diff --git a/src/session/sidebar/row.rs b/src/session/sidebar/row.rs index 3a4629b5..9ff8ed80 100644 --- a/src/session/sidebar/row.rs +++ b/src/session/sidebar/row.rs @@ -3,7 +3,7 @@ use gtk::{glib, prelude::*, subclass::prelude::*}; use crate::session::{ room::Room, - sidebar::{Category, CategoryRow, RoomRow}, + sidebar::{Category, CategoryRow, Entry, EntryRow, RoomRow}, }; mod imp { @@ -151,6 +151,21 @@ impl Row { if let Some(list_item) = self.parent() { list_item.set_css_classes(&["room"]); } + } else if let Some(entry) = item.downcast_ref::() { + let child = if let Some(Ok(child)) = self.child().map(|w| w.downcast::()) + { + child + } else { + let child = EntryRow::new(); + self.set_child(Some(&child)); + child + }; + + child.set_entry(Some(entry.clone())); + + if let Some(list_item) = self.parent() { + list_item.set_css_classes(&["entry"]); + } } else { panic!("Wrong row item: {:?}", item); } diff --git a/src/session/sidebar/selection.rs b/src/session/sidebar/selection.rs index d74a3f81..544b6bf2 100644 --- a/src/session/sidebar/selection.rs +++ b/src/session/sidebar/selection.rs @@ -1,6 +1,6 @@ use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*}; -use crate::session::room::Room; +use crate::session::{content::ContentType, room::Room, sidebar::Entry}; mod imp { use super::*; @@ -11,7 +11,7 @@ mod imp { pub struct Selection { pub model: RefCell>, pub selected: Cell, - pub selected_room: RefCell>, + pub selected_item: RefCell>, pub signal_handler: RefCell>, } @@ -51,10 +51,18 @@ mod imp { glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, ), glib::ParamSpec::new_object( - "selected-room", - "Selected Room", - "The selected room", - Room::static_type(), + "selected-item", + "Selected Item", + "The selected item", + glib::Object::static_type(), + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), + glib::ParamSpec::new_enum( + "selected-type", + "Selected Type", + "The currently selected content type", + ContentType::static_type(), + ContentType::default() as i32, glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, ), ] @@ -79,10 +87,8 @@ mod imp { let selected = value.get().unwrap(); obj.set_selected(selected); } - "selected-room" => { - let selected_room = value.get().unwrap(); - obj.set_selected_room(selected_room); - } + "selected-item" => obj.set_selected_item(value.get().unwrap()), + "selected-type" => obj.set_selected_type(value.get().unwrap()), _ => unimplemented!(), } } @@ -91,7 +97,8 @@ mod imp { match pspec.name() { "model" => obj.model().to_value(), "selected" => obj.selected().to_value(), - "selected-room" => obj.selected_room().to_value(), + "selected-item" => obj.selected_item().to_value(), + "selected-type" => obj.selected_type().to_value(), _ => unimplemented!(), } } @@ -157,9 +164,77 @@ impl Selection { priv_.selected.get() } - pub fn selected_room(&self) -> Option { + pub fn selected_item(&self) -> Option { let priv_ = imp::Selection::from_instance(self); - priv_.selected_room.borrow().clone() + priv_.selected_item.borrow().clone() + } + + pub fn selected_type(&self) -> ContentType { + if let Some(item) = self.selected_item() { + if item.is::() { + return ContentType::Room; + } else if let Ok(entry) = item.downcast::() { + return entry.type_(); + } + } + + ContentType::None + } + + pub fn set_selected_type(&self, selected_type: ContentType) { + let priv_ = imp::Selection::from_instance(self); + + if self.selected_type() == selected_type { + return; + } + + match selected_type { + ContentType::None => self.set_selected_item(None), + ContentType::Room => { + if self + .selected_item() + .and_then(|item| item.downcast::().ok()) + .is_none() + { + if let Some(model) = &*priv_.model.borrow() { + for i in 0..model.n_items() { + if let Some(room) = model + .item(i) + .and_then(|item| item.downcast::().ok()) + .and_then(|i| i.item()) + .and_then(|o| o.downcast::().ok()) + { + self.set_selected_item(Some(room.upcast())); + break; + } + } + } + } + } + ContentType::Explore => { + if !self + .selected_item() + .and_then(|item| item.downcast::().ok()) + .map_or(false, |entry| entry.type_() == selected_type) + { + if let Some(model) = &*priv_.model.borrow() { + for i in 0..model.n_items() { + if let Some(entry) = model + .item(i) + .and_then(|item| item.downcast::().ok()) + .and_then(|i| i.item()) + .and_then(|o| o.downcast::().ok()) + { + if entry.type_() == selected_type { + self.set_selected_item(Some(entry.upcast())); + break; + } + } + } + } + } + } + }; } pub fn set_model>(&self, model: Option<&P>) { @@ -202,9 +277,10 @@ impl Selection { priv_.selected.replace(gtk::INVALID_LIST_POSITION); self.notify("selected"); } - if self.selected_room().is_some() { - priv_.selected_room.replace(None); - self.notify("selected-room"); + if self.selected_item().is_some() { + priv_.selected_item.replace(None); + self.notify("selected-type"); + self.notify("selected-item"); } self.items_changed(0, n_items_before, 0); @@ -221,13 +297,13 @@ impl Selection { return; } - let selected_room = self + let selected_item = self .model() .and_then(|m| m.item(position)) .and_then(|o| o.downcast::().ok()) - .and_then(|r| r.item()) - .and_then(|o| o.downcast::().ok()); - let selected = if selected_room.is_none() { + .and_then(|r| r.item()); + + let selected = if selected_item.is_none() { gtk::INVALID_LIST_POSITION } else { position @@ -238,7 +314,7 @@ impl Selection { } priv_.selected.replace(selected); - priv_.selected_room.replace(selected_room); + priv_.selected_item.replace(selected_item); if old_selected == gtk::INVALID_LIST_POSITION { self.selection_changed(selected, 1); @@ -251,14 +327,15 @@ impl Selection { } self.notify("selected"); - self.notify("selected-room"); + self.notify("selected-item"); + self.notify("selected-type"); } - pub fn set_selected_room(&self, room: Option) { + fn set_selected_item(&self, item: Option) { let priv_ = imp::Selection::from_instance(self); - let selected_room = self.selected_room(); - if selected_room == room { + let selected_item = self.selected_item(); + if selected_item == item { return; } @@ -266,15 +343,14 @@ impl Selection { let mut selected = gtk::INVALID_LIST_POSITION; - if room.is_some() { + if item.is_some() { if let Some(model) = self.model() { for i in 0..model.n_items() { - let r = model + let current_item = model .item(i) .and_then(|o| o.downcast::().ok()) - .and_then(|r| r.item()) - .and_then(|o| o.downcast::().ok()); - if r == room { + .and_then(|r| r.item()); + if current_item == item { selected = i; break; } @@ -282,7 +358,7 @@ impl Selection { } } - priv_.selected_room.replace(room); + priv_.selected_item.replace(item); if old_selected != selected { priv_.selected.replace(selected); @@ -299,7 +375,8 @@ impl Selection { self.notify("selected"); } - self.notify("selected-room"); + self.notify("selected-item"); + self.notify("selected-type"); } fn items_changed_cb(&self, model: &gio::ListModel, position: u32, removed: u32, added: u32) { @@ -308,9 +385,9 @@ impl Selection { let _guard = self.freeze_notify(); let selected = self.selected(); - let selected_room = self.selected_room(); + let selected_item = self.selected_item(); - if selected_room.is_none() || selected < position { + if selected_item.is_none() || selected < position { // unchanged } else if selected != gtk::INVALID_LIST_POSITION && selected >= position + removed { priv_.selected.replace(selected + added - removed); @@ -322,12 +399,11 @@ impl Selection { priv_.selected.replace(gtk::INVALID_LIST_POSITION); self.notify("selected"); } else { - let room = model + let item = model .item(position + i) .and_then(|o| o.downcast::().ok()) - .and_then(|r| r.item()) - .and_then(|o| o.downcast::().ok()); - if room == selected_room { + .and_then(|r| r.item()); + if item == selected_item { // the item moved if selected != position + i { priv_.selected.replace(position + i); diff --git a/src/session/sidebar/sidebar.rs b/src/session/sidebar/sidebar.rs index 40d858dd..4b868516 100644 --- a/src/session/sidebar/sidebar.rs +++ b/src/session/sidebar/sidebar.rs @@ -2,8 +2,9 @@ use adw::subclass::prelude::BinImpl; use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate}; use crate::session::{ + content::ContentType, room::Room, - sidebar::{Category, ItemList, RoomRow, Row, Selection}, + sidebar::{Category, Entry, ItemList, RoomRow, Row, Selection}, RoomList, }; @@ -18,6 +19,7 @@ mod imp { pub struct Sidebar { pub compact: Cell, pub selected_room: RefCell>, + pub selected_type: Cell, #[template_child] pub headerbar: TemplateChild, #[template_child] @@ -68,6 +70,14 @@ mod imp { Room::static_type(), glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, ), + glib::ParamSpec::new_enum( + "selected-type", + "Selected", + "The type of item that is selected", + ContentType::static_type(), + ContentType::default() as i32, + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), ] }); @@ -94,6 +104,7 @@ mod imp { let selected_room = value.get().unwrap(); obj.set_selected_room(selected_room); } + "selected-type" => obj.set_selected_type(value.get().unwrap()), _ => unimplemented!(), } } @@ -102,6 +113,7 @@ mod imp { match pspec.name() { "compact" => self.compact.get().to_value(), "selected-room" => obj.selected_room().to_value(), + "selected-type" => obj.selected_type().to_value(), _ => unimplemented!(), } } @@ -126,6 +138,12 @@ mod imp { row.set_expanded(!row.is_expanded()); } else if row.item().and_then(|o| o.downcast::().ok()).is_some() { model.set_selected(pos); + } else if row + .item() + .and_then(|o| o.downcast::().ok()) + .is_some() + { + model.set_selected(pos); } } } @@ -147,6 +165,23 @@ impl Sidebar { glib::Object::new(&[]).expect("Failed to create Sidebar") } + pub fn selected_type(&self) -> ContentType { + let priv_ = imp::Sidebar::from_instance(self); + priv_.selected_type.get() + } + + fn set_selected_type(&self, selected_type: ContentType) { + let priv_ = imp::Sidebar::from_instance(self); + + if self.selected_type() == selected_type { + return; + } + + priv_.selected_type.set(selected_type); + + self.notify("selected-type"); + } + pub fn selected_room(&self) -> Option { let priv_ = imp::Sidebar::from_instance(self); priv_.selected_room.borrow().clone() @@ -188,7 +223,11 @@ impl Sidebar { .build(); let selection = Selection::new(Some(&filter_model)); - self.bind_property("selected-room", &selection, "selected-room") + self.bind_property("selected-room", &selection, "selected-item") + .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL) + .build(); + + self.bind_property("selected-type", &selection, "selected-type") .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL) .build(); @@ -206,7 +245,6 @@ impl Sidebar { } priv_.selected_room.replace(selected_room); - self.notify("selected-room"); } }