parent
29a98b2e2b
commit
f414a6f5ba
|
@ -56,6 +56,7 @@
|
|||
<style>
|
||||
<class name="navigation-sidebar"/>
|
||||
</style>
|
||||
<property name="single-click-activate">true</property>
|
||||
<property name="factory">
|
||||
<object class="GtkBuilderListItemFactory">
|
||||
<property name="resource">/org/gnome/FractalNext/sidebar-item.ui</property>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
mod category_row;
|
||||
mod room_row;
|
||||
mod row;
|
||||
mod selection;
|
||||
mod sidebar;
|
||||
|
||||
use self::category_row::CategoryRow;
|
||||
use self::room_row::RoomRow;
|
||||
use self::row::Row;
|
||||
use self::selection::Selection;
|
||||
pub use self::sidebar::Sidebar;
|
||||
|
|
|
@ -0,0 +1,341 @@
|
|||
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
|
||||
|
||||
use crate::session::room::Room;
|
||||
|
||||
// FIXME Could not find it in gtk
|
||||
pub const GTK_INVALID_LIST_POSITION: u32 = u32::MAX;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Selection {
|
||||
pub model: RefCell<Option<gio::ListModel>>,
|
||||
pub selected: Cell<u32>,
|
||||
pub selected_room: RefCell<Option<Room>>,
|
||||
pub signal_handler: RefCell<Option<glib::SignalHandlerId>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Selection {
|
||||
const NAME: &'static str = "SidebarSelection";
|
||||
type Type = super::Selection;
|
||||
type ParentType = glib::Object;
|
||||
type Interfaces = (gio::ListModel, gtk::SelectionModel);
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
selected: Cell::new(GTK_INVALID_LIST_POSITION),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for Selection {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpec::new_object(
|
||||
"model",
|
||||
"Model",
|
||||
"The model being managed",
|
||||
gio::ListModel::static_type(),
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
glib::ParamSpec::new_uint(
|
||||
"selected",
|
||||
"Selected",
|
||||
"The position of the selected item",
|
||||
0,
|
||||
u32::MAX,
|
||||
GTK_INVALID_LIST_POSITION,
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
glib::ParamSpec::new_object(
|
||||
"selected-room",
|
||||
"Selected Room",
|
||||
"The selected room",
|
||||
Room::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() {
|
||||
"model" => {
|
||||
let model: Option<gio::ListModel> = value.get().unwrap();
|
||||
obj.set_model(model.as_ref());
|
||||
}
|
||||
"selected" => {
|
||||
let selected = value.get().unwrap();
|
||||
obj.set_selected(selected);
|
||||
}
|
||||
"selected-room" => {
|
||||
let selected_room = value.get().unwrap();
|
||||
obj.set_selected_room(selected_room);
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"model" => obj.model().to_value(),
|
||||
"selected" => obj.selected().to_value(),
|
||||
"selected-room" => obj.selected_room().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListModelImpl for Selection {
|
||||
fn item_type(&self, _list_model: &Self::Type) -> glib::Type {
|
||||
gtk::TreeListRow::static_type()
|
||||
}
|
||||
fn n_items(&self, _list_model: &Self::Type) -> u32 {
|
||||
self.model
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|m| m.n_items())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
|
||||
self.model.borrow().as_ref().and_then(|m| m.item(position))
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectionModelImpl for Selection {
|
||||
fn selection_in_range(
|
||||
&self,
|
||||
_model: &Self::Type,
|
||||
_position: u32,
|
||||
_n_items: u32,
|
||||
) -> gtk::Bitset {
|
||||
let bitset = gtk::Bitset::new_empty();
|
||||
let selected = self.selected.get();
|
||||
|
||||
if selected != GTK_INVALID_LIST_POSITION {
|
||||
bitset.add(selected);
|
||||
}
|
||||
|
||||
bitset
|
||||
}
|
||||
|
||||
fn is_selected(&self, _model: &Self::Type, position: u32) -> bool {
|
||||
self.selected.get() == position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Selection(ObjectSubclass<imp::Selection>)
|
||||
@implements gio::ListModel, gtk::SelectionModel;
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
pub fn new<P: IsA<gio::ListModel>>(model: Option<&P>) -> Selection {
|
||||
let model = model.map(|m| m.clone().upcast::<gio::ListModel>());
|
||||
glib::Object::new(&[("model", &model)]).expect("Failed to create Selection")
|
||||
}
|
||||
|
||||
pub fn model(&self) -> Option<gio::ListModel> {
|
||||
let priv_ = imp::Selection::from_instance(self);
|
||||
priv_.model.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> u32 {
|
||||
let priv_ = imp::Selection::from_instance(self);
|
||||
priv_.selected.get()
|
||||
}
|
||||
|
||||
pub fn selected_room(&self) -> Option<Room> {
|
||||
let priv_ = imp::Selection::from_instance(self);
|
||||
priv_.selected_room.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn set_model<P: IsA<gio::ListModel>>(&self, model: Option<&P>) {
|
||||
let priv_ = imp::Selection::from_instance(self);
|
||||
|
||||
let model = model.map(|m| m.clone().upcast::<gio::ListModel>());
|
||||
|
||||
let old_model = self.model();
|
||||
if old_model == model {
|
||||
return;
|
||||
}
|
||||
|
||||
let n_items_before = old_model
|
||||
.map(|model| {
|
||||
if let Some(id) = priv_.signal_handler.take() {
|
||||
model.disconnect(id);
|
||||
}
|
||||
model.n_items()
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
if let Some(model) = model {
|
||||
priv_
|
||||
.signal_handler
|
||||
.replace(Some(model.connect_items_changed(
|
||||
clone!(@weak self as obj => move |m, p, r, a| {
|
||||
obj.items_changed_cb(m, p, r, a);
|
||||
}),
|
||||
)));
|
||||
|
||||
self.items_changed_cb(&model, 0, n_items_before, model.n_items());
|
||||
|
||||
priv_.model.replace(Some(model));
|
||||
} else {
|
||||
priv_.model.replace(None);
|
||||
|
||||
if self.selected() != GTK_INVALID_LIST_POSITION {
|
||||
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");
|
||||
}
|
||||
|
||||
self.items_changed(0, n_items_before, 0);
|
||||
}
|
||||
|
||||
self.notify("model");
|
||||
}
|
||||
|
||||
pub fn set_selected(&self, position: u32) {
|
||||
let priv_ = imp::Selection::from_instance(self);
|
||||
|
||||
let old_selected = self.selected();
|
||||
if old_selected == position {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected_room = self
|
||||
.model()
|
||||
.and_then(|m| m.item(position))
|
||||
.and_then(|o| o.downcast::<gtk::TreeListRow>().ok())
|
||||
.and_then(|r| r.item())
|
||||
.and_then(|o| o.downcast::<Room>().ok());
|
||||
let selected = if selected_room.is_none() {
|
||||
GTK_INVALID_LIST_POSITION
|
||||
} else {
|
||||
position
|
||||
};
|
||||
|
||||
if old_selected == selected {
|
||||
return;
|
||||
}
|
||||
|
||||
priv_.selected.replace(selected);
|
||||
priv_.selected_room.replace(selected_room);
|
||||
|
||||
if old_selected == GTK_INVALID_LIST_POSITION {
|
||||
self.selection_changed(selected, 1);
|
||||
} else if selected == GTK_INVALID_LIST_POSITION {
|
||||
self.selection_changed(old_selected, 1);
|
||||
} else if selected < old_selected {
|
||||
self.selection_changed(selected, old_selected - selected + 1);
|
||||
} else {
|
||||
self.selection_changed(old_selected, selected - old_selected + 1);
|
||||
}
|
||||
|
||||
self.notify("selected");
|
||||
self.notify("selected-room");
|
||||
}
|
||||
|
||||
pub fn set_selected_room(&self, room: Option<Room>) {
|
||||
let priv_ = imp::Selection::from_instance(self);
|
||||
|
||||
let selected_room = self.selected_room();
|
||||
if selected_room == room {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_selected = self.selected();
|
||||
|
||||
let mut selected = GTK_INVALID_LIST_POSITION;
|
||||
|
||||
if let Some(model) = self.model() {
|
||||
for i in 0..=model.n_items() {
|
||||
let room = model
|
||||
.item(i)
|
||||
.and_then(|o| o.downcast::<gtk::TreeListRow>().ok())
|
||||
.and_then(|r| r.item())
|
||||
.and_then(|o| o.downcast::<Room>().ok());
|
||||
if room == selected_room {
|
||||
selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
priv_.selected_room.replace(selected_room);
|
||||
|
||||
if old_selected != selected {
|
||||
priv_.selected.replace(selected);
|
||||
|
||||
if old_selected == GTK_INVALID_LIST_POSITION {
|
||||
self.selection_changed(selected, 1);
|
||||
} else if selected == GTK_INVALID_LIST_POSITION {
|
||||
self.selection_changed(old_selected, 1);
|
||||
} else if selected < old_selected {
|
||||
self.selection_changed(selected, old_selected - selected + 1);
|
||||
} else {
|
||||
self.selection_changed(old_selected, selected - old_selected + 1);
|
||||
}
|
||||
self.notify("selected");
|
||||
}
|
||||
|
||||
self.notify("selected-room");
|
||||
}
|
||||
|
||||
fn items_changed_cb(&self, model: &gio::ListModel, position: u32, removed: u32, added: u32) {
|
||||
let priv_ = imp::Selection::from_instance(self);
|
||||
|
||||
let selected = self.selected();
|
||||
let selected_room = self.selected_room();
|
||||
|
||||
if selected_room.is_none() || selected < position {
|
||||
// unchanged
|
||||
} else if selected != GTK_INVALID_LIST_POSITION && selected >= position + removed {
|
||||
priv_.selected.replace(selected + added - removed);
|
||||
self.notify("selected");
|
||||
} else {
|
||||
for i in 0..=added {
|
||||
if i == added {
|
||||
// the item really was deleted
|
||||
priv_.selected.replace(GTK_INVALID_LIST_POSITION);
|
||||
self.notify("selected");
|
||||
} else {
|
||||
let room = model
|
||||
.item(position + i)
|
||||
.and_then(|o| o.downcast::<gtk::TreeListRow>().ok())
|
||||
.and_then(|r| r.item())
|
||||
.and_then(|o| o.downcast::<Room>().ok());
|
||||
if room == selected_room {
|
||||
// the item moved
|
||||
if selected != position + i {
|
||||
priv_.selected.replace(position + i);
|
||||
self.notify("selected");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.items_changed(position, removed, added);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
use adw::subclass::prelude::BinImpl;
|
||||
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
|
||||
use crate::session::{
|
||||
categories::{Categories, Category},
|
||||
room::Room,
|
||||
sidebar::{RoomRow, Row},
|
||||
sidebar::{RoomRow, Row, Selection},
|
||||
};
|
||||
|
||||
mod imp {
|
||||
|
@ -110,18 +110,23 @@ mod imp {
|
|||
self.parent_constructed(obj);
|
||||
|
||||
self.listview.get().connect_activate(move |listview, pos| {
|
||||
if let Some(row) = listview
|
||||
if let Some(model) = listview
|
||||
.model()
|
||||
.and_then(|m| m.downcast::<gtk::SingleSelection>().ok())
|
||||
.and_then(|m| m.item(pos))
|
||||
.and_then(|o| o.downcast::<gtk::TreeListRow>().ok())
|
||||
.and_then(|m| m.downcast::<Selection>().ok())
|
||||
{
|
||||
if row
|
||||
.item()
|
||||
.and_then(|o| o.downcast::<Category>().ok())
|
||||
.is_some()
|
||||
if let Some(row) = model
|
||||
.item(pos)
|
||||
.and_then(|o| o.downcast::<gtk::TreeListRow>().ok())
|
||||
{
|
||||
row.set_expanded(!row.is_expanded());
|
||||
if row
|
||||
.item()
|
||||
.and_then(|o| o.downcast::<Category>().ok())
|
||||
.is_some()
|
||||
{
|
||||
row.set_expanded(!row.is_expanded());
|
||||
} else if row.item().and_then(|o| o.downcast::<Room>().ok()).is_some() {
|
||||
model.set_selected(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -183,12 +188,10 @@ impl Sidebar {
|
|||
.flags(glib::BindingFlags::SYNC_CREATE)
|
||||
.build();
|
||||
|
||||
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.set_selected_room(Some(room));
|
||||
}
|
||||
}));
|
||||
let selection = Selection::new(Some(&filter_model));
|
||||
self.bind_property("selected-room", &selection, "selected-room")
|
||||
.flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
|
||||
.build();
|
||||
|
||||
priv_.listview.set_model(Some(&selection));
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue