#[cfg(feature = "parser")]
pub mod parser;
pub mod text_processing;
use crate::font::Font;
use crate::renderer::backgroundmesh::BackgroundMesh;
use crate::renderer::textbuffermesh::TextBufferMesh;
use crate::terminal::Terminal;
use crate::text_processing::ProcessedChar;
use std::sync::atomic::{AtomicUsize, Ordering};
pub type Color = [f32; 4];
pub type RawCharacter = u16;
static INDEX_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub struct TextBuffer {
index: u32,
pub(crate) chars: Vec<TermCharacter>,
pub(crate) height: u32,
pub(crate) width: u32,
pub(crate) mesh: Option<TextBufferMesh>,
pub(crate) background_mesh: Option<BackgroundMesh>,
pub(crate) aspect_ratio: f32,
pub cursor: TermCursor,
dirty: bool,
}
impl TextBuffer {
pub fn create(terminal: &Terminal, dimensions: (u32, u32)) -> Result<TextBuffer, String> {
let (width, height) = dimensions;
if width == 0 || height == 0 {
return Err(
"TextBuffer dimensions are erronous; either width or height is below 1".to_owned(),
);
}
let chars =
vec![TermCharacter::new(' ' as u16, Default::default()); (width * height) as usize];
let (mesh, background_mesh) = if terminal.headless {
(None, None)
} else {
(
Some(TextBufferMesh::new(
terminal.get_program(),
dimensions,
&terminal.font,
)),
Some(BackgroundMesh::new(
terminal.get_background_program(),
dimensions,
)),
)
};
let true_height = height * terminal.font.line_height;
let true_width = (width as f32 * terminal.font.average_xadvance) as u32;
let index = INDEX_COUNTER.fetch_add(1, Ordering::Relaxed) as u32;
Ok(TextBuffer {
index: index,
chars,
height,
width,
mesh,
background_mesh,
cursor: TermCursor {
x: 0,
y: 0,
style: Default::default(),
limits: TermLimits::new(width, height),
},
aspect_ratio: true_width as f32 / true_height as f32,
dirty: true,
})
}
pub(crate) fn get_idx(&self) -> u32 {
self.index
}
pub(crate) fn swap_buffers(&mut self, font: &Font) {
if self.dirty {
if let (&Some(ref mesh), &Some(ref background_mesh)) =
(&self.mesh, &self.background_mesh)
{
mesh.update(&self, font);
background_mesh.update(&self);
}
self.dirty = false;
}
}
pub fn get_dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn set_char(&mut self, x: u32, y: u32, character: TermCharacter) {
self.chars[(y * self.width + x) as usize] = character;
}
pub fn get_character(&self, x: u32, y: u32) -> Option<TermCharacter> {
if x >= self.width || y >= self.height {
None
} else {
Some(self.chars[(y * self.width + x) as usize])
}
}
pub fn clear(&mut self) {
self.chars = vec![
TermCharacter::new(' ' as u16, Default::default());
(self.width * self.height) as usize
];
}
pub fn put_char(&mut self, character: char) {
if character.len_utf16() > 1 {
panic!("Can not insert over 16-bit characters");
} else {
let mut bytes = [0; 1];
character.encode_utf16(&mut bytes);
self.put_raw_char(bytes[0]);
}
}
pub fn put_raw_char(&mut self, character: RawCharacter) {
self.chars[(self.cursor.y * self.width + self.cursor.x) as usize] =
TermCharacter::new(character, self.cursor.style);
self.cursor.move_by(1);
self.dirty = true;
}
pub fn write<T: Into<String>>(&mut self, text: T) {
let text = text.into();
for c in text.to_owned().encode_utf16() {
self.put_raw_char(c);
}
}
pub fn write_processed(&mut self, char_list: &[ProcessedChar]) {
let default = self.cursor.style;
for character in char_list {
self.cursor.style.fg_color = character.style.fg_color.unwrap_or(default.fg_color);
self.cursor.style.bg_color = character.style.bg_color.unwrap_or(default.bg_color);
self.cursor.style.shakiness = character.style.shakiness.unwrap_or(default.shakiness);
self.put_char(character.character);
}
self.cursor.style = default;
}
pub fn get_cursor_position(&self) -> (u32, u32) {
(self.cursor.x, self.cursor.y)
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TextStyle {
pub fg_color: Color,
pub bg_color: Color,
pub shakiness: f32,
}
impl Default for TextStyle {
fn default() -> TextStyle {
TextStyle {
fg_color: [1.0; 4],
bg_color: [0.0; 4],
shakiness: 0.0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TermCharacter {
character: RawCharacter,
pub style: TextStyle,
}
impl TermCharacter {
pub(crate) fn new(character: RawCharacter, style: TextStyle) -> TermCharacter {
TermCharacter { character, style }
}
pub fn get_raw_char(&self) -> RawCharacter {
self.character
}
pub fn get_char(&self) -> char {
String::from_utf16(&[self.character]).unwrap().remove(0)
}
}
#[derive(Clone, Debug)]
pub struct TermCursor {
x: u32,
y: u32,
pub style: TextStyle,
limits: TermLimits,
}
impl TermCursor {
pub fn set_limits(
&mut self,
x_min: Option<u32>,
x_max: Option<u32>,
y_min: Option<u32>,
y_max: Option<u32>,
) {
self.limits.x_min = x_min;
self.limits.x_max = x_max;
self.limits.y_min = y_min;
self.limits.y_max = y_max;
}
pub fn get_limits(&self) -> TermLimits {
self.limits.clone()
}
pub fn move_to(&mut self, x: u32, y: u32) {
let x = x.max(self.limits.get_min_x()).min(self.limits.get_max_x());
let y = y.max(self.limits.get_min_y()).min(self.limits.get_max_y());
self.x = x;
self.y = y;
}
fn move_by(&mut self, amount: u32) {
self.x += amount;
if self.x > self.limits.get_max_x() {
self.x = self.limits.get_min_x();
self.y += 1;
if self.y > self.limits.get_max_y() {
self.y = self.limits.get_min_y();
}
}
}
}
#[derive(Clone, Debug)]
pub struct TermLimits {
width: u32,
height: u32,
x_min: Option<u32>,
x_max: Option<u32>,
y_min: Option<u32>,
y_max: Option<u32>,
}
impl TermLimits {
fn new(width: u32, height: u32) -> TermLimits {
TermLimits {
width: width,
height: height,
x_min: None,
x_max: None,
y_min: None,
y_max: None,
}
}
pub fn get_min_x(&self) -> u32 {
if let Some(x_min) = self.x_min {
x_min.max(0)
} else {
0
}
}
pub fn get_max_x(&self) -> u32 {
if let Some(x_max) = self.x_max {
x_max.min(self.width - 1)
} else {
self.width - 1
}
}
pub fn get_min_y(&self) -> u32 {
if let Some(y_min) = self.y_min {
y_min.max(0)
} else {
0
}
}
pub fn get_max_y(&self) -> u32 {
if let Some(y_max) = self.y_max {
y_max.min(self.height - 1)
} else {
self.height - 1
}
}
}