macro_rules! include {
($module: ident) => {
mod $module;
pub use $module::*;
};
}
include!(backend);
include!(widget);
include!(animation);
include!(shader);
include!(content_processor);
include!(style);
mod type_aliases;
use type_aliases::*;
pub mod preludes;
pub mod backends;
pub mod content_processors;
pub mod widgets;
pub mod animations;
pub mod shaders;
use std::io;
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Content {
Styled(char, Style),
Clear,
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ContextConfig {
pub frame_delay: Option<std::time::Duration>,
pub draw_row_delay: Option<std::time::Duration>,
pub draw_col_delay: Option<std::time::Duration>,
pub clear_buffer: bool,
pub custom_size: Option<Size>,
pub relative_printing: bool,
pub damaged_only: bool,
}
impl Default for ContextConfig {
fn default() -> Self {
Self {
frame_delay: None,
draw_row_delay: None,
draw_col_delay: None,
clear_buffer: true,
custom_size: None,
relative_printing: false,
damaged_only: true,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ContextSetupConfig {
pub raw_mode: bool,
pub alt_screen: bool,
pub hide_cursor: bool,
}
impl Default for ContextSetupConfig {
fn default() -> Self {
Self {
raw_mode: true,
alt_screen: true,
hide_cursor: true,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DrawSummary {
pub skipped: bool,
pub duration: std::time::Duration,
pub count: usize,
pub area: Transform,
}
impl DrawSummary {
fn skipped() -> Self {
Self {
skipped: true,
duration: std::time::Duration::ZERO,
count: 0,
area: Transform::zero(),
}
}
}
#[derive(Debug, Clone)]
pub struct Context<B: Backend, C: ContentProcessor, R: RootWidget> {
pub config: ContextConfig,
pub backend: B,
pub content_processor: C,
pub root: R,
filler: Content,
refresh_filler: bool,
setup_config: ContextSetupConfig,
has_drawn_before: bool,
last_draw: Option<std::time::Instant>,
last_size: Option<Size>,
last_damaged: Option<Transform>,
content: HashMap<Position, Content>,
}
impl<B: Backend, C: ContentProcessor, R: RootWidget> Context<B, C, R> {
pub fn new(config: ContextConfig, setup_config: ContextSetupConfig, backend: B, content_processor: C, root: R) -> Self {
Self {
config,
backend,
content_processor,
root,
setup_config,
has_drawn_before: false,
last_draw: None,
last_size: None,
last_damaged: None,
content: HashMap::new(),
filler: Content::Clear,
refresh_filler: true,
}
}
pub fn setup(&mut self) -> Result<(), io::Error> {
self.set_state(true)?;
Ok(())
}
pub fn cleanup(&mut self) -> Result<(), io::Error> {
self.set_state(false)?;
if self.setup_config.alt_screen == false {
self.backend.clear(ClearType::FromCursorDown)?;
}
Ok(())
}
fn set_state(&mut self, enable: bool) -> Result<(), io::Error> {
if self.setup_config.alt_screen {
self.backend.alt_screen(enable)?;
}
if self.setup_config.raw_mode {
self.backend.raw_mode(enable)?;
}
if self.setup_config.hide_cursor {
self.backend.show_cursor(!enable)?;
}
Ok(())
}
pub fn last_size(&self) -> Option<Size> {
return self.last_size;
}
pub fn last_draw(&self) -> Option<std::time::Instant> {
return self.last_draw;
}
pub fn duration_since_last_draw(&self) -> Option<std::time::Duration> {
match self.last_draw() {
Some(s) => Some(std::time::Instant::now() - s),
None => None,
}
}
#[inline(always)]
fn should_draw(&self) -> bool {
let mut should_draw = true;
if let Some(frame_delay) = self.config.frame_delay {
match self.duration_since_last_draw() {
Some(s) => {
if s < frame_delay {
should_draw = false;
}
},
None => (),
};
}
return should_draw;
}
pub fn draw(&mut self) -> Result<DrawSummary, io::Error> {
let draw_start_time = std::time::Instant::now();
let mut draw_count: usize = 0;
if self.should_draw() == false {
return Ok(DrawSummary::skipped());
}
if self.config.clear_buffer {
self.content.clear();
}
let offset = match self.config.relative_printing {
true => self.backend.cursor_position()?,
false => Position::new(0, 0),
};
let size = match self.config.custom_size {
Some(s) => s,
None => self.backend.terminal_size()?,
} - Size::new(offset.col as u16, offset.row as u16);
let mut canvas = Canvas::new(Transform::new(Position::new(0, 0), size));
self.root.draw(&mut canvas);
for (i, data) in canvas.waiting.into_iter().enumerate() {
if i < canvas.start_index {
continue;
}
self.set(data.0, data.1);
}
let new_damaged: Option<Transform> = canvas.damaged;
let total_damaged: Transform = compute_refresh_area(
new_damaged,
self.last_damaged,
self.has_drawn_before,
size,
).unwrap_or(Transform::zero());
let mut draw_area: Transform = match self.config.damaged_only {
true => total_damaged,
false => Transform::new(Position::zero(), size),
};
if self.refresh_filler {
draw_area = Transform::new(Position::zero(), size);
}
for row in 0..draw_area.size.rows {
for col in 0..draw_area.size.cols {
let position = Position::new(
col as i16,
row as i16,
) + draw_area.position;
self.backend.set_cursor_pos(position + offset)?;
let content: Content = match self.content.get(&position) {
Some(s) => s.clone(),
None => self.filler.clone(),
};
self.print(&content, &mut draw_count)?;
if let Some(s) = self.config.draw_col_delay {
self.backend.flush()?;
std::thread::sleep(s);
}
}
if let Some(s) = self.config.draw_row_delay {
self.backend.flush()?;
std::thread::sleep(s);
}
}
self.backend.set_cursor_pos(offset)?;
self.backend.flush()?;
self.last_draw = Some(std::time::Instant::now());
self.last_size = Some(size);
self.last_damaged = new_damaged;
self.refresh_filler = false;
self.has_drawn_before = true;
let draw_duration = std::time::Instant::now() - draw_start_time;
Ok(DrawSummary {
skipped: false,
duration: draw_duration,
count: draw_count,
area: draw_area,
})
}
#[inline(always)]
fn print(&mut self, content: &Content, draw_count: &mut usize) -> Result<(), io::Error> {
self.backend.print(match content {
Content::Clear => String::from(" "),
Content::Styled(character, style) => {
self.content_processor.stringify(*character, style)
},
})?;
*draw_count += 1;
Ok(())
}
#[inline(always)]
pub fn set_filler(&mut self, content: Content) {
if self.filler != content {
self.refresh_filler = true;
}
self.filler = content;
}
#[inline(always)]
pub fn get_filler(&self) -> &Content {
return &self.filler;
}
#[inline(always)]
fn set(&mut self, position: Position, content: Option<Content>) {
match content {
Some(s) => self.content.insert(position, s),
None => self.content.remove(&position),
};
}
}
pub struct Canvas {
pub transform: Transform,
pub waiting: Vec<(Position, Option<Content>)>,
pub start_index: usize,
damaged: Option<Transform>,
transform_original: Transform,
}
impl Canvas {
pub fn new(transform: Transform) -> Self {
return Self {
transform,
waiting: Vec::new(),
start_index: 0,
damaged: None,
transform_original: transform,
};
}
#[inline(always)]
pub fn animate<A: Animation>(
&mut self,
animation: &mut A,
animation_data: &AnimationData,
custom_original: Option<Transform>,
) {
self.animate_with_offset(animation, animation_data, custom_original, 0.0);
}
#[inline(always)]
pub fn animate_with_offset<A: Animation>(
&mut self,
animation: &mut A,
animation_data: &AnimationData,
custom_original: Option<Transform>,
offset: f64,
) {
let original = custom_original.unwrap_or(self.original_transform());
self.transform = animate(animation, animation_data, original, offset);
}
#[inline(always)]
pub fn original_transform(&self) -> Transform {
return self.transform_original;
}
#[inline(always)]
pub fn is_visible(&self) -> bool {
if self.transform.size.cols == 0 || self.transform.size.rows == 0 {
return false;
}
return true;
}
#[inline(always)]
pub fn set(&mut self, position: Position, content: Option<Content>) {
if position.row < 0 || position.col < 0 {
return;
}
if position.row < self.transform.size.rows as i16 && position.col < self.transform.size.cols as i16 {
let data = (self.transform.position + position, content);
self.damaged = Some(match self.damaged {
Some(damaged) => damaged.expand_to_position(data.0),
None => Transform::new(data.0, Size::new(1, 1)),
});
self.waiting.push(data);
}
}
#[inline(always)]
pub fn consume(&mut self, inner_canvas: Self) {
for i in inner_canvas.waiting {
self.set(i.0, i.1);
}
if let Some(inner_damaged) = inner_canvas.damaged {
self.damaged = Some(match self.damaged {
Some(s) => s.combined_area(inner_damaged),
None => inner_damaged,
});
}
}
}
pub fn compute_refresh_area(
damaged: Option<Transform>,
prev_damaged: Option<Transform>,
has_drawn_before: bool,
full_size: Size,
) -> Option<Transform> {
let last_damaged: Option<Transform> = match (prev_damaged, has_drawn_before) {
(None, false) => Some(Transform::new(Position::zero(), full_size)),
(l_d, _) => l_d,
};
return match (damaged, last_damaged) {
(Some(damaged), Some(last_damaged)) => Some(damaged.combined_area(last_damaged)),
(Some(damaged), None) => Some(damaged),
(None, Some(last_damaged)) => Some(last_damaged),
(None, None) => None,
};
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Transform {
pub position: Position,
pub size: Size,
}
impl Transform {
pub fn new(position: Position, size: Size) -> Self {
Self {
position,
size,
}
}
pub fn zero() -> Self {
Self::new(Position::zero(), Size::zero())
}
pub fn expand_to_position(&self, position: Position) -> Self {
return self.combined_area(Self::new(position, Size::new(1, 1)));
}
pub fn combined_area(&self, other: Self) -> Self {
let mut total = *self;
if other.position.row < total.position.row {
total.size.rows += (total.position.row - other.position.row) as u16;
total.position.row = other.position.row;
}
if other.position.col < total.position.col {
total.size.cols += (total.position.col - other.position.col) as u16;
total.position.col = other.position.col;
}
if other.size.rows as i16 + other.position.row >= total.size.rows as i16 + total.position.row {
total.size.rows = ((other.size.rows as i16 + other.position.row) - total.position.row) as u16;
}
if other.size.cols as i16 + other.position.col >= total.size.cols as i16 + total.position.col {
total.size.cols = ((other.size.cols as i16 + other.position.col) - total.position.col) as u16;
}
return total;
}
#[inline(always)]
pub fn apply_lerp(&self, b: Self, t: f64, f: fn(f64, f64, f64) -> f64) -> Self {
return Self::new(
Position {
col: f(self.position.col as f64, b.position.col as f64, t) as i16,
row: f(self.position.row as f64, b.position.row as f64, t) as i16,
},
Size {
cols: f(self.size.cols as f64, b.size.cols as f64, t) as u16,
rows: f(self.size.rows as f64, b.size.rows as f64, t) as u16,
},
);
}
#[inline(always)]
pub fn apply_quadratic_bezier(&self, control: Self, b: Self, t: f64, f: fn(f64, f64, f64, f64) -> f64) -> Self {
return Self::new(
Position {
col: f(self.position.col as f64, control.position.col as f64, b.position.col as f64, t) as i16,
row: f(self.position.row as f64, control.position.row as f64, b.position.row as f64, t) as i16,
},
Size {
cols: f(self.size.cols as f64, control.size.cols as f64, b.size.cols as f64, t) as u16,
rows: f(self.size.rows as f64, control.size.rows as f64, b.size.rows as f64, t) as u16,
},
);
}
#[inline(always)]
pub fn apply_cubic_bezier(&self, a_control: Self, b: Self, b_control: Self, t: f64, f: fn(f64, f64, f64, f64, f64) -> f64) -> Self {
return Self::new(
Position {
col: f(self.position.col as f64, a_control.position.col as f64, b.position.col as f64, b_control.position.col as f64, t) as i16,
row: f(self.position.row as f64, a_control.position.row as f64, b.position.row as f64, b_control.position.row as f64, t) as i16,
},
Size {
cols: f(self.size.cols as f64, a_control.size.cols as f64, b.size.cols as f64, b_control.size.cols as f64, t) as u16,
rows: f(self.size.rows as f64, a_control.size.rows as f64, b.size.rows as f64, b_control.size.rows as f64, t) as u16,
},
);
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Size {
pub cols: u16,
pub rows: u16,
}
impl Size {
pub fn new(cols: u16, rows: u16) -> Self {
Self {
cols,
rows,
}
}
pub fn zero() -> Self {
Self::new(0, 0)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Position {
pub col: i16,
pub row: i16,
}
impl Position {
pub fn new(col: i16, row: i16) -> Self {
Self {
col,
row,
}
}
pub fn zero() -> Self {
Self::new(0, 0)
}
}
macro_rules! op_impl_core {
($op: ident, $func: ident, $subfunc: ident) => {
impl std::ops::$op for Size {
type Output = Self;
fn $func(self, rhs: Self) -> Self::Output {
Self {
cols: self.cols.$subfunc(rhs.cols),
rows: self.rows.$subfunc(rhs.rows),
}
}
}
impl std::ops::$op for Position {
type Output = Self;
fn $func(self, rhs: Self) -> Self::Output {
Self {
col: self.col.$subfunc(rhs.col),
row: self.row.$subfunc(rhs.row),
}
}
}
impl std::ops::$op for Transform {
type Output = Self;
fn $func(self, rhs: Self) -> Self::Output {
Self {
position: self.position.$func(rhs.position),
size: self.size.$func(rhs.size),
}
}
}
};
}
macro_rules! op_impl {
($op: ident, $func: ident) => {
op_impl_core!($op, $func, $func);
};
($op: ident, $func: ident, $subfunc: ident) => {
op_impl_core!($op, $func, $subfunc);
};
}
op_impl!(Add, add, saturating_add);
op_impl!(Sub, sub, saturating_sub);
op_impl!(Mul, mul, saturating_mul);
op_impl!(Div, div, saturating_div);
op_impl!(Rem, rem);
op_impl!(Shl, shl);
op_impl!(Shr, shr);