134 lines
4.1 KiB
Rust
134 lines
4.1 KiB
Rust
/*
|
|
* Copyright (C) 2023 Valentin Lorentz
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use ratatui::prelude::*;
|
|
use ratatui::widgets::{Paragraph, Widget};
|
|
|
|
use super::{BottomAlignedParagraph, OverlappableWidget};
|
|
|
|
/// Container of multiple [`BottomAlignedParagraph`]
|
|
#[derive(Debug)]
|
|
pub struct BottomAlignedContainer<'a> {
|
|
/// Pairs of `(padding, content)`
|
|
paragraphs: Vec<(String, BottomAlignedParagraph<'a>)>,
|
|
/// Number of lines at the bottom that should not be rendered
|
|
scroll: u64,
|
|
}
|
|
|
|
impl<'a> BottomAlignedContainer<'a> {
|
|
pub fn new(paragraphs: Vec<(String, BottomAlignedParagraph<'a>)>) -> BottomAlignedContainer<'a> {
|
|
BottomAlignedContainer {
|
|
paragraphs,
|
|
scroll: 0,
|
|
}
|
|
}
|
|
|
|
/// How many lines should be skipped at the bottom
|
|
///
|
|
/// This is like [`Paragraph::scroll`](ratatui::widgets::Paragraph::scroll), but it's only vertical.
|
|
pub fn scroll(mut self, offset: u64) -> BottomAlignedContainer<'a> {
|
|
self.scroll = offset;
|
|
self
|
|
}
|
|
|
|
fn padding_width(padding: &Text<'_>, max_width: u16) -> u16 {
|
|
usize::min(
|
|
(max_width - 10).into(), // TODO: make the minimum content width (10) configurable
|
|
padding.width(),
|
|
) as u16
|
|
}
|
|
}
|
|
|
|
impl<'a> OverlappableWidget for BottomAlignedContainer<'a> {
|
|
fn height(&self, width: u16) -> u64 {
|
|
self
|
|
.paragraphs
|
|
.iter()
|
|
.map(|(padding, paragraph)| {
|
|
paragraph.height(width - Self::padding_width(&Line::raw(padding).into(), width))
|
|
})
|
|
.sum()
|
|
}
|
|
|
|
fn render_overlap(self, mut area: Rect, buf: &mut Buffer) -> (u16, u16) {
|
|
if area.height == 0 {
|
|
// Don't even bother
|
|
return (0, 0);
|
|
}
|
|
let mut scroll = self.scroll;
|
|
|
|
let mut actual_width = 0u16;
|
|
let mut actual_height = 0usize;
|
|
|
|
for (padding, paragraph) in self.paragraphs.into_iter().rev() {
|
|
let padding: Text<'_> = Line::raw(&padding).into();
|
|
let padding_width = Self::padding_width(&padding, area.width);
|
|
assert_eq!(
|
|
padding.height(),
|
|
1,
|
|
"Unexpected padding height: {} (padding={:?})",
|
|
padding.height(),
|
|
padding
|
|
);
|
|
|
|
// FIXME: paragraph.height() is expensive because it needs to run line-wrapping,
|
|
// and paragraph.render_overlap then needs to run it again twice.
|
|
let paragraph_height = paragraph.height(area.width - padding_width);
|
|
if paragraph_height <= scroll {
|
|
// Paragraph is under the viewport, don't render it
|
|
scroll -= paragraph_height;
|
|
} else {
|
|
let paragraph = paragraph.scroll(scroll);
|
|
let (actual_paragraph_width, actual_paragraph_height) = paragraph.render_overlap(
|
|
Rect {
|
|
x: area.x + padding_width,
|
|
width: area.width - padding_width,
|
|
..area
|
|
},
|
|
buf,
|
|
);
|
|
scroll = 0;
|
|
|
|
// Write the padding on each line the paragraph was rendered on
|
|
for y in (u16::max(
|
|
area.top(),
|
|
area.bottom().saturating_sub(actual_paragraph_height),
|
|
))..area.bottom()
|
|
{
|
|
Paragraph::new(padding.clone()).render(
|
|
Rect {
|
|
x: area.x,
|
|
y,
|
|
height: 1,
|
|
width: padding_width,
|
|
},
|
|
buf,
|
|
);
|
|
}
|
|
|
|
area.height = area.height.saturating_sub(actual_paragraph_height);
|
|
actual_height = actual_height.saturating_add(actual_paragraph_height.into());
|
|
actual_width = u16::max(actual_width, padding_width + actual_paragraph_width);
|
|
if area.height == 0 {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
(actual_width, actual_height as u16)
|
|
}
|
|
}
|