fractal/session/model/room/timeline/
timeline_item.rsuse gtk::{glib, prelude::*, subclass::prelude::*};
use matrix_sdk_ui::timeline::{TimelineItem as SdkTimelineItem, TimelineItemKind};
use ruma::OwnedUserId;
use tracing::error;
use super::{Event, Timeline, VirtualItem};
use crate::session::model::Room;
mod imp {
use std::{
cell::{Cell, OnceCell, RefCell},
marker::PhantomData,
};
use super::*;
#[repr(C)]
pub struct TimelineItemClass {
parent_class: glib::object::ObjectClass,
pub(super) selectable: fn(&super::TimelineItem) -> bool,
pub(super) can_hide_header: fn(&super::TimelineItem) -> bool,
pub(super) event_sender_id: fn(&super::TimelineItem) -> Option<OwnedUserId>,
}
unsafe impl ClassStruct for TimelineItemClass {
type Type = TimelineItem;
}
pub(super) fn timeline_item_selectable(this: &super::TimelineItem) -> bool {
let klass = this.class();
(klass.as_ref().selectable)(this)
}
pub(super) fn timeline_item_can_hide_header(this: &super::TimelineItem) -> bool {
let klass = this.class();
(klass.as_ref().can_hide_header)(this)
}
pub(super) fn timeline_item_event_sender_id(this: &super::TimelineItem) -> Option<OwnedUserId> {
let klass = this.class();
(klass.as_ref().event_sender_id)(this)
}
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::TimelineItem)]
pub struct TimelineItem {
#[property(get, construct_only)]
timeline: OnceCell<Timeline>,
#[property(get, construct_only)]
timeline_id: RefCell<String>,
#[property(get = Self::selectable)]
selectable: PhantomData<bool>,
#[property(get, set = Self::set_show_header, explicit_notify)]
show_header: Cell<bool>,
#[property(get = Self::can_hide_header)]
can_hide_header: PhantomData<bool>,
#[property(get = Self::event_sender_id)]
event_sender_id: PhantomData<Option<String>>,
}
#[glib::object_subclass]
impl ObjectSubclass for TimelineItem {
const NAME: &'static str = "TimelineItem";
const ABSTRACT: bool = true;
type Type = super::TimelineItem;
type Class = TimelineItemClass;
}
#[glib::derived_properties]
impl ObjectImpl for TimelineItem {}
impl TimelineItem {
fn selectable(&self) -> bool {
imp::timeline_item_selectable(&self.obj())
}
fn set_show_header(&self, show: bool) {
if self.show_header.get() == show {
return;
}
self.show_header.set(show);
self.obj().notify_show_header();
}
fn can_hide_header(&self) -> bool {
imp::timeline_item_can_hide_header(&self.obj())
}
fn event_sender_id(&self) -> Option<String> {
imp::timeline_item_event_sender_id(&self.obj()).map(Into::into)
}
}
}
glib::wrapper! {
pub struct TimelineItem(ObjectSubclass<imp::TimelineItem>);
}
impl TimelineItem {
pub fn new(item: &SdkTimelineItem, timeline: &Timeline) -> Self {
let timeline_id = &item.unique_id().0;
match item.kind() {
TimelineItemKind::Event(event_item) => {
Event::new(timeline, event_item.clone(), timeline_id).upcast()
}
TimelineItemKind::Virtual(virtual_item) => {
VirtualItem::with_item(timeline, virtual_item, timeline_id).upcast()
}
}
}
pub(crate) fn update_with(&self, item: &SdkTimelineItem) {
if self.timeline_id() != item.unique_id().0 {
error!("Should not update an item with a different timeline ID");
}
match item.kind() {
TimelineItemKind::Event(new_event) => {
if let Some(event) = self.downcast_ref::<Event>() {
event.update_with(new_event.clone());
} else {
error!("Could not update a TimelineItem that is not an Event with an event SDK item");
}
}
TimelineItemKind::Virtual(new_item) => {
if let Some(virtual_item) = self.downcast_ref::<VirtualItem>() {
virtual_item.update_with_item(new_item);
} else {
error!("Could not update a TimelineItem that is not a VirtualItem with a virtual SDK item");
}
}
}
}
}
#[allow(dead_code)]
pub(crate) trait TimelineItemExt: 'static {
fn timeline(&self) -> Timeline;
fn room(&self) -> Room {
self.timeline().room()
}
fn timeline_id(&self) -> String;
fn selectable(&self) -> bool;
fn show_header(&self) -> bool;
fn set_show_header(&self, show: bool);
fn can_hide_header(&self) -> bool;
fn event_sender_id(&self) -> Option<OwnedUserId>;
}
impl<O: IsA<TimelineItem>> TimelineItemExt for O {
fn timeline(&self) -> Timeline {
self.upcast_ref().timeline()
}
fn timeline_id(&self) -> String {
self.upcast_ref().timeline_id()
}
fn selectable(&self) -> bool {
self.upcast_ref().selectable()
}
fn show_header(&self) -> bool {
self.upcast_ref().show_header()
}
fn set_show_header(&self, show: bool) {
self.upcast_ref().set_show_header(show);
}
fn can_hide_header(&self) -> bool {
self.upcast_ref().can_hide_header()
}
fn event_sender_id(&self) -> Option<OwnedUserId> {
imp::timeline_item_event_sender_id(self.upcast_ref())
}
}
pub(crate) trait TimelineItemImpl: ObjectImpl {
fn selectable(&self) -> bool {
false
}
fn can_hide_header(&self) -> bool {
false
}
fn event_sender_id(&self) -> Option<OwnedUserId> {
None
}
}
unsafe impl<T> IsSubclassable<T> for TimelineItem
where
T: TimelineItemImpl,
T::Type: IsA<TimelineItem>,
{
fn class_init(class: &mut glib::Class<Self>) {
Self::parent_class_init::<T>(class.upcast_ref_mut());
let klass = class.as_mut();
klass.selectable = selectable_trampoline::<T>;
klass.can_hide_header = can_hide_header_trampoline::<T>;
klass.event_sender_id = event_sender_id_trampoline::<T>;
}
}
fn selectable_trampoline<T>(this: &TimelineItem) -> bool
where
T: ObjectSubclass + TimelineItemImpl,
T::Type: IsA<TimelineItem>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
this.imp().selectable()
}
fn can_hide_header_trampoline<T>(this: &TimelineItem) -> bool
where
T: ObjectSubclass + TimelineItemImpl,
T::Type: IsA<TimelineItem>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
this.imp().can_hide_header()
}
fn event_sender_id_trampoline<T>(this: &TimelineItem) -> Option<OwnedUserId>
where
T: ObjectSubclass + TimelineItemImpl,
T::Type: IsA<TimelineItem>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
this.imp().event_sender_id()
}