|
|
|
|
@ -54,6 +54,10 @@ use crate::{
|
|
|
|
|
const SCROLL_TIMEOUT: Duration = Duration::from_millis(500); |
|
|
|
|
/// The time to wait before considering that messages on a screen where read.
|
|
|
|
|
const READ_TIMEOUT: Duration = Duration::from_secs(5); |
|
|
|
|
/// The time to keep the jump highlight on a message.
|
|
|
|
|
const JUMP_HIGHLIGHT_DURATION: Duration = Duration::from_millis(1200); |
|
|
|
|
/// The CSS class used for jump highlighting.
|
|
|
|
|
const JUMP_HIGHLIGHT_CLASS: &str = "jump-highlight"; |
|
|
|
|
|
|
|
|
|
mod imp { |
|
|
|
|
use std::{ |
|
|
|
|
@ -120,6 +124,8 @@ mod imp {
|
|
|
|
|
grouping_model: OnceCell<GroupingListModel>, |
|
|
|
|
scroll_timeout: RefCell<Option<glib::SourceId>>, |
|
|
|
|
read_timeout: RefCell<Option<glib::SourceId>>, |
|
|
|
|
jump_highlight_timeout: RefCell<Option<glib::SourceId>>, |
|
|
|
|
jump_highlight_key: RefCell<Option<TimelineEventItemId>>, |
|
|
|
|
room_handler: RefCell<Option<glib::SignalHandlerId>>, |
|
|
|
|
permissions_handlers: RefCell<Vec<glib::SignalHandlerId>>, |
|
|
|
|
membership_handler: RefCell<Option<glib::SignalHandlerId>>, |
|
|
|
|
@ -611,6 +617,16 @@ mod imp {
|
|
|
|
|
if let Some(event) = item.downcast_ref::<Event>() { |
|
|
|
|
let child = list_item.child_or_else::<EventRow>(|| EventRow::new(&self.obj())); |
|
|
|
|
child.set_event(Some(event.clone())); |
|
|
|
|
if self |
|
|
|
|
.jump_highlight_key |
|
|
|
|
.borrow() |
|
|
|
|
.as_ref() |
|
|
|
|
.is_some_and(|key| event.matches_identifier(key)) |
|
|
|
|
{ |
|
|
|
|
child.add_css_class(JUMP_HIGHLIGHT_CLASS); |
|
|
|
|
} else { |
|
|
|
|
child.remove_css_class(JUMP_HIGHLIGHT_CLASS); |
|
|
|
|
} |
|
|
|
|
} else if let Some(virtual_item) = item.downcast_ref::<VirtualItem>() { |
|
|
|
|
set_virtual_item_child(list_item, virtual_item); |
|
|
|
|
} else if let Some(group) = item.downcast_ref::<GroupingListGroup>() { |
|
|
|
|
@ -836,11 +852,78 @@ mod imp {
|
|
|
|
|
|
|
|
|
|
if let Some(pos) = timeline.find_event_position(key) { |
|
|
|
|
let pos = pos as u32; |
|
|
|
|
self.set_is_auto_scrolling(false); |
|
|
|
|
self.set_sticky(false); |
|
|
|
|
self.listview |
|
|
|
|
.scroll_to(pos, gtk::ListScrollFlags::FOCUS, None); |
|
|
|
|
self.schedule_jump_highlight(key.clone()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn schedule_jump_highlight(&self, key: TimelineEventItemId) { |
|
|
|
|
glib::idle_add_local_once(clone!( |
|
|
|
|
#[weak(rename_to = imp)] |
|
|
|
|
self, |
|
|
|
|
move || { |
|
|
|
|
imp.highlight_event_row(&key); |
|
|
|
|
} |
|
|
|
|
)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn highlight_event_row(&self, key: &TimelineEventItemId) { |
|
|
|
|
self.clear_jump_highlight(); |
|
|
|
|
|
|
|
|
|
let Some(row) = self.find_event_row(key) else { |
|
|
|
|
return; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
row.add_css_class(JUMP_HIGHLIGHT_CLASS); |
|
|
|
|
self.jump_highlight_key.replace(Some(key.clone())); |
|
|
|
|
|
|
|
|
|
let timeout_id = glib::timeout_add_local_once(JUMP_HIGHLIGHT_DURATION, clone!( |
|
|
|
|
#[weak(rename_to = imp)] |
|
|
|
|
self, |
|
|
|
|
move || { |
|
|
|
|
imp.clear_jump_highlight(); |
|
|
|
|
} |
|
|
|
|
)); |
|
|
|
|
self.jump_highlight_timeout.replace(Some(timeout_id)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn clear_jump_highlight(&self) { |
|
|
|
|
if let Some(timeout_id) = self.jump_highlight_timeout.take() { |
|
|
|
|
timeout_id.remove(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let Some(key) = self.jump_highlight_key.take() else { |
|
|
|
|
return; |
|
|
|
|
}; |
|
|
|
|
if let Some(row) = self.find_event_row(&key) { |
|
|
|
|
row.remove_css_class(JUMP_HIGHLIGHT_CLASS); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn find_event_row(&self, key: &TimelineEventItemId) -> Option<EventRow> { |
|
|
|
|
let listview = &*self.listview; |
|
|
|
|
let mut child = listview.first_child(); |
|
|
|
|
|
|
|
|
|
while let Some(item) = child { |
|
|
|
|
if let Some(event_row) = item |
|
|
|
|
.first_child() |
|
|
|
|
.and_downcast::<EventRow>() |
|
|
|
|
&& event_row |
|
|
|
|
.event() |
|
|
|
|
.is_some_and(|event| event.matches_identifier(key)) |
|
|
|
|
{ |
|
|
|
|
return Some(event_row); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
child = item.next_sibling(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
None |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// The ancestor window of the room history.
|
|
|
|
|
fn parent_window(&self) -> Option<Window> { |
|
|
|
|
self.obj().root().and_downcast() |
|
|
|
|
|