use crate::grid::{GridLine, GridLineFlags};
use crate::input::{Colour, COLOUR_DEFAULT};
use super::Screen;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScreenCellRef<'a> {
text: &'a str,
width: u8,
padding: bool,
attr: u16,
fg: Colour,
bg: Colour,
us: Colour,
link: u32,
}
impl<'a> ScreenCellRef<'a> {
#[must_use]
pub const fn text(&self) -> &'a str {
self.text
}
#[must_use]
pub const fn width(&self) -> u8 {
self.width
}
#[must_use]
pub const fn is_padding(&self) -> bool {
self.padding
}
#[must_use]
pub const fn attr(&self) -> u16 {
self.attr
}
#[must_use]
pub const fn fg(&self) -> Colour {
self.fg
}
#[must_use]
pub const fn bg(&self) -> Colour {
self.bg
}
#[must_use]
pub const fn us(&self) -> Colour {
self.us
}
#[must_use]
pub const fn link(&self) -> u32 {
self.link
}
}
fn blank_cell_ref() -> ScreenCellRef<'static> {
ScreenCellRef {
text: " ",
width: 1,
padding: false,
attr: 0,
fg: COLOUR_DEFAULT,
bg: COLOUR_DEFAULT,
us: COLOUR_DEFAULT,
link: 0,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScreenCellView {
pub(super) text: String,
pub(super) width: u8,
pub(super) padding: bool,
pub(super) attr: u16,
pub(super) fg: crate::input::Colour,
pub(super) bg: crate::input::Colour,
pub(super) us: crate::input::Colour,
pub(super) link: u32,
}
impl ScreenCellView {
#[must_use]
pub fn text(&self) -> &str {
&self.text
}
#[must_use]
pub const fn width(&self) -> u8 {
self.width
}
#[must_use]
pub const fn is_padding(&self) -> bool {
self.padding
}
#[must_use]
pub const fn attr(&self) -> u16 {
self.attr
}
#[must_use]
pub const fn fg(&self) -> crate::input::Colour {
self.fg
}
#[must_use]
pub const fn bg(&self) -> crate::input::Colour {
self.bg
}
#[must_use]
pub const fn us(&self) -> crate::input::Colour {
self.us
}
#[must_use]
pub const fn link(&self) -> u32 {
self.link
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScreenLineView {
pub(super) cells: Vec<ScreenCellView>,
width: u32,
pub(super) wrapped: bool,
pub(super) start_prompt: bool,
pub(super) start_output: bool,
pub(super) time: i64,
}
impl ScreenLineView {
#[must_use]
pub fn cells(&self) -> &[ScreenCellView] {
&self.cells
}
#[must_use]
pub const fn width(&self) -> u32 {
self.width
}
#[must_use]
pub fn cell(&self, x: u32) -> Option<&ScreenCellView> {
self.cells.get(x as usize)
}
#[must_use]
pub const fn wrapped(&self) -> bool {
self.wrapped
}
#[must_use]
pub const fn start_prompt(&self) -> bool {
self.start_prompt
}
#[must_use]
pub const fn start_output(&self) -> bool {
self.start_output
}
#[must_use]
pub const fn time(&self) -> i64 {
self.time
}
#[must_use]
pub fn owning_cell_x(&self, x: u32) -> Option<u32> {
if x >= self.width {
return None;
}
let Some(cell) = self.cell(x) else {
return Some(x);
};
if !cell.is_padding() {
return Some(x);
}
let mut owner = x;
while owner > 0 {
owner -= 1;
let candidate = self.cell(owner)?;
if !candidate.is_padding() {
let width = u32::from(candidate.width().max(1));
if owner.saturating_add(width) > x {
return Some(owner);
}
return None;
}
}
None
}
}
impl Screen {
pub fn visit_visible_line_cells(
&self,
row: usize,
cols: usize,
mut visit: impl FnMut(ScreenCellRef<'_>),
) -> bool {
let Some(line) = self
.grid
.visible_line(u32::try_from(row).unwrap_or(u32::MAX))
else {
return false;
};
if let Some(text) = line.plain_text() {
let text_cols = text.len().min(cols);
for col in 0..text_cols {
visit(ScreenCellRef {
text: &text[col..col + 1],
width: 1,
padding: false,
attr: 0,
fg: COLOUR_DEFAULT,
bg: COLOUR_DEFAULT,
us: COLOUR_DEFAULT,
link: 0,
});
}
for _ in text_cols..cols {
visit(blank_cell_ref());
}
return true;
}
let mut emitted = 0_usize;
for cell in line.cells().iter().take(cols) {
visit(ScreenCellRef {
text: cell.text(),
width: cell.width(),
padding: cell.is_padding(),
attr: cell.attr(),
fg: cell.fg(),
bg: cell.bg(),
us: cell.us(),
link: cell.link(),
});
emitted += 1;
}
for _ in emitted..cols {
visit(blank_cell_ref());
}
true
}
#[must_use]
pub fn absolute_line_view(&self, absolute_y: usize) -> Option<ScreenLineView> {
let line = self.grid.absolute_line(absolute_y)?;
let width = u32::from(self.grid.size().cols.max(1));
let cells = if let Some(text) = line.plain_text() {
let mut cells = text
.bytes()
.map(|byte| ScreenCellView {
text: char::from(byte).to_string(),
width: 1,
padding: false,
attr: 0,
fg: crate::input::COLOUR_DEFAULT,
bg: crate::input::COLOUR_DEFAULT,
us: crate::input::COLOUR_DEFAULT,
link: 0,
})
.collect::<Vec<_>>();
cells.resize_with(width as usize, || ScreenCellView {
text: " ".to_owned(),
width: 1,
padding: false,
attr: 0,
fg: crate::input::COLOUR_DEFAULT,
bg: crate::input::COLOUR_DEFAULT,
us: crate::input::COLOUR_DEFAULT,
link: 0,
});
cells
} else {
line.cells()
.iter()
.map(|cell| ScreenCellView {
text: cell.text().to_owned(),
width: cell.width(),
padding: cell.is_padding(),
attr: cell.attr(),
fg: cell.fg(),
bg: cell.bg(),
us: cell.us(),
link: cell.link(),
})
.collect()
};
Some(ScreenLineView {
cells,
width,
wrapped: line.flags().contains(GridLineFlags::WRAPPED),
start_prompt: line.flags().contains(GridLineFlags::START_PROMPT),
start_output: line.flags().contains(GridLineFlags::START_OUTPUT),
time: line.time(),
})
}
#[must_use]
pub fn clone_viewport(&self, top_line: usize, cursor_x: u32, cursor_absolute_y: usize) -> Self {
let size = self.grid.size();
let rows = usize::from(size.rows.max(1));
let cols = u32::from(size.cols.max(1));
let total_lines = self.absolute_line_count();
let top_line = top_line.min(total_lines.saturating_sub(rows));
let mut viewport = Self::new(size, 0);
viewport.mode = self.mode;
viewport.cursor_style = self.cursor_style;
viewport.title = self.title.clone();
viewport.window_name = self.window_name.clone();
viewport.path = self.path.clone();
viewport.title_stack = self.title_stack.clone();
viewport.hyperlinks = self.hyperlinks.clone();
viewport.active_hyperlink = self.active_hyperlink;
viewport.bell_count = 0;
viewport.utf8_config = self.utf8_config.clone();
let lines = (0..rows)
.map(|offset| {
self.grid
.absolute_line(top_line + offset)
.cloned()
.unwrap_or_else(|| GridLine::new(cols))
})
.collect();
viewport.grid.replace_visible(lines);
viewport.cursor_x = cursor_x.min(viewport.max_cursor_x());
viewport.cursor_y = if (top_line..top_line + rows).contains(&cursor_absolute_y) {
(cursor_absolute_y - top_line) as u32
} else {
0
};
viewport.pending_wrap = false;
viewport.saved_cursor_x = None;
viewport.saved_cursor_y = None;
viewport.saved_cursor_pending_wrap = false;
viewport.saved_grid = None;
viewport.rupper = 0;
viewport.rlower = u32::from(size.rows.max(1)).saturating_sub(1);
viewport.reset_tabs();
viewport
}
}