[fractal/fractal-next] Reimplement sidebar using TreeListModel and session::room::Room
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] Reimplement sidebar using TreeListModel and session::room::Room
- Date: Fri, 30 Apr 2021 18:58:09 +0000 (UTC)
commit f352fc3cffa3f1ebf534637c67492f8268036ac2
Author: Julian Sparber <julian sparber net>
Date: Wed Apr 28 14:57:33 2021 +0200
Reimplement sidebar using TreeListModel and session::room::Room
data/resources/resources.gresource.xml | 4 +-
data/resources/style.css | 8 +-
data/resources/ui/session.ui | 1 +
data/resources/ui/sidebar-category-item.ui | 52 -----
data/resources/ui/sidebar-category-row.ui | 25 ---
data/resources/ui/sidebar-item.ui | 21 +++
data/resources/ui/sidebar-room-item.ui | 30 ---
data/resources/ui/sidebar.ui | 20 +-
src/meson.build | 6 +-
src/session/mod.rs | 3 +-
src/session/sidebar/category.rs | 293 -----------------------------
src/session/sidebar/category_list.rs | 110 -----------
src/session/sidebar/category_row.rs | 115 -----------
src/session/sidebar/mod.rs | 161 +---------------
src/session/sidebar/room.rs | 194 -------------------
src/session/sidebar/room_row.rs | 235 +++++++++++++----------
src/session/sidebar/row.rs | 130 +++++++++++++
src/session/sidebar/sidebar.rs | 133 +++++++++++++
18 files changed, 436 insertions(+), 1105 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index fd8d12a0..aae78ef5 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -6,9 +6,7 @@
<file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="session.ui">ui/session.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="sidebar.ui">ui/sidebar.ui</file>
- <file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-category-item.ui">ui/sidebar-category-item.ui</file>
- <file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-category-row.ui">ui/sidebar-category-row.ui</file>
- <file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-room-item.ui">ui/sidebar-room-item.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks" alias="sidebar-item.ui">ui/sidebar-item.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="sidebar-room-row.ui">ui/sidebar-room-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="window.ui">ui/window.ui</file>
<file compressed="true">style.css</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 85ea4858..0360bab4 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -21,10 +21,6 @@
}
/* Sidebar */
-.sidebar .navigation-sidebar row {
- padding: 6px 12px;
-}
-
.sidebar row .dim-label {
padding: 6px 12px;
font-size: 0.8em;
@@ -35,8 +31,8 @@
font-weight: bold;
}
-.sidebar .view {
- padding: 0px;
+.sidebar indent {
+ -gtk-icon-size: 0px;
}
.sidebar row .notification_count {
diff --git a/data/resources/ui/session.ui b/data/resources/ui/session.ui
index 33b35007..eb778f0d 100644
--- a/data/resources/ui/session.ui
+++ b/data/resources/ui/session.ui
@@ -6,6 +6,7 @@
<child>
<object class="Sidebar" id="sidebar">
<property name="compact" bind-source="session" bind-property="folded" bind-flags="sync-create" />
+ <property name="categories" bind-source="Session" bind-property="categories"
bind-flags="sync-create" />
</object>
</child>
<child>
diff --git a/data/resources/ui/sidebar-item.ui b/data/resources/ui/sidebar-item.ui
new file mode 100644
index 00000000..8697f3d0
--- /dev/null
+++ b/data/resources/ui/sidebar-item.ui
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="GtkListItem">
+ <property name="child">
+ <!-- TODO: we need to implement our own Expander:
https://gitlab.gnome.org/GNOME/fractal/-/issues/749-->
+ <object class="GtkTreeExpander" id="expander">
+ <binding name="list-row">
+ <lookup name="item">GtkListItem</lookup>
+ </binding>
+ <property name="child">
+ <object class="SidebarRow">
+ <property name="hexpand">True</property>
+ <binding name="item">
+ <lookup name="item">expander</lookup>
+ </binding>
+ </object>
+ </property>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/data/resources/ui/sidebar.ui b/data/resources/ui/sidebar.ui
index 3bf94c9f..ae7e0f49 100644
--- a/data/resources/ui/sidebar.ui
+++ b/data/resources/ui/sidebar.ui
@@ -52,18 +52,14 @@
<property name="vexpand">True</property>
<property name="hscrollbar-policy">never</property>
<property name="child">
- <object class="GtkListView" id="listview">
- <property name="model">
- <object class="GtkNoSelection">
- <property name="model">
- <object class="CategoryList" />
- </property>
- </object>
- </property>
- <property name="factory">
- <object class="GtkBuilderListItemFactory">
- <property name="resource">/org/gnome/FractalNext/sidebar-category-item.ui</property>
- </object>
+ <object class="GtkListView" id="listview">
+ <style>
+ <class name="navigation-sidebar"/>
+ </style>
+ <property name="factory">
+ <object class="GtkBuilderListItemFactory">
+ <property name="resource">/org/gnome/FractalNext/sidebar-item.ui</property>
+ </object>
</property>
<accessibility>
<property name="label" translatable="yes">Sidebar</property>
diff --git a/src/meson.build b/src/meson.build
index 853bf061..1e192571 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -40,11 +40,9 @@ sources = files(
'session/room/room.rs',
'session/room/timeline.rs',
'session/sidebar/mod.rs',
- 'session/sidebar/category_row.rs',
+ 'session/sidebar/row.rs',
'session/sidebar/room_row.rs',
- 'session/sidebar/category.rs',
- 'session/sidebar/category_list.rs',
- 'session/sidebar/room.rs',
+ 'session/sidebar/sidebar.rs',
)
custom_target(
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 6938445d..c19ca8aa 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -298,8 +298,7 @@ impl Session {
/// Note that the `Store` currently doesn't store all events, therefore, we arn't really
/// loading much via this function.
pub fn load(&self) {
- let priv_ = imp::Session::from_instance(self);
- priv_.sidebar.load(&priv_.client.get().unwrap());
+ // TODO: load rooms from the store before the sync completes
}
/// Returns and consumes the `error` that was generated when the session failed to login,
diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs
index f3b83dbf..17e19937 100644
--- a/src/session/sidebar/mod.rs
+++ b/src/session/sidebar/mod.rs
@@ -1,158 +1,7 @@
-mod category;
-mod category_list;
-mod category_row;
-mod room;
mod room_row;
+mod row;
+mod sidebar;
-use self::category::{Category, CategoryName};
-use self::category_list::CategoryList;
-use self::category_row::SidebarCategoryRow;
-use self::room::{HighlightFlags, Room};
-use self::room_row::SidebarRoomRow;
-
-use adw;
-use adw::subclass::prelude::BinImpl;
-use gtk::subclass::prelude::*;
-use gtk::{self, prelude::*};
-use gtk::{glib, glib::clone, glib::SyncSender, CompositeTemplate};
-use matrix_sdk::{identifiers::RoomId, Client};
-
-mod imp {
- use super::*;
- use glib::subclass::InitializingObject;
- use std::cell::Cell;
-
- #[derive(Debug, CompositeTemplate)]
- #[template(resource = "/org/gnome/FractalNext/sidebar.ui")]
- pub struct Sidebar {
- pub compact: Cell<bool>,
- #[template_child]
- pub headerbar: TemplateChild<adw::HeaderBar>,
- #[template_child]
- pub listview: TemplateChild<gtk::ListView>,
- }
-
- #[glib::object_subclass]
- impl ObjectSubclass for Sidebar {
- const NAME: &'static str = "Sidebar";
- type Type = super::Sidebar;
- type ParentType = adw::Bin;
-
- fn new() -> Self {
- Self {
- compact: Cell::new(false),
- listview: TemplateChild::default(),
- headerbar: TemplateChild::default(),
- }
- }
-
- fn class_init(klass: &mut Self::Class) {
- CategoryList::static_type();
- SidebarRoomRow::static_type();
- SidebarCategoryRow::static_type();
- Self::bind_template(klass);
- }
-
- fn instance_init(obj: &InitializingObject<Self>) {
- obj.init_template();
- }
- }
-
- impl ObjectImpl for Sidebar {
- fn properties() -> &'static [glib::ParamSpec] {
- use once_cell::sync::Lazy;
- static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
- vec![glib::ParamSpec::new_boolean(
- "compact",
- "Compact",
- "Wheter a compact view is used or not",
- false,
- glib::ParamFlags::READWRITE,
- )]
- });
-
- PROPERTIES.as_ref()
- }
-
- fn set_property(
- &self,
- _obj: &Self::Type,
- _id: usize,
- value: &glib::Value,
- pspec: &glib::ParamSpec,
- ) {
- match pspec.name() {
- "compact" => {
- let compact = value
- .get()
- .expect("type conformity checked by `Object::set_property`");
- self.compact.set(compact);
- }
- _ => unimplemented!(),
- }
- }
-
- fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
- match pspec.name() {
- "compact" => self.compact.get().to_value(),
- _ => unimplemented!(),
- }
- }
- }
-
- impl WidgetImpl for Sidebar {}
- impl BinImpl for Sidebar {}
-}
-
-glib::wrapper! {
- pub struct Sidebar(ObjectSubclass<imp::Sidebar>)
- @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
-}
-
-impl Sidebar {
- pub fn new() -> Self {
- glib::Object::new(&[]).expect("Failed to create Sidebar")
- }
-
- /// Sets up the required channel to recive async updates from the `Client`
- pub fn setup_channel(&self) -> SyncSender<RoomId> {
- let (sender, receiver) = glib::MainContext::sync_channel::<RoomId>(Default::default(), 100);
-
- receiver.attach(
- None,
- clone!(@weak self as obj => @default-panic, move |room_id| {
- obj.get_list_model().update(&room_id);
- glib::Continue(true)
- }),
- );
- sender
- }
-
- /// Loads the state from the `Store`
- pub fn load(&self, client: &Client) {
- let list = self.get_list_model();
- // TODO: Add list for user defined categories e.g. favorite
- let invited = Category::new(client.clone(), CategoryName::Invited);
- let joined = Category::new(client.clone(), CategoryName::Normal);
- let left = Category::new(client.clone(), CategoryName::Left);
-
- invited.append_batch(client.invited_rooms().into_iter().map(Into::into).collect());
- joined.append_batch(client.joined_rooms().into_iter().map(Into::into).collect());
- left.append_batch(client.left_rooms().into_iter().map(Into::into).collect());
-
- list.append_batch(&[invited, joined, left]);
- }
-
- fn get_list_model(&self) -> CategoryList {
- imp::Sidebar::from_instance(self)
- .listview
- .model()
- .unwrap()
- .downcast::<gtk::NoSelection>()
- .unwrap()
- .model()
- .unwrap()
- .downcast::<CategoryList>()
- .unwrap()
- }
-}
+use self::room_row::RoomRow;
+use self::row::Row;
+pub use self::sidebar::Sidebar;
diff --git a/src/session/sidebar/room_row.rs b/src/session/sidebar/room_row.rs
index ef1dd252..6183564c 100644
--- a/src/session/sidebar/room_row.rs
+++ b/src/session/sidebar/room_row.rs
@@ -1,17 +1,20 @@
-use crate::session::sidebar::HighlightFlags;
-use adw;
use adw::subclass::prelude::BinImpl;
-use gtk::subclass::prelude::*;
-use gtk::{self, prelude::*};
-use gtk::{gio, glib, CompositeTemplate};
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+use crate::session::room::{HighlightFlags, Room};
mod imp {
use super::*;
- use glib::subclass::InitializingObject;
+ use glib::{subclass::InitializingObject, SignalHandlerId};
+ use once_cell::sync::Lazy;
+ use std::cell::RefCell;
- #[derive(Debug, CompositeTemplate)]
+ #[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/FractalNext/sidebar-room-row.ui")]
- pub struct SidebarRoomRow {
+ pub struct RoomRow {
+ pub room: RefCell<Option<Room>>,
+ pub bindings: RefCell<Vec<glib::Binding>>,
+ pub signal_handler: RefCell<Option<SignalHandlerId>>,
#[template_child]
pub avatar: TemplateChild<adw::Avatar>,
#[template_child]
@@ -21,19 +24,11 @@ mod imp {
}
#[glib::object_subclass]
- impl ObjectSubclass for SidebarRoomRow {
+ impl ObjectSubclass for RoomRow {
const NAME: &'static str = "SidebarRoomRow";
- type Type = super::SidebarRoomRow;
+ type Type = super::RoomRow;
type ParentType = adw::Bin;
- fn new() -> Self {
- Self {
- avatar: TemplateChild::default(),
- display_name: TemplateChild::default(),
- notification_count: TemplateChild::default(),
- }
- }
-
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
}
@@ -43,43 +38,16 @@ mod imp {
}
}
- impl ObjectImpl for SidebarRoomRow {
+ impl ObjectImpl for RoomRow {
fn properties() -> &'static [glib::ParamSpec] {
- use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
- vec![
- glib::ParamSpec::new_object(
- "avatar",
- "Avatar",
- "The url of the avatar of this room",
- gio::LoadableIcon::static_type(),
- glib::ParamFlags::WRITABLE,
- ),
- glib::ParamSpec::new_string(
- "display-name",
- "Display Name",
- "The display name of this room",
- None,
- glib::ParamFlags::WRITABLE,
- ),
- glib::ParamSpec::new_flags(
- "highlight",
- "Highlight",
- "What type of highligh this room needs",
- HighlightFlags::static_type(),
- HighlightFlags::default().bits(),
- glib::ParamFlags::WRITABLE,
- ),
- glib::ParamSpec::new_uint64(
- "notification-count",
- "Notification count",
- "The notification count of this room",
- std::u64::MIN,
- std::u64::MAX,
- 0,
- glib::ParamFlags::WRITABLE,
- ),
- ]
+ vec![glib::ParamSpec::new_object(
+ "room",
+ "Room",
+ "The room of this row",
+ Room::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ )]
});
PROPERTIES.as_ref()
@@ -87,75 +55,136 @@ mod imp {
fn set_property(
&self,
- _obj: &Self::Type,
+ obj: &Self::Type,
_id: usize,
value: &glib::Value,
pspec: &glib::ParamSpec,
) {
match pspec.name() {
- "avatar" => {
- let _avatar = value
- .get::<Option<gio::LoadableIcon>>()
- .expect("type conformity checked by `Object::set_property`");
- // TODO: set custom avatar https://gitlab.gnome.org/exalm/libadwaita/-/issues/29
- }
- "display-name" => {
- let display_name = value
- .get()
- .expect("type conformity checked by `Object::set_property`");
- self.display_name.set_label(display_name);
- }
- "highlight" => {
- let flags = value
- .get::<HighlightFlags>()
- .expect("type conformity checked by `Object::set_property`");
- match flags {
- HighlightFlags::NONE => {
- self.notification_count.remove_css_class("highlight");
- self.display_name.remove_css_class("bold");
- }
- HighlightFlags::HIGHLIGHT => {
- self.notification_count.add_css_class("highlight");
- self.display_name.remove_css_class("bold");
- }
- HighlightFlags::BOLD => {
- self.display_name.add_css_class("bold");
- self.notification_count.remove_css_class("highlight");
- }
- HighlightFlags::HIGHLIGHT_BOLD => {
- self.notification_count.add_css_class("highlight");
- self.display_name.add_css_class("bold");
- }
- _ => {}
- }
- }
- "notification-count" => {
- let count = value
- .get::<u64>()
- .expect("type conformity checked by `Object::set_property`");
- self.notification_count.set_label(&count.to_string());
- self.notification_count.set_visible(count > 0);
+ "room" => {
+ let room = value.get().unwrap();
+ obj.set_room(room);
}
_ => unimplemented!(),
}
}
- fn constructed(&self, obj: &Self::Type) {
- self.parent_constructed(obj);
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "room" => obj.room().to_value(),
+ _ => unimplemented!(),
+ }
}
}
- impl WidgetImpl for SidebarRoomRow {}
- impl BinImpl for SidebarRoomRow {}
+ impl WidgetImpl for RoomRow {}
+ impl BinImpl for RoomRow {}
}
glib::wrapper! {
- pub struct SidebarRoomRow(ObjectSubclass<imp::SidebarRoomRow>)
+ pub struct RoomRow(ObjectSubclass<imp::RoomRow>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
-impl SidebarRoomRow {
+impl RoomRow {
pub fn new() -> Self {
- glib::Object::new(&[]).expect("Failed to create SidebarRoomRow")
+ glib::Object::new(&[]).expect("Failed to create RoomRow")
+ }
+
+ pub fn room(&self) -> Option<Room> {
+ let priv_ = imp::RoomRow::from_instance(&self);
+ priv_.room.borrow().clone()
+ }
+
+ pub fn set_room(&self, room: Option<Room>) {
+ let priv_ = imp::RoomRow::from_instance(&self);
+
+ if self.room() == room {
+ return;
+ }
+
+ if let Some(room) = priv_.room.take() {
+ if let Some(id) = priv_.signal_handler.take() {
+ room.disconnect(id);
+ }
+ }
+
+ let mut bindings = priv_.bindings.borrow_mut();
+ while let Some(binding) = bindings.pop() {
+ binding.unbind();
+ }
+
+ if let Some(ref room) = room {
+ // TODO: set custom avatar https://gitlab.gnome.org/exalm/libadwaita/-/issues/29
+
+ let display_name_binding = room
+ .bind_property("display-name", &priv_.display_name.get(), "label")
+ .flags(glib::BindingFlags::SYNC_CREATE)
+ .build()
+ .unwrap();
+
+ let notification_count_binding = room
+ .bind_property(
+ "notification-count",
+ &priv_.notification_count.get(),
+ "label",
+ )
+ .flags(glib::BindingFlags::SYNC_CREATE)
+ .build()
+ .unwrap();
+ let notification_count_vislbe_binding = room
+ .bind_property(
+ "notification-count",
+ &priv_.notification_count.get(),
+ "visible",
+ )
+ .flags(glib::BindingFlags::SYNC_CREATE)
+ .transform_from(|_, value| Some((value.get::<u64>().unwrap() > 0).to_value()))
+ .build()
+ .unwrap();
+
+ priv_.signal_handler.replace(Some(room.connect_notify_local(
+ Some("highlight"),
+ clone!(@weak self as obj => move |_, _| {
+ obj.set_highlight();
+ }),
+ )));
+
+ self.set_highlight();
+
+ bindings.append(&mut vec![
+ display_name_binding,
+ notification_count_binding,
+ notification_count_vislbe_binding,
+ ]);
+ }
+
+ priv_.room.replace(room);
+ self.notify("room");
+ }
+
+ fn set_highlight(&self) {
+ let priv_ = imp::RoomRow::from_instance(&self);
+ if let Some(room) = &*priv_.room.borrow() {
+ match room.highlight() {
+ HighlightFlags::NONE => {
+ priv_.notification_count.remove_css_class("highlight");
+ priv_.display_name.remove_css_class("bold");
+ }
+ HighlightFlags::HIGHLIGHT => {
+ priv_.notification_count.add_css_class("highlight");
+ priv_.display_name.remove_css_class("bold");
+ }
+ HighlightFlags::BOLD => {
+ priv_.display_name.add_css_class("bold");
+ priv_.notification_count.remove_css_class("highlight");
+ }
+ HighlightFlags::HIGHLIGHT_BOLD => {
+ priv_.notification_count.add_css_class("highlight");
+ priv_.display_name.add_css_class("bold");
+ }
+ _ => {}
+ };
+ }
}
}
diff --git a/src/session/sidebar/row.rs b/src/session/sidebar/row.rs
new file mode 100644
index 00000000..d0845c4c
--- /dev/null
+++ b/src/session/sidebar/row.rs
@@ -0,0 +1,130 @@
+use adw::{subclass::prelude::BinImpl, BinExt};
+use gtk::{glib, prelude::*, subclass::prelude::*};
+
+use crate::session::sidebar::RoomRow;
+use crate::session::{categories::Category, room::Room};
+
+mod imp {
+ use super::*;
+ use once_cell::sync::Lazy;
+ use std::cell::RefCell;
+
+ #[derive(Debug, Default)]
+ pub struct Row {
+ pub item: RefCell<Option<glib::Object>>,
+ pub binding: RefCell<Option<glib::Binding>>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for Row {
+ const NAME: &'static str = "SidebarRow";
+ type Type = super::Row;
+ type ParentType = adw::Bin;
+ }
+
+ impl ObjectImpl for Row {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpec::new_object(
+ "item",
+ "Item",
+ "The sidebar item of this row",
+ glib::Object::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() {
+ "item" => {
+ let item = value.get().unwrap();
+ obj.set_item(item);
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "item" => obj.item().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ }
+
+ impl WidgetImpl for Row {}
+ impl BinImpl for Row {}
+}
+
+glib::wrapper! {
+ pub struct Row(ObjectSubclass<imp::Row>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl Row {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create Row")
+ }
+
+ pub fn item(&self) -> Option<glib::Object> {
+ let priv_ = imp::Row::from_instance(&self);
+ priv_.item.borrow().clone()
+ }
+
+ pub fn set_item(&self, item: Option<glib::Object>) {
+ let priv_ = imp::Row::from_instance(&self);
+
+ if self.item() == item {
+ return;
+ }
+
+ if let Some(binding) = priv_.binding.take() {
+ binding.unbind();
+ }
+
+ if let Some(item) = item {
+ if let Some(category) = item.downcast_ref::<Category>() {
+ let child =
+ if let Some(Ok(child)) = self.child().map(|w| w.downcast::<gtk::Label>()) {
+ child
+ } else {
+ let child = gtk::Label::new(None);
+ self.set_child(Some(&child));
+ self.set_halign(gtk::Align::Start);
+ child.add_css_class("dim-label");
+ child
+ };
+
+ let binding = category
+ .bind_property("display-name", &child, "label")
+ .flags(glib::BindingFlags::SYNC_CREATE)
+ .build()
+ .unwrap();
+
+ priv_.binding.replace(Some(binding));
+ } else if let Some(room) = item.downcast_ref::<Room>() {
+ let child = if let Some(Ok(child)) = self.child().map(|w| w.downcast::<RoomRow>()) {
+ child
+ } else {
+ let child = RoomRow::new();
+ self.set_child(Some(&child));
+ child
+ };
+
+ child.set_room(Some(room.clone()));
+ } else {
+ panic!("Wrong row item: {:?}", item);
+ }
+ }
+ self.notify("item");
+ }
+}
diff --git a/src/session/sidebar/sidebar.rs b/src/session/sidebar/sidebar.rs
new file mode 100644
index 00000000..5ddf2a9d
--- /dev/null
+++ b/src/session/sidebar/sidebar.rs
@@ -0,0 +1,133 @@
+use adw::subclass::prelude::BinImpl;
+use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+use crate::session::{
+ categories::Categories,
+ room::Room,
+ sidebar::{RoomRow, Row},
+};
+
+mod imp {
+ use super::*;
+ use glib::subclass::InitializingObject;
+ use once_cell::sync::Lazy;
+ use std::cell::Cell;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/sidebar.ui")]
+ pub struct Sidebar {
+ pub compact: Cell<bool>,
+ #[template_child]
+ pub headerbar: TemplateChild<adw::HeaderBar>,
+ #[template_child]
+ pub listview: TemplateChild<gtk::ListView>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for Sidebar {
+ const NAME: &'static str = "Sidebar";
+ type Type = super::Sidebar;
+ type ParentType = adw::Bin;
+
+ fn class_init(klass: &mut Self::Class) {
+ RoomRow::static_type();
+ Row::static_type();
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for Sidebar {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpec::new_boolean(
+ "compact",
+ "Compact",
+ "Wheter a compact view is used or not",
+ false,
+ glib::ParamFlags::READWRITE,
+ ),
+ glib::ParamSpec::new_object(
+ "categories",
+ "Categories",
+ "A list of rooms grouped into categories",
+ Categories::static_type(),
+ glib::ParamFlags::WRITABLE,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "compact" => {
+ let compact = value.get().unwrap();
+ self.compact.set(compact);
+ }
+ "categories" => {
+ let categories = value.get().unwrap();
+ obj.set_categories(categories);
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "compact" => self.compact.get().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ }
+
+ impl WidgetImpl for Sidebar {}
+ impl BinImpl for Sidebar {}
+}
+
+glib::wrapper! {
+ pub struct Sidebar(ObjectSubclass<imp::Sidebar>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl Sidebar {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create Sidebar")
+ }
+
+ pub fn set_categories(&self, categories: Option<Categories>) {
+ let priv_ = imp::Sidebar::from_instance(self);
+
+ if let Some(categories) = categories {
+ // TODO: hide empty categories
+ let tree_model = gtk::TreeListModel::new(&categories, false, true, |item| {
+ item.clone().downcast::<gio::ListModel>().ok()
+ });
+
+ // TODO: set filter based on the text in the search entry
+ let filter_model = gtk::FilterListModel::new(Some(&tree_model), gtk::NONE_FILTER);
+
+ let selection = gtk::SingleSelection::new(Some(&filter_model));
+ selection.connect_notify_local(Some("selected-item"), clone!(@weak self as obj => move |model,
_| {
+ if let Some(room) = model.selected_item().and_then(|row|
row.downcast_ref::<gtk::TreeListRow>().unwrap().item()).and_then(|o| o.downcast::<Room>().ok()) {
+ obj.activate_action("session.show-room",
Some(&room.matrix_room().room_id().as_str().to_variant()));
+ }
+ }));
+
+ priv_.listview.set_model(Some(&selection));
+ } else {
+ priv_.listview.set_model(gtk::NONE_SELECTION_MODEL);
+ }
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]