Fix image previews in replies (#225)

This commit is contained in:
Ulyssa 2024-03-23 19:41:05 -07:00 committed by GitHub
parent 2ac71da9a6
commit d3b717d1be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 89 additions and 103 deletions

View file

@ -142,6 +142,35 @@ pub fn text_to_message(input: String) -> RoomMessageEventContent {
RoomMessageEventContent::new(msg)
}
/// Before the image is loaded, already display a placeholder frame of the image size.
fn placeholder_frame(
text: Option<&str>,
outer_width: usize,
image_preview_size: &ImagePreviewSize,
) -> Option<String> {
let ImagePreviewSize { width, height } = image_preview_size;
if outer_width < *width || (*width < 2 || *height < 2) {
return None;
}
let mut placeholder = "\u{230c}".to_string();
placeholder.push_str(&" ".repeat(width - 2));
placeholder.push_str("\u{230d}\n");
if *height > 2 {
if let Some(text) = text {
if text.width() <= width - 2 {
placeholder.push(' ');
placeholder.push_str(text);
}
}
}
placeholder.push_str(&"\n".repeat(height - 2));
placeholder.push('\u{230e}');
placeholder.push_str(&" ".repeat(width - 2));
placeholder.push_str("\u{230f}\n");
Some(placeholder)
}
#[inline]
fn millis_to_datetime(ms: UInt) -> DateTime<LocalTz> {
let time = i64::from(ms) / 1000;
@ -531,6 +560,16 @@ enum MessageColumns {
One,
}
impl MessageColumns {
fn user_gutter_width(&self, settings: &ApplicationSettings) -> u16 {
if let MessageColumns::One = self {
0
} else {
settings.tunables.user_gutter_width as u16
}
}
}
struct MessageFormatter<'a> {
settings: &'a ApplicationSettings,
@ -649,7 +688,7 @@ impl<'a> MessageFormatter<'a> {
let width = self.width();
let w = width.saturating_sub(2);
let shortcodes = self.settings.tunables.message_shortcode_display;
let mut replied = msg.show_msg(w, style, true, shortcodes);
let (mut replied, _) = msg.show_msg(w, style, true, shortcodes);
let mut sender = msg.sender_span(info, self.settings);
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
let trailing = w.saturating_sub(sender_width + 1);
@ -881,46 +920,17 @@ impl Message {
}
}
/// Get the image preview Protocol and x,y offset, based on get_render_format.
pub fn line_preview<'a>(
&'a self,
prev: Option<&Message>,
vwctx: &ViewportContext<MessageCursor>,
info: &'a RoomInfo,
settings: &'a ApplicationSettings,
) -> Option<(&dyn Protocol, u16, u16)> {
let width = vwctx.get_width();
let user_gutter = settings.tunables.user_gutter_width;
// The x position where get_render_format would render the text.
let x = (if user_gutter + MIN_MSG_LEN <= width {
user_gutter
} else {
0
}) as u16;
// See get_render_format; account for possible "date" line.
let date_y = match &prev {
Some(prev) if !prev.timestamp.same_day(&self.timestamp) => 1,
_ => 0,
};
if let ImageStatus::Loaded(backend) = &self.image_preview {
return Some((backend.as_ref(), x, date_y));
} else if let Some(reply) = self.reply_to().and_then(|e| info.get_event(&e)) {
if let ImageStatus::Loaded(backend) = &reply.image_preview {
// The reply should be offset a bit:
return Some((backend.as_ref(), x + 2, date_y + 1));
}
}
None
}
pub fn show<'a>(
/// Render the message as a [Text] object for the terminal.
///
/// This will also get the image preview Protocol with an x/y offset.
pub fn show_with_preview<'a>(
&'a self,
prev: Option<&Message>,
selected: bool,
vwctx: &ViewportContext<MessageCursor>,
info: &'a RoomInfo,
settings: &'a ApplicationSettings,
) -> Text<'a> {
) -> (Text<'a>, Option<(&dyn Protocol, u16, u16)>) {
let width = vwctx.get_width();
let style = self.get_render_style(selected, settings);
@ -939,12 +949,20 @@ impl Message {
}
// Now show the message contents, and the inlined reply if we couldn't find it above.
let msg = self.show_msg(
let (msg, proto) = self.show_msg(
width,
style,
reply.is_some(),
settings.tunables.message_shortcode_display,
);
// Given our text so far, determine the image offset.
let proto = proto.map(|p| {
let y_off = text.lines.len() as u16;
let x_off = fmt.cols.user_gutter_width(settings);
(p, x_off, y_off)
});
fmt.push_text(msg, style, &mut text);
if text.lines.is_empty() {
@ -961,18 +979,29 @@ impl Message {
fmt.push_thread_reply_count(thread.len(), &mut text);
}
text
(text, proto)
}
pub fn show_msg(
pub fn show<'a>(
&'a self,
prev: Option<&Message>,
selected: bool,
vwctx: &ViewportContext<MessageCursor>,
info: &'a RoomInfo,
settings: &'a ApplicationSettings,
) -> Text<'a> {
self.show_with_preview(prev, selected, vwctx, info, settings).0
}
fn show_msg(
&self,
width: usize,
style: Style,
hide_reply: bool,
emoji_shortcodes: bool,
) -> Text {
) -> (Text, Option<&dyn Protocol>) {
if let Some(html) = &self.html {
html.to_text(width, style, hide_reply, emoji_shortcodes)
(html.to_text(width, style, hide_reply, emoji_shortcodes), None)
} else {
let mut msg = self.event.body();
if emoji_shortcodes {
@ -983,20 +1012,24 @@ impl Message {
msg.to_mut().push_str(" \u{2705}");
}
if let Some(placeholder) = match &self.image_preview {
let mut proto = None;
let placeholder = match &self.image_preview {
ImageStatus::None => None,
ImageStatus::Downloading(image_preview_size) => {
Message::placeholder_frame(Some("Downloading..."), width, image_preview_size)
placeholder_frame(Some("Downloading..."), width, image_preview_size)
},
ImageStatus::Loaded(backend) => {
Message::placeholder_frame(None, width, &backend.rect().into())
proto = Some(backend.as_ref());
placeholder_frame(None, width, &backend.rect().into())
},
ImageStatus::Error(err) => Some(format!("[Image error: {err}]\n")),
} {
};
if let Some(placeholder) = placeholder {
msg.to_mut().insert_str(0, &placeholder);
}
wrapped_text(msg, width, style)
(wrapped_text(msg, width, style), proto)
}
}
@ -1008,35 +1041,6 @@ impl Message {
settings.get_user_span(self.sender.as_ref(), info)
}
/// Before the image is loaded, already display a placeholder frame of the image size.
fn placeholder_frame(
text: Option<&str>,
outer_width: usize,
image_preview_size: &ImagePreviewSize,
) -> Option<String> {
let ImagePreviewSize { width, height } = image_preview_size;
if outer_width < *width || (*width < 2 || *height < 2) {
return None;
}
let mut placeholder = "\u{230c}".to_string();
placeholder.push_str(&" ".repeat(width - 2));
placeholder.push_str("\u{230d}\n");
if *height > 2 {
if let Some(text) = text {
if text.width() <= width - 2 {
placeholder.push(' ');
placeholder.push_str(text);
}
}
}
placeholder.push_str(&"\n".repeat(height - 2));
placeholder.push('\u{230e}');
placeholder.push_str(&" ".repeat(width - 2));
placeholder.push_str("\u{230f}\n");
Some(placeholder)
}
fn show_sender<'a>(
&'a self,
prev: Option<&Message>,
@ -1287,7 +1291,7 @@ pub mod tests {
}
assert_eq!(
Message::placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 4 }),
placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 4 }),
pretty_frame_test(
r#"
@ -1298,22 +1302,13 @@ pub mod tests {
)
);
assert_eq!(
Message::placeholder_frame(None, 2, &ImagePreviewSize { width: 4, height: 4 }),
None
);
assert_eq!(
Message::placeholder_frame(None, 4, &ImagePreviewSize { width: 1, height: 4 }),
None
);
assert_eq!(placeholder_frame(None, 2, &ImagePreviewSize { width: 4, height: 4 }), None);
assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 1, height: 4 }), None);
assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 1 }), None);
assert_eq!(
Message::placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 1 }),
None
);
assert_eq!(
Message::placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 4 }),
placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 4 }),
pretty_frame_test(
r#"
@ -1324,10 +1319,7 @@ pub mod tests {
)
);
assert_eq!(
Message::placeholder_frame(Some("idontfit"), 4, &ImagePreviewSize {
width: 4,
height: 4,
}),
placeholder_frame(Some("idontfit"), 4, &ImagePreviewSize { width: 4, height: 4 }),
pretty_frame_test(
r#"
@ -1338,7 +1330,7 @@ pub mod tests {
)
);
assert_eq!(
Message::placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 2 }),
placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 2 }),
pretty_frame_test(
r#"
@ -1347,7 +1339,7 @@ pub mod tests {
)
);
assert_eq!(
Message::placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 2, height: 3 }),
placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 2, height: 3 }),
pretty_frame_test(
r#"

View file

@ -1292,7 +1292,6 @@ impl<'a> StatefulWidget for Scrollback<'a> {
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let info = self.store.application.rooms.get_or_default(state.room_id.clone());
let settings = &self.store.application.settings;
let picker = &self.store.application.picker;
let area = if state.cursor.timestamp.is_some() {
render_jump_to_recent(area, buf, self.focused)
} else {
@ -1343,13 +1342,8 @@ impl<'a> StatefulWidget for Scrollback<'a> {
for (key, item) in thread.range(&corner_key..) {
let sel = key == cursor_key;
let txt = item.show(prev, foc && sel, &state.viewctx, info, settings);
let mut msg_preview = if picker.is_some() {
item.line_preview(prev, &state.viewctx, info, settings)
} else {
None
};
let (txt, mut msg_preview) =
item.show_with_preview(prev, foc && sel, &state.viewctx, info, settings);
let incomplete_ok = !full || !sel;