diff --git a/Cargo.toml b/Cargo.toml index 06a585c8..d2bd7f5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ anathema-testutils = { path = "anathema-testutils" } default = [] profile = ["anathema-runtime/profile", "anathema-widgets/profile", "anathema-backend/profile"] serde = ["anathema-state/serde", "anathema-store/serde"] +osc = ["anathema-backend/osc"] # filelog = ["anathema-debug/filelog", "anathema-widgets/filelog", "anathema-runtime/filelog"] [lints] diff --git a/anathema-backend/Cargo.toml b/anathema-backend/Cargo.toml index 7721c76a..e41ee831 100644 --- a/anathema-backend/Cargo.toml +++ b/anathema-backend/Cargo.toml @@ -23,6 +23,7 @@ puffin = { version = "0.19.1", optional = true } [features] default = [] profile = ["puffin"] +osc = [] [lints] workspace = true diff --git a/anathema-backend/src/tui/commands.rs b/anathema-backend/src/tui/commands.rs new file mode 100644 index 00000000..f3ad1dc3 --- /dev/null +++ b/anathema-backend/src/tui/commands.rs @@ -0,0 +1,54 @@ +use std::fmt; + +use anathema_state::Color; +use crossterm::Command; + +/// A command that sets the the background color of the entire terminal using OSC 11. +/// This might not work on all terminals. When it is not supported it will do nothing. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetTerminalBackground(pub Color); + +impl Command for SetTerminalBackground { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + if let Color::Rgb(r, g, b) = self.0 { + // OSC 11 format for RGB is 'rgb:RR/GG/BB' in hexadecimal format + return write!(f, "\x1b]11;rgb:{:02x}/{:02x}/{:02x}\x07", r, g, b); + } else if let Color::AnsiVal(_) = self.0 { + // Ansi values are not supported by OSC 11 + Ok(()) + } else { + write!(f, "\x1b]11;{}\x07", self.0) + } + } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { + Ok(()) + } +} + +/// A command that resets the the background color with OSC 111. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ResetTerminalBackground(); + +impl Command for ResetTerminalBackground { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + // Some terminals require a ; at the end of the command + // to reset the background color, while some do not work with it. + let _ = write!(f, "\x1b]111\x07"); + write!(f, "\x1b]111;\x07") + } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/anathema-backend/src/tui/mod.rs b/anathema-backend/src/tui/mod.rs index f0e10ab8..ee8657ab 100644 --- a/anathema-backend/src/tui/mod.rs +++ b/anathema-backend/src/tui/mod.rs @@ -8,6 +8,8 @@ use std::ops::Add; use std::time::Duration; use anathema_geometry::{LocalPos, Pos, Size}; +#[cfg(feature = "osc")] +use anathema_state::Color; use anathema_value_resolver::AttributeStorage; use anathema_widgets::components::events::Event; pub use anathema_widgets::{Attributes, Style}; @@ -22,6 +24,7 @@ use self::events::Events; use crate::Backend; mod buffer; +mod commands; /// Events pub mod events; mod screen; @@ -144,6 +147,14 @@ impl TuiBackend { let _ = Screen::disable_raw_mode(); self } + + /// Set the background color of the terminal using OSC 11. + /// Might not work on all terminals. Ansi values (0-255) are not supported. + /// Use RGB or named colors. + #[cfg(feature = "osc")] + pub fn set_background_color(&mut self, color: Color) { + let _ = Screen::set_terminal_background_color(&mut self.output, color); + } } impl Backend for TuiBackend { diff --git a/anathema-backend/src/tui/screen.rs b/anathema-backend/src/tui/screen.rs index daa5d72a..8211ebb2 100644 --- a/anathema-backend/src/tui/screen.rs +++ b/anathema-backend/src/tui/screen.rs @@ -1,6 +1,7 @@ use std::io::{Result, Write}; use anathema_geometry::{Pos, Size}; +use anathema_state::Color; use anathema_value_resolver::Attributes; use anathema_widgets::paint::Glyph; use anathema_widgets::{GlyphMap, Style, WidgetRenderer}; @@ -8,6 +9,8 @@ use crossterm::event::EnableMouseCapture; use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}; use crossterm::{ExecutableCommand, QueueableCommand, cursor}; +use super::commands::{ResetTerminalBackground, SetTerminalBackground}; + use super::LocalPos; use super::buffer::{Buffer, Change, diff, draw_changes}; @@ -105,6 +108,12 @@ impl Screen { Ok(()) } + /// Set Terminal background color + pub fn set_terminal_background_color(mut output: impl Write, color: Color) -> Result<()> { + output.queue(SetTerminalBackground(color))?; + Ok(()) + } + /// Enter an alternative screen. /// When using this with stdout it means the output will not persist once the program exits. pub fn enter_alt_screen(mut output: impl Write) -> Result<()> { @@ -134,6 +143,7 @@ impl Screen { #[cfg(not(target_os = "windows"))] output.execute(crossterm::event::DisableMouseCapture)?; output.execute(cursor::Show)?; + output.execute(ResetTerminalBackground())?; Ok(()) } } diff --git a/examples/terminal_background.rs b/examples/terminal_background.rs new file mode 100644 index 00000000..cc523b8d --- /dev/null +++ b/examples/terminal_background.rs @@ -0,0 +1,29 @@ +use std::fs::read_to_string; + +use anathema::backend::Backend; +use anathema::backend::tui::TuiBackend; +use anathema::runtime::Runtime; +use anathema::state::Color; +use anathema::templates::{Document, ToSourceKind}; + +fn main() { + let template = read_to_string("examples/templates/basic/basic.aml").unwrap(); + + let doc = Document::new("@index"); + + let mut backend = TuiBackend::builder() + .enable_alt_screen() + .enable_raw_mode() + .hide_cursor() + .finish() + .unwrap(); + backend.finalize(); + + // This is not set on the builder as it can be called at any time + // to change the background color of the terminal. + backend.set_background_color(Color::Rgb(135, 105, 20)); + + let mut builder = Runtime::builder(doc, &backend); + builder.template("index", template.to_template()).unwrap(); + let _ = builder.finish(&mut backend, |runtime, backend| runtime.run(backend)); +}