use crate::screens::annotation::{AnnotatedRows, AnnotatedRowsView};
use crate::view::selection::{Direction, scroll_into_view, step_clamped};
use crate::theme::Theme;
use crate::view::wrap::as_u16;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Position, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::widgets::Widget;
pub(crate) const MOUSE_SCROLL_LINES: usize = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Pane {
Nav,
Document,
}
pub(crate) fn body_and_footer(area: Rect) -> (Rect, Rect) {
let [body, footer] = Layout::vertical([Constraint::Min(0), Constraint::Length(1)]).areas(area);
(body, footer)
}
pub(crate) fn focused_title(focused: bool, theme: &Theme) -> Style {
let color = if focused { theme.accent } else { theme.text_primary };
Style::new().fg(color).add_modifier(Modifier::BOLD)
}
pub(crate) fn focused_border(focused: bool, theme: &Theme) -> Style {
Style::new().fg(if focused { theme.accent } else { theme.muted })
}
pub(crate) struct DocumentPane<A> {
rows: AnnotatedRows<A>,
pub(crate) cursor: A,
scroll: usize,
area: Rect,
}
impl<A: Default> Default for DocumentPane<A> {
fn default() -> Self {
Self { rows: AnnotatedRows::default(), cursor: A::default(), scroll: 0, area: Rect::ZERO }
}
}
impl<A: Copy + Default + PartialEq> DocumentPane<A> {
pub(crate) fn render_rows(
&mut self,
rows: AnnotatedRows<A>,
area: Rect,
buf: &mut Buffer,
theme: &Theme,
mark_cursor: bool,
) {
self.rows = rows;
self.area = area;
let cursor = self.rows.row_of(self.cursor);
self.scroll = cursor.map_or(self.scroll, |row| scroll_into_view(self.scroll, row, usize::from(area.height)));
AnnotatedRowsView::new(&self.rows, self.scroll, mark_cursor.then_some(cursor).flatten(), theme)
.render(area, buf);
}
pub(crate) fn draft_cursor_position(&self) -> Option<Position> {
let (draft_row, draft_col) = self.rows.draft_cursor()?;
if draft_row < self.scroll || draft_row >= self.scroll + usize::from(self.area.height) {
return None;
}
let row = self.area.y + as_u16(draft_row - self.scroll);
let column = (self.area.x + draft_col).min(self.area.right().saturating_sub(1));
Some(Position::new(column, row))
}
pub(crate) fn scroll_by(&mut self, direction: Direction, amount: usize) {
let last_row = self.rows.len().saturating_sub(1);
self.scroll = step_clamped(self.scroll, direction, amount, last_row);
self.follow_row(self.scroll);
}
pub(crate) fn follow_cursor(&mut self) {
if let Some(row) = self.rows.row_of(self.cursor) {
self.scroll = scroll_into_view(self.scroll, row, usize::from(self.area.height));
}
}
pub(crate) fn contains(&self, position: Position) -> bool {
self.area.contains(position)
}
pub(crate) fn focus_at(&mut self, row: u16) {
let clicked = self.scroll + usize::from(row.saturating_sub(self.area.y));
self.follow_row(clicked);
}
fn follow_row(&mut self, row: usize) {
if let Some(anchor) = self.rows.anchor_at_or_above(row) {
self.cursor = anchor;
}
}
}