use std::time::Duration; use crossterm::style::ContentStyle; use serde::{Deserialize, Serialize}; use tortuise::script::Operation; use crate::ScriptContext; #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct Cell { pub character: char, pub foreground_color: Color, pub background_color: Color, pub underline_color: Color, pub attributes: Attributes, pub x: u16, pub y: u16, pub z: u8, } #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct WindowSize { pub rows: u16, pub columns: u16, } #[derive( Copy, Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, )] pub enum Color { /// Resets the terminal color. #[default] Reset, /// Black color. Black, /// Dark grey color. DarkGrey, /// Light red color. Red, /// Dark red color. DarkRed, /// Light green color. Green, /// Dark green color. DarkGreen, /// Light yellow color. Yellow, /// Dark yellow color. DarkYellow, /// Light blue color. Blue, /// Dark blue color. DarkBlue, /// Light magenta color. Magenta, /// Dark magenta color. DarkMagenta, /// Light cyan color. Cyan, /// Dark cyan color. DarkCyan, /// White color. White, /// Grey color. Grey, /// An RGB color. See [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) for more info. /// /// Most UNIX terminals and Windows 10 supported only. /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info. Rgb { r: u8, g: u8, b: u8 }, /// An ANSI color. See [256 colors - cheat sheet](https://jonasjacek.github.io/colors/) for more info. /// /// Most UNIX terminals and Windows 10 supported only. /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info. AnsiValue(u8), } #[derive( Copy, Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize, )] pub struct Attributes(u32); pub enum Attribute { /// Increases the text intensity. Bold = 1, /// Decreases the text intensity. Dim = 2, /// Emphasises the text. Italic = 4, /// Underlines the text. Underlined = 8, // Other types of underlining /// Double underlines the text. DoubleUnderlined = 16, /// Undercurls the text. Undercurled = 32, /// Underdots the text. Underdotted = 64, /// Underdashes the text. Underdashed = 128, /// Makes the text blinking (< 150 per minute). SlowBlink = 256, /// Makes the text blinking (>= 150 per minute). RapidBlink = 512, /// Swaps foreground and background colors. Reverse = 1024, /// Hides the text (also known as Conceal). Hidden = 2048, /// Crosses the text. CrossedOut = 4096, /// Sets the [Fraktur](https://en.wikipedia.org/wiki/Fraktur) typeface. /// /// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols). Fraktur = 8192, /// Makes the text framed. Framed = 16384, /// Makes the text encircled. Encircled = 32768, /// Draws a line at the top of the text. OverLined = 65536, } impl Attributes { pub fn none() -> Self { Self(0) } pub fn with(self, attribute: Attribute) -> Self { Self(self.0 | attribute as u32) } pub fn without(self, attribute: Attribute) -> Self { Self(self.0 & !(attribute as u32)) } pub fn set(&mut self, attribute: Attribute) { self.0 |= attribute as u32; } pub fn unset(&mut self, attribute: Attribute) { self.0 &= !(attribute as u32) } pub fn has(self, attribute: Attribute) -> bool { self.0 & (attribute as u32) != 0 } pub fn extend(&mut self, attributes: Attributes) { self.0 |= attributes.0 } pub fn is_empty(self) -> bool { self.0 == 0 } } impl From for crossterm::style::Color { fn from(value: Color) -> Self { match value { Color::Reset => crossterm::style::Color::Reset, Color::Black => crossterm::style::Color::Black, Color::DarkGrey => crossterm::style::Color::DarkGrey, Color::Red => crossterm::style::Color::Red, Color::DarkRed => crossterm::style::Color::DarkRed, Color::Green => crossterm::style::Color::Green, Color::DarkGreen => crossterm::style::Color::DarkGreen, Color::Yellow => crossterm::style::Color::Yellow, Color::DarkYellow => crossterm::style::Color::DarkYellow, Color::Blue => crossterm::style::Color::Blue, Color::DarkBlue => crossterm::style::Color::DarkBlue, Color::Magenta => crossterm::style::Color::Magenta, Color::DarkMagenta => crossterm::style::Color::DarkMagenta, Color::Cyan => crossterm::style::Color::Cyan, Color::DarkCyan => crossterm::style::Color::DarkCyan, Color::White => crossterm::style::Color::White, Color::Grey => crossterm::style::Color::Grey, Color::Rgb { r, g, b } => crossterm::style::Color::Rgb { r, g, b }, Color::AnsiValue(v) => crossterm::style::Color::AnsiValue(v), } } } impl From for crossterm::style::Attributes { fn from(value: Attributes) -> Self { let mut new = crossterm::style::Attributes::none(); if value.has(Attribute::Bold) { new.set(crossterm::style::Attribute::Bold); } if value.has(Attribute::Dim) { new.set(crossterm::style::Attribute::Dim); } if value.has(Attribute::Italic) { new.set(crossterm::style::Attribute::Italic); } if value.has(Attribute::Underlined) { new.set(crossterm::style::Attribute::Underlined); } if value.has(Attribute::DoubleUnderlined) { new.set(crossterm::style::Attribute::DoubleUnderlined); } if value.has(Attribute::Undercurled) { new.set(crossterm::style::Attribute::Undercurled); } if value.has(Attribute::Underdotted) { new.set(crossterm::style::Attribute::Underdotted); } if value.has(Attribute::Underdashed) { new.set(crossterm::style::Attribute::Underdashed); } if value.has(Attribute::SlowBlink) { new.set(crossterm::style::Attribute::SlowBlink); } if value.has(Attribute::RapidBlink) { new.set(crossterm::style::Attribute::RapidBlink); } if value.has(Attribute::Reverse) { new.set(crossterm::style::Attribute::Reverse); } if value.has(Attribute::Hidden) { new.set(crossterm::style::Attribute::Hidden); } if value.has(Attribute::CrossedOut) { new.set(crossterm::style::Attribute::CrossedOut); } if value.has(Attribute::Fraktur) { new.set(crossterm::style::Attribute::Fraktur); } if value.has(Attribute::Framed) { new.set(crossterm::style::Attribute::Framed); } if value.has(Attribute::Encircled) { new.set(crossterm::style::Attribute::Encircled); } if value.has(Attribute::OverLined) { new.set(crossterm::style::Attribute::OverLined); } new } } impl ScriptContext<'_> { pub fn add_cell(&mut self, cell: Cell) { puffin::profile_function!(); self.0 .operation_queue .push(Operation::AddCell(tortuise::buffer::Cell { character: cell.character, style: ContentStyle { foreground_color: Some(cell.foreground_color.into()), background_color: Some(cell.background_color.into()), underline_color: Some(cell.underline_color.into()), attributes: cell.attributes.into(), }, x: cell.x, y: cell.y, z: cell.z, })); } pub fn window_size(&self) -> WindowSize { puffin::profile_function!(); let size = &self.0.buffer.window_size; WindowSize { rows: size.rows, columns: size.columns, } } pub fn set_framerate_limit(&mut self, limit: Option) { puffin::profile_function!(); let limit = limit.map(|limit| Duration::from_nanos(1_000_000_000 / limit as u64)); self.0.operation_queue.push(Operation::SetFrameLimit(limit)); } }