fix: left-align thumbnails and tuck text beside the image
This commit is contained in:
+6
-3
@@ -17,7 +17,8 @@
|
||||
//! Esc closes the popup; j/k loads the selected video's description and i returns to editing.
|
||||
//! Normal-mode t toggles a thumbnail in the same-sized popup; j/k follows selection, Esc closes.
|
||||
//! Images use native terminal graphics when supported, otherwise colored half-block characters.
|
||||
//! Thumbnails scale proportionally in the left pane, aligned one column from the text.
|
||||
//! Thumbnails scale proportionally and align to the popup's left inner edge.
|
||||
//! Left-aligned text starts one column after the actual image width, including height-limited images.
|
||||
//! A blank row separates the bold title from the dimmed author · publication date.
|
||||
//! Text retains a two-column right gutter; overflowing excerpts end with … without splitting wide glyphs.
|
||||
//! Both previews share asynchronous full-description fetching and per-video caching.
|
||||
@@ -1031,6 +1032,10 @@ impl App {
|
||||
Constraint::Fill(2),
|
||||
])
|
||||
.areas(block.inner(popup));
|
||||
let image_width = self.thumbnail.draw(frame, image);
|
||||
let reclaimed = image.width - image_width;
|
||||
details.x -= reclaimed;
|
||||
details.width += reclaimed;
|
||||
details.width = details.width.saturating_sub(2);
|
||||
let metadata = Paragraph::new(vec![
|
||||
Line::raw(video.title.as_str()).bold(),
|
||||
@@ -1102,8 +1107,6 @@ impl App {
|
||||
.set_symbol("…")
|
||||
.set_style(Style::new().dim());
|
||||
}
|
||||
// Drawing polls completion and starts resize work before we choose the title state.
|
||||
self.thumbnail.draw(frame, image);
|
||||
frame.render_widget(
|
||||
block.title(loading_title(
|
||||
" Thumbnail ",
|
||||
|
||||
+22
-14
@@ -96,23 +96,25 @@ impl Thumbnail {
|
||||
.is_some_and(|image| image.complete && image.protocol.protocol_type().is_some())
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, frame: &mut Frame, area: Rect) {
|
||||
/// Draw the image left-aligned and return its occupied width for adjacent content.
|
||||
pub fn draw(&mut self, frame: &mut Frame, mut area: Rect) -> u16 {
|
||||
self.poll();
|
||||
if area.width == 0 || area.height == 0 || self.selected.is_none() {
|
||||
return;
|
||||
return area.width;
|
||||
}
|
||||
|
||||
let mut message = self.error.as_deref().unwrap_or("Loading thumbnail...");
|
||||
if let Some(image) = self.image.as_mut() {
|
||||
let resize = Resize::Scale(Some(FilterType::Triangle));
|
||||
if let Some(size) = image.protocol.size_for(resize.clone(), area.into()) {
|
||||
area = left_aligned(area, size);
|
||||
frame.render_stateful_widget(
|
||||
StatefulImage::new().resize(resize),
|
||||
right_aligned(area, size),
|
||||
area,
|
||||
&mut image.protocol,
|
||||
);
|
||||
if image.protocol.protocol_type().is_some() {
|
||||
return;
|
||||
return area.width;
|
||||
}
|
||||
}
|
||||
message = "Preparing thumbnail...";
|
||||
@@ -130,6 +132,7 @@ impl Thumbnail {
|
||||
area.height - area.height / 2,
|
||||
),
|
||||
);
|
||||
area.width
|
||||
}
|
||||
|
||||
fn poll(&mut self) {
|
||||
@@ -235,15 +238,10 @@ fn decode(bytes: Vec<u8>) -> Result<image::DynamicImage, String> {
|
||||
.map_err(|error| format!("Could not decode thumbnail JPEG: {error}"))
|
||||
}
|
||||
|
||||
fn right_aligned(area: Rect, size: Size) -> Rect {
|
||||
fn left_aligned(area: Rect, size: Size) -> Rect {
|
||||
let width = size.width.min(area.width);
|
||||
let height = size.height.min(area.height);
|
||||
Rect::new(
|
||||
area.x + area.width - width,
|
||||
area.y + (area.height - height) / 2,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
Rect::new(area.x, area.y + (area.height - height) / 2, width, height)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -309,7 +307,11 @@ mod tests {
|
||||
});
|
||||
let mut terminal = Terminal::new(TestBackend::new(100, 40)).unwrap();
|
||||
for area in [Rect::new(3, 2, 80, 18), Rect::new(7, 4, 30, 8)] {
|
||||
terminal.draw(|frame| thumbnail.draw(frame, area)).unwrap();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
thumbnail.draw(frame, area);
|
||||
})
|
||||
.unwrap();
|
||||
assert!(thumbnail.is_loading());
|
||||
let response = requests_rx
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
@@ -317,7 +319,12 @@ mod tests {
|
||||
.resize_encode()
|
||||
.unwrap();
|
||||
replies_tx.send(Ok(response)).unwrap();
|
||||
terminal.draw(|frame| thumbnail.draw(frame, area)).unwrap();
|
||||
let mut occupied_width = 0;
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
occupied_width = thumbnail.draw(frame, area);
|
||||
})
|
||||
.unwrap();
|
||||
assert!(!thumbnail.is_loading());
|
||||
let buffer = terminal.backend().buffer();
|
||||
let pixels: Vec<_> = (0..40)
|
||||
@@ -332,7 +339,8 @@ mod tests {
|
||||
let top = pixels.iter().map(|&(_, y)| y).min().unwrap();
|
||||
let bottom = pixels.iter().map(|&(_, y)| y).max().unwrap() + 1;
|
||||
assert!(left >= area.x && right <= area.right());
|
||||
assert_eq!(right, area.right()); // Keep the image against the metadata-facing edge.
|
||||
assert_eq!(left, area.x);
|
||||
assert_eq!(occupied_width, right - left);
|
||||
assert!(top >= area.y && bottom <= area.bottom());
|
||||
assert!(right - left == area.width || bottom - top == area.height);
|
||||
assert!((top - area.y).abs_diff(area.bottom() - bottom) <= 1);
|
||||
|
||||
Reference in New Issue
Block a user