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;
pub mod event;
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 clear_buffer: bool,
pub custom_size: Option<Size>,
pub relative_printing: bool,
pub damaged_only: bool,
pub clear_paste: bool,
pub allow_screen_tearing: bool,
}
impl Default for ContextConfig {
fn default() -> Self {
Self {
frame_delay: None,
clear_buffer: true,
custom_size: None,
relative_printing: false,
damaged_only: true,
clear_paste: true,
allow_screen_tearing: false,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ContextSetupConfig {
pub raw_mode: bool,
pub alt_screen: bool,
pub hide_cursor: bool,
pub capture_mouse: bool,
}
impl Default for ContextSetupConfig {
fn default() -> Self {
Self {
raw_mode: true,
alt_screen: true,
hide_cursor: true,
capture_mouse: false,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DrawSummary {
pub duration: std::time::Duration,
pub count: usize,
pub drawn_buffer: usize,
}
#[derive(Debug, Clone)]
pub struct Context<CT: ContentProcessorOutput, B: Backend<CT>, C: ContentProcessor<CT>, R: Widget> {
pub config: ContextConfig,
pub backend: B,
pub content_processor: C,
pub root: R,
pub event_state: event::EventState,
last_event_state: Option<event::EventState>,
filler: Content,
refresh_all: bool,
setup_config: ContextSetupConfig,
has_drawn_before: bool,
last_draw: Option<std::time::Instant>,
last_size: Option<Size>,
buffers: [Buffer; 2],
front_buffer: usize,
_phantom: std::marker::PhantomData<CT>,
}
impl<CT: ContentProcessorOutput, B: Backend<CT>, C: ContentProcessor<CT>, R: Widget> Context<CT, 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,
buffers: [
Buffer::new(),
Buffer::new(),
],
front_buffer: 0,
filler: Content::Clear,
refresh_all: true,
event_state: event::EventState::new(),
last_event_state: None,
_phantom: std::marker::PhantomData,
}
}
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.capture_mouse {
self.backend.capture_mouse(enable)?;
}
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;
}
#[inline(always)]
fn front_buffer_index(&self) -> usize {
return self.front_buffer;
}
#[inline(always)]
fn back_buffer_index(&self) -> usize {
return match self.front_buffer_index() {
0 => 1,
1 => 0,
_ => unreachable!(),
}
}
#[inline(always)]
fn swap_buffers(&mut self) {
self.front_buffer = self.back_buffer_index();
}
pub fn draw(&mut self) -> Result<Option<DrawSummary>, io::Error> {
let draw_start_time = std::time::Instant::now();
let mut draw_count: usize = 0;
if self.should_draw() == false {
return Ok(None);
}
self.swap_buffers();
let front_index = self.front_buffer_index();
let back_index = self.back_buffer_index();
if self.config.clear_buffer {
self.buffers[front_index].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::zero(),
size,
),
0,
&mut self.buffers[front_index],
self.filler.clone(),
);
canvas.transform.size = self.root.widget_info().size_info.correct_size(canvas.transform.size);
self.root.draw(&mut canvas, Some(&self.event_state_frame()));
if size != self.last_size.unwrap_or(size) {
self.refresh_all = true;
}
if self.config.allow_screen_tearing == false {
self.backend.begin_sync_update()?;
}
for row in 0..size.rows {
for col in 0..size.cols {
let position = Position::new(col as i16, row as i16);
let draw_position = position + offset;
let back_content = self.buffers[back_index].get(position);
let front_content = self.buffers[front_index].get(position);
let should_draw: bool = self.refresh_all
|| self.config.damaged_only == false
|| front_content != back_content;
if should_draw {
self.backend.set_cursor_pos(draw_position)?;
let content: Content = match self.buffers[front_index].get(position) {
Some(s) => s.clone(),
None => self.filler.clone(),
};
self.print(&content, &mut draw_count)?;
}
}
}
if self.config.allow_screen_tearing == false {
self.backend.end_sync_update()?;
}
self.backend.set_cursor_pos(offset)?;
self.backend.flush()?;
self.last_draw = Some(std::time::Instant::now());
self.last_size = Some(size);
self.last_event_state = Some(self.event_state.clone());
self.refresh_all = false;
if self.config.clear_paste {
self.event_state.terminal.paste = None;
}
self.has_drawn_before = true;
let draw_duration = std::time::Instant::now() - draw_start_time;
Ok(Some(DrawSummary {
duration: draw_duration,
count: draw_count,
drawn_buffer: front_index,
}))
}
#[inline(always)]
pub fn event_state_frame(&self) -> event::EventStateFrame {
return self.event_state.calculate_frame(self.last_event_state.clone().unwrap_or(self.event_state.clone()));
}
#[inline(always)]
pub fn get_last_event_state(&self) -> Option<event::EventState> {
return self.last_event_state.clone();
}
#[inline(always)]
fn print(&mut self, content: &Content, draw_count: &mut usize) -> Result<(), io::Error> {
self.backend.print(match content {
Content::Clear => CT::clear_output(),
Content::Styled(character, style) => {
self.content_processor.process(*character, style)
},
})?;
*draw_count += 1;
Ok(())
}
#[inline(always)]
pub fn set_filler(&mut self, content: Content) {
if self.filler != content {
self.refresh_all = true;
}
self.filler = content;
}
#[inline(always)]
pub fn get_filler(&self) -> &Content {
return &self.filler;
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
struct Buffer {
map: HashMap<Position, Content>,
}
impl Buffer {
pub fn new() -> Self {
Self {
map: HashMap::new(),
}
}
#[inline(always)]
pub fn set(&mut self, position: Position, content: Option<Content>) {
match content {
Some(s) => self.map.insert(position, s),
None => self.map.remove(&position),
};
}
#[inline(always)]
pub fn get(&self, position: Position) -> Option<&Content> {
return self.map.get(&position);
}
#[inline(always)]
pub fn clear(&mut self) {
self.map = HashMap::new(); }
}
pub struct Canvas {
pub transform: Transform,
buffer_pointer: *mut Buffer,
set_by_canvas: HashMap<Position, ()>,
transform_original: Transform,
depth: u32,
filler: Content,
}
impl Canvas {
fn new(
transform: Transform,
depth: u32,
buffer_pointer: *mut Buffer,
filler: Content,
) -> Self {
return Self {
transform,
buffer_pointer,
set_by_canvas: HashMap::new(),
transform_original: transform,
depth,
filler,
};
}
#[inline(always)]
pub fn new_child(&self, transform: Transform) -> Self {
return Self::new(
transform.offset_by(self.transform.position),
self.depth + 1,
self.buffer_pointer,
self.filler.clone(),
);
}
#[inline(always)]
pub fn new_copy_child(&self) -> Self {
return self.new_child(Transform::new(Position::zero(), self.transform.size));
}
#[inline(always)]
pub fn depth(&self) -> u32 {
return self.depth;
}
#[inline(always)]
pub fn is_root(&self) -> bool {
return self.depth == 0;
}
#[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 changed_at(&self, position: Position) -> bool {
return !(self.set_by_canvas.get(&position) == None);
}
#[inline(always)]
pub fn get(&self, position: Position) -> Option<&Content> {
if self.transform.zero_position().contains_point(position) == false {
return None;
}
let real_position = self.transform.position + position;
let buffer = unsafe { &*self.buffer_pointer };
return buffer.get(real_position);
}
#[inline(always)]
pub fn set(&mut self, position: Position, content: Option<Content>) {
if self.transform.zero_position().contains_point(position) == false {
return;
}
let real_position = self.transform.position + position;
let mut content = content;
let buffer = unsafe { &mut *self.buffer_pointer };
if let Some(Content::Styled(_, ref mut style)) = content {
let behind_content = buffer.get(real_position).unwrap_or(&self.filler);
*style = overlay_transparent(style, Some(behind_content), Some(&self.filler));
}
buffer.set(real_position, content);
self.set_by_canvas.insert(position, ());
}
}
#[inline(always)]
fn overlay_transparent(front: &Style, back: Option<&Content>, filler: Option<&Content>) -> Style {
let mut style = front.clone();
macro_rules! style_apply {
($fg_bg: ident, $opposite_fg_bg: ident, $pull_layer: expr, $back: ident) => {
if let Some(content) = $back {
match content {
Content::Styled(_, b_style) => {
style.$fg_bg = match $pull_layer {
StylePullLayer::Foreground => b_style.fg,
StylePullLayer::Background => b_style.bg,
StylePullLayer::Same => b_style.$fg_bg,
StylePullLayer::Any | StylePullLayer::AnyColor => {
let color_only = $pull_layer == StylePullLayer::AnyColor;
if (b_style.$fg_bg.is_final() && color_only == false)
|| (b_style.$fg_bg.is_color() && color_only) {
b_style.$fg_bg
} else {
if (b_style.$opposite_fg_bg.is_final() && color_only == false)
|| (b_style.$opposite_fg_bg.is_color() && color_only) {
b_style.$opposite_fg_bg
} else {
b_style.$fg_bg
}
}
},
};
},
Content::Clear => style.$fg_bg = StyleGround::Clear,
};
}
};
}
macro_rules! apply {
($fg_bg: ident, $opposite_fg_bg: ident) => {
if style.$fg_bg.is_final() == false {
if let StyleGround::Filler(pull_layer) = style.$fg_bg {
style_apply!($fg_bg, $opposite_fg_bg, pull_layer, filler);
}
else if let StyleGround::Transparent(pull_layer) = style.$fg_bg {
style_apply!($fg_bg, $opposite_fg_bg, pull_layer, back);
}
}
};
}
apply!(fg, bg);
apply!(bg, fg);
return style;
}
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,
}
}
#[inline(always)]
pub fn zero() -> Self {
Self::new(Position::zero(), Size::zero())
}
#[inline(always)]
pub fn zero_position(&self) -> Self {
return Self::new(Position::zero(), self.size);
}
#[inline(always)]
pub fn offset_by(&self, offset: Position) -> Self {
return Self::new(self.position + offset, self.size);
}
#[inline(always)]
pub fn contains_point(&self, position: Position) -> bool {
if position.col >= self.position.col
&& position.row >= self.position.row {
if position.col < self.size.cols as i16 + self.position.col
&& position.row < self.size.rows as i16 + self.position.row {
return true;
}
}
return false;
}
#[inline(always)]
pub fn expand_to_position(&self, position: Position) -> Self {
return self.combined_area(Self::new(position, Size::new(1, 1)));
}
#[inline(always)]
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,
}
}
#[inline(always)]
pub fn zero() -> Self {
Self::new(0, 0)
}
#[inline(always)]
pub fn same(cols_rows: u16) -> Self {
Self::new(cols_rows, cols_rows)
}
#[inline(always)]
pub fn area(&self) -> u32 {
return (self.cols as u32) * (self.rows as u32);
}
}
#[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,
}
}
#[inline(always)]
pub fn zero() -> Self {
Self::new(0, 0)
}
#[inline(always)]
pub fn same(col_row: i16) -> Self {
Self::new(col_row, col_row)
}
}
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);