use crate::cell::{unicode_column_width, AttributeChange, CellAttributes};
use crate::color::ColorAttribute;
pub use crate::image::{ImageData, TextureCoordinate};
use crate::surface::{CursorShape, CursorVisibility, Position};
use finl_unicode::grapheme_clusters::Graphemes;
#[cfg(feature = "use_serde")]
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Change {
Attribute(AttributeChange),
AllAttributes(CellAttributes),
Text(String),
ClearScreen(ColorAttribute),
ClearToEndOfLine(ColorAttribute),
ClearToEndOfScreen(ColorAttribute),
CursorPosition { x: Position, y: Position },
CursorColor(ColorAttribute),
CursorShape(CursorShape),
CursorVisibility(CursorVisibility),
Image(Image),
ScrollRegionUp {
first_row: usize,
region_size: usize,
scroll_count: usize,
},
ScrollRegionDown {
first_row: usize,
region_size: usize,
scroll_count: usize,
},
Title(String),
}
impl Change {
pub fn is_text(&self) -> bool {
matches!(self, Change::Text(_))
}
pub fn text(&self) -> &str {
match self {
Change::Text(text) => text,
_ => panic!("you must use Change::is_text() to guard calls to Change::text()"),
}
}
}
impl<S: Into<String>> From<S> for Change {
fn from(s: S) -> Self {
Change::Text(s.into())
}
}
impl From<AttributeChange> for Change {
fn from(c: AttributeChange) -> Self {
Change::Attribute(c)
}
}
pub struct ChangeSequence {
changes: Vec<Change>,
screen_rows: usize,
screen_cols: usize,
pub(crate) cursor_x: usize,
pub(crate) cursor_y: isize,
render_y_max: isize,
render_y_min: isize,
}
impl ChangeSequence {
pub fn new(rows: usize, cols: usize) -> Self {
Self {
changes: vec![],
screen_rows: rows,
screen_cols: cols,
cursor_x: 0,
cursor_y: 0,
render_y_max: 0,
render_y_min: 0,
}
}
pub fn consume(self) -> Vec<Change> {
self.changes
}
pub fn current_cursor_position(&self) -> (usize, isize) {
(self.cursor_x, self.cursor_y)
}
pub fn move_to(&mut self, (cursor_x, cursor_y): (usize, isize)) {
self.add(Change::CursorPosition {
x: Position::Relative(cursor_x as isize - self.cursor_x as isize),
y: Position::Relative(cursor_y - self.cursor_y),
});
}
pub fn render_height(&self) -> usize {
(self.render_y_max - self.render_y_min).max(0).abs() as usize
}
fn update_render_height(&mut self) {
self.render_y_max = self.render_y_max.max(self.cursor_y);
self.render_y_min = self.render_y_min.min(self.cursor_y);
}
pub fn add_changes(&mut self, changes: Vec<Change>) {
for change in changes {
self.add(change);
}
}
pub fn add<C: Into<Change>>(&mut self, change: C) {
let change = change.into();
match &change {
Change::AllAttributes(_)
| Change::Attribute(_)
| Change::CursorColor(_)
| Change::CursorShape(_)
| Change::CursorVisibility(_)
| Change::ClearToEndOfLine(_)
| Change::Title(_)
| Change::ClearToEndOfScreen(_) => {}
Change::Text(t) => {
for g in Graphemes::new(t.as_str()) {
if self.cursor_x == self.screen_cols {
self.cursor_y += 1;
self.cursor_x = 0;
}
if g == "\n" {
self.cursor_y += 1;
} else if g == "\r" {
self.cursor_x = 0;
} else if g == "\r\n" {
self.cursor_y += 1;
self.cursor_x = 0;
} else {
let len = unicode_column_width(g, None);
self.cursor_x += len;
}
}
self.update_render_height();
}
Change::Image(im) => {
self.cursor_x += im.width;
self.render_y_max = self.render_y_max.max(self.cursor_y + im.height as isize);
}
Change::ClearScreen(_) => {
self.cursor_x = 0;
self.cursor_y = 0;
}
Change::CursorPosition { x, y } => {
self.cursor_x = match x {
Position::Relative(x) => {
((self.cursor_x as isize + x) % self.screen_cols as isize) as usize
}
Position::Absolute(x) => x % self.screen_cols,
Position::EndRelative(x) => (self.screen_cols - x) % self.screen_cols,
};
self.cursor_y = match y {
Position::Relative(y) => {
(self.cursor_y as isize + y) % self.screen_rows as isize
}
Position::Absolute(y) => (y % self.screen_rows) as isize,
Position::EndRelative(y) => {
((self.screen_rows - y) % self.screen_rows) as isize
}
};
self.update_render_height();
}
Change::ScrollRegionUp { .. } | Change::ScrollRegionDown { .. } => {
self.cursor_x = 0;
self.cursor_y = 0;
}
}
self.changes.push(change);
}
}
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Image {
pub width: usize,
pub height: usize,
pub top_left: TextureCoordinate,
pub bottom_right: TextureCoordinate,
pub image: Arc<ImageData>,
}