#![deny(missing_docs)]
use std::io::{Stdout, Write};
use std::ops::Add;
use std::time::Duration;
use anathema_geometry::{LocalPos, Pos, Size};
use anathema_value_resolver::AttributeStorage;
use anathema_widgets::components::events::Event;
pub use anathema_widgets::{Attributes, Style};
use anathema_widgets::{GlyphMap, PaintChildren, WidgetRenderer};
use crossterm::cursor::{RestorePosition, SavePosition};
use crossterm::execute;
use crossterm::terminal::{BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate, size};
pub use screen::Screen;
pub use self::buffer::Buffer;
use self::events::Events;
use crate::Backend;
mod buffer;
pub mod events;
mod screen;
mod style;
pub struct TuiBackendBuilder {
output: Stdout,
hide_cursor: bool,
enable_raw_mode: bool,
enable_alt_screen: bool,
enable_mouse: bool,
}
impl TuiBackendBuilder {
pub fn enable_alt_screen(mut self) -> Self {
self.enable_alt_screen = true;
self
}
pub fn enable_mouse(mut self) -> Self {
self.enable_mouse = true;
self
}
pub fn enable_raw_mode(mut self) -> Self {
self.enable_raw_mode = true;
self
}
pub fn hide_cursor(mut self) -> Self {
self.hide_cursor = true;
self
}
pub fn clear(mut self) -> Self {
let _ = execute!(&mut self.output, Clear(ClearType::All));
self
}
pub fn finish(self) -> Result<TuiBackend, std::io::Error> {
let size = size()?;
let screen = Screen::new(size);
let backend = TuiBackend {
screen,
output: self.output,
events: Events,
hide_cursor: self.hide_cursor,
enable_raw_mode: self.enable_raw_mode,
enable_alt_screen: self.enable_alt_screen,
enable_mouse: self.enable_mouse,
};
Ok(backend)
}
}
pub struct TuiBackend {
screen: Screen,
output: Stdout,
events: Events,
hide_cursor: bool,
enable_raw_mode: bool,
enable_alt_screen: bool,
enable_mouse: bool,
}
impl TuiBackend {
pub fn builder() -> TuiBackendBuilder {
TuiBackendBuilder {
output: std::io::stdout(),
hide_cursor: false,
enable_raw_mode: false,
enable_alt_screen: false,
enable_mouse: false,
}
}
pub fn full_screen() -> Self {
let mut inst = Self::builder()
.enable_alt_screen()
.enable_raw_mode()
.hide_cursor()
.finish()
.unwrap();
inst.finalize();
inst
}
pub fn disable_raw_mode(self) -> Self {
let _ = Screen::disable_raw_mode();
self
}
}
impl Backend for TuiBackend {
fn size(&self) -> Size {
self.screen.size()
}
fn next_event(&mut self, timeout: Duration) -> Option<Event> {
self.events.poll(timeout)
}
fn resize(&mut self, new_size: Size, _: &mut GlyphMap) {
self.screen.resize(new_size);
}
fn paint<'bp>(
&mut self,
glyph_map: &mut GlyphMap,
widgets: PaintChildren<'_, 'bp>,
attribute_storage: &AttributeStorage<'bp>,
) {
anathema_widgets::paint::paint(&mut self.screen, glyph_map, widgets, attribute_storage);
}
fn render(&mut self, glyph_map: &mut GlyphMap) {
let _ = execute!(&mut self.output, BeginSynchronizedUpdate);
let _ = self.screen.render(&mut self.output, glyph_map);
let _ = execute!(&mut self.output, EndSynchronizedUpdate);
}
fn clear(&mut self) {
self.screen.erase();
}
fn finalize(&mut self) {
if self.enable_alt_screen {
let _ = execute!(&mut self.output, SavePosition);
let _ = Screen::enter_alt_screen(&mut self.output);
}
if self.hide_cursor {
let _ = Screen::show_cursor(&mut self.output);
let _ = Screen::hide_cursor(&mut self.output);
}
if self.enable_raw_mode {
let _ = Screen::enable_raw_mode();
}
if self.enable_mouse {
let _ = Screen::enable_mouse(&mut self.output);
}
let _ = self.output.flush();
}
}
impl Drop for TuiBackend {
fn drop(&mut self) {
if self.enable_alt_screen {
let _ = execute!(&mut self.output, RestorePosition);
}
let _ = self.screen.restore(&mut self.output, self.enable_alt_screen);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScreenPos {
pub x: u16,
pub y: u16,
}
impl ScreenPos {
pub const ZERO: Self = Self::new(0, 0);
pub const fn new(x: u16, y: u16) -> Self {
Self { x, y }
}
}
impl Add<ScreenPos> for LocalPos {
type Output = Self;
fn add(self, rhs: ScreenPos) -> Self::Output {
LocalPos {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl TryFrom<LocalPos> for ScreenPos {
type Error = <u16 as TryFrom<usize>>::Error;
fn try_from(value: LocalPos) -> std::result::Result<ScreenPos, Self::Error> {
let x = value.x;
let y = value.y;
Ok(ScreenPos::new(x, y))
}
}
impl TryFrom<Pos> for ScreenPos {
type Error = <i32 as TryFrom<usize>>::Error;
fn try_from(value: Pos) -> std::result::Result<ScreenPos, Self::Error> {
let x: u16 = value.x.try_into()?;
let y: u16 = value.y.try_into()?;
Ok(ScreenPos::new(x, y))
}
}