fractal/session/model/room/timeline/
virtual_item.rs

1use gtk::{glib, glib::closure_local, prelude::*, subclass::prelude::*};
2use matrix_sdk_ui::timeline::VirtualTimelineItem;
3
4use super::{Timeline, TimelineItem, TimelineItemImpl};
5use crate::utils::matrix::timestamp_to_date;
6
7/// The kind of virtual item.
8#[derive(Debug, Default, Clone, Eq, PartialEq)]
9pub(crate) enum VirtualItemKind {
10    /// A spinner, when the timeline is loading.
11    #[default]
12    Spinner,
13    /// The typing status.
14    Typing,
15    /// The start of the timeline.
16    TimelineStart,
17    /// A day separator.
18    ///
19    /// The date is in UTC.
20    DayDivider(glib::DateTime),
21    /// A separator for the read marker.
22    NewMessages,
23}
24
25impl VirtualItemKind {
26    /// Construct a `VirtualItemKind` from the given item.
27    fn with_item(item: &VirtualTimelineItem) -> Self {
28        match item {
29            VirtualTimelineItem::DateDivider(ts) => Self::DayDivider(timestamp_to_date(*ts)),
30            VirtualTimelineItem::ReadMarker => Self::NewMessages,
31            VirtualTimelineItem::TimelineStart => Self::TimelineStart,
32        }
33    }
34}
35
36mod imp {
37    use std::{cell::RefCell, sync::LazyLock};
38
39    use glib::subclass::Signal;
40
41    use super::*;
42
43    #[derive(Debug, Default)]
44    pub struct VirtualItem {
45        /// The kind of virtual item.
46        kind: RefCell<VirtualItemKind>,
47    }
48
49    #[glib::object_subclass]
50    impl ObjectSubclass for VirtualItem {
51        const NAME: &'static str = "TimelineVirtualItem";
52        type Type = super::VirtualItem;
53        type ParentType = TimelineItem;
54    }
55
56    impl ObjectImpl for VirtualItem {
57        fn signals() -> &'static [Signal] {
58            static SIGNALS: LazyLock<Vec<Signal>> =
59                LazyLock::new(|| vec![Signal::builder("kind-changed").build()]);
60            SIGNALS.as_ref()
61        }
62    }
63
64    impl TimelineItemImpl for VirtualItem {}
65
66    impl VirtualItem {
67        /// Set the kind of virtual item.
68        pub(super) fn set_kind(&self, kind: VirtualItemKind) {
69            self.kind.replace(kind);
70            self.obj().emit_by_name::<()>("kind-changed", &[]);
71        }
72
73        /// The kind of virtual item.
74        pub(super) fn kind(&self) -> VirtualItemKind {
75            self.kind.borrow().clone()
76        }
77    }
78}
79
80glib::wrapper! {
81    /// A virtual item in the timeline.
82    ///
83    /// A virtual item is an item not based on a timeline event.
84    pub struct VirtualItem(ObjectSubclass<imp::VirtualItem>) @extends TimelineItem;
85}
86
87impl VirtualItem {
88    /// Create a new `VirtualItem`.
89    fn new(timeline: &Timeline, kind: VirtualItemKind, timeline_id: &str) -> Self {
90        let obj = glib::Object::builder::<Self>()
91            .property("timeline", timeline)
92            .property("timeline-id", timeline_id)
93            .build();
94        obj.imp().set_kind(kind);
95        obj
96    }
97
98    /// Create a new `VirtualItem` from a virtual timeline item.
99    pub(crate) fn with_item(
100        timeline: &Timeline,
101        item: &VirtualTimelineItem,
102        timeline_id: &str,
103    ) -> Self {
104        let kind = VirtualItemKind::with_item(item);
105        Self::new(timeline, kind, timeline_id)
106    }
107
108    /// The kind of virtual item.
109    pub(crate) fn kind(&self) -> VirtualItemKind {
110        self.imp().kind()
111    }
112
113    /// Update this `VirtualItem` with the given virtual timeline item.
114    pub(crate) fn update_with_item(&self, item: &VirtualTimelineItem) {
115        let kind = VirtualItemKind::with_item(item);
116        self.imp().set_kind(kind);
117    }
118
119    /// Create a spinner virtual item.
120    pub(crate) fn spinner(timeline: &Timeline) -> Self {
121        Self::new(
122            timeline,
123            VirtualItemKind::Spinner,
124            "VirtualItemKind::Spinner",
125        )
126    }
127
128    /// Create a typing virtual item.
129    pub(crate) fn typing(timeline: &Timeline) -> Self {
130        Self::new(timeline, VirtualItemKind::Typing, "VirtualItemKind::Typing")
131    }
132
133    /// Connect to the signal emitted when the kind changed.
134    pub fn connect_kind_changed<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
135        self.connect_closure(
136            "kind-changed",
137            true,
138            closure_local!(move |obj: Self| {
139                f(&obj);
140            }),
141        )
142    }
143}