use std::collections::{HashMap, HashSet};
use ratatui::layout::Rect;
use crate::tui::media::ImagePreviewKey;
#[derive(Clone, Default)]
pub(super) struct FramePlacements {
previews: HashMap<ImagePreviewKey, Rect>,
avatars: HashMap<(String, isize), (isize, u16, u16)>,
popup_avatar: Option<(String, bool, Rect)>,
}
#[derive(Default)]
pub(super) struct PlacementDiff {
pub(super) need_clear: bool,
pub(super) unchanged_previews: HashSet<ImagePreviewKey>,
pub(super) unchanged_avatars: HashSet<(String, isize)>,
pub(super) popup_avatar_unchanged: bool,
}
impl FramePlacements {
pub(super) fn insert_preview(&mut self, key: ImagePreviewKey, area: Rect) {
self.previews.insert(key, area);
}
pub(super) fn insert_avatar(
&mut self,
url: String,
row: isize,
fingerprint: (isize, u16, u16),
) {
self.avatars.insert((url, row), fingerprint);
}
pub(super) fn set_popup_avatar(&mut self, popup: Option<(String, bool, Rect)>) {
self.popup_avatar = popup;
}
pub(super) fn diff(&self, previous: &FramePlacements) -> PlacementDiff {
let mut diff = PlacementDiff::default();
for (key, area) in &self.previews {
if previous.previews.get(key) == Some(area) {
diff.unchanged_previews.insert(key.clone());
} else {
diff.need_clear = true;
}
}
for (key, fingerprint) in &self.avatars {
if previous.avatars.get(key) == Some(fingerprint) {
diff.unchanged_avatars.insert(key.clone());
} else {
diff.need_clear = true;
}
}
if previous
.previews
.keys()
.any(|key| !self.previews.contains_key(key))
|| previous
.avatars
.keys()
.any(|key| !self.avatars.contains_key(key))
{
diff.need_clear = true;
}
if self.popup_avatar == previous.popup_avatar {
diff.popup_avatar_unchanged = true;
} else {
diff.need_clear = true;
}
diff
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::discord::ids::{Id, marker::MessageMarker};
use crate::tui::media::ImagePreviewTarget;
fn preview_target(message_id: u64, y_offset: usize) -> ImagePreviewTarget {
ImagePreviewTarget {
viewer: false,
message_index: 0,
preview_index: 0,
preview_x_offset_columns: 0,
preview_y_offset_rows: y_offset,
preview_width: 20,
preview_height: 10,
visible_preview_height: 10,
top_clip_rows: 0,
accent_color: None,
show_play_marker: false,
message_id: Id::<MessageMarker>::new(message_id),
url: "https://cdn.discordapp.com/image.png".to_owned(),
filename: "image.png".to_owned(),
}
}
#[test]
fn member_pane_only_change_keeps_message_preview_unchanged() {
let target = preview_target(1, 0);
let mut previous = FramePlacements::default();
previous.insert_preview(target.key(), Rect::new(10, 5, 20, 10));
previous.insert_avatar("avatar".to_owned(), 4, (4, 3, 0));
let mut current = FramePlacements::default();
current.insert_preview(target.key(), Rect::new(10, 5, 20, 10));
current.insert_avatar("avatar".to_owned(), 3, (3, 3, 0));
let diff = current.diff(&previous);
assert!(diff.need_clear);
assert!(diff.unchanged_previews.contains(&target.key()));
assert!(!diff.unchanged_avatars.contains(&("avatar".to_owned(), 3)));
}
#[test]
fn vertical_scroll_changes_preview_placement() {
let target = preview_target(1, 0);
let mut previous = FramePlacements::default();
previous.insert_preview(target.key(), Rect::new(10, 5, 20, 10));
let mut current = FramePlacements::default();
current.insert_preview(target.key(), Rect::new(10, 3, 20, 10));
let diff = current.diff(&previous);
assert!(diff.need_clear);
assert!(!diff.unchanged_previews.contains(&target.key()));
}
#[test]
fn identical_frame_needs_no_clear() {
let target = preview_target(1, 0);
let mut previous = FramePlacements::default();
previous.insert_preview(target.key(), Rect::new(10, 5, 20, 10));
previous.insert_avatar("avatar".to_owned(), 4, (4, 3, 0));
let mut current = FramePlacements::default();
current.insert_preview(target.key(), Rect::new(10, 5, 20, 10));
current.insert_avatar("avatar".to_owned(), 4, (4, 3, 0));
let diff = current.diff(&previous);
assert!(!diff.need_clear);
assert!(diff.unchanged_previews.contains(&target.key()));
assert!(diff.unchanged_avatars.contains(&("avatar".to_owned(), 4)));
assert!(diff.popup_avatar_unchanged);
}
#[test]
fn removed_preview_forces_clear() {
let target = preview_target(1, 0);
let mut previous = FramePlacements::default();
previous.insert_preview(target.key(), Rect::new(10, 5, 20, 10));
let current = FramePlacements::default();
let diff = current.diff(&previous);
assert!(diff.need_clear);
assert!(diff.unchanged_previews.is_empty());
}
}