From 5a3dd95e7287c747833bd8be91a958652475b29e Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Tue, 15 Jul 2025 10:43:06 +0200 Subject: [PATCH 1/6] Add set_background_color on TuiBackend --- anathema-backend/src/tui/commands.rs | 41 ++++++++++++++++++++++++++++ anathema-backend/src/tui/mod.rs | 9 ++++++ anathema-backend/src/tui/screen.rs | 10 +++++++ examples/terminal_background.rs | 31 +++++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 anathema-backend/src/tui/commands.rs create mode 100644 examples/terminal_background.rs diff --git a/anathema-backend/src/tui/commands.rs b/anathema-backend/src/tui/commands.rs new file mode 100644 index 00000000..6e6482c6 --- /dev/null +++ b/anathema-backend/src/tui/commands.rs @@ -0,0 +1,41 @@ +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) + } + } +} + +/// 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 { + write!(f, "\x1b]111\x07") + } +} diff --git a/anathema-backend/src/tui/mod.rs b/anathema-backend/src/tui/mod.rs index f0e10ab8..da1a2385 100644 --- a/anathema-backend/src/tui/mod.rs +++ b/anathema-backend/src/tui/mod.rs @@ -8,6 +8,7 @@ use std::ops::Add; use std::time::Duration; use anathema_geometry::{LocalPos, Pos, Size}; +use anathema_state::Color; use anathema_value_resolver::AttributeStorage; use anathema_widgets::components::events::Event; pub use anathema_widgets::{Attributes, Style}; @@ -22,6 +23,7 @@ use self::events::Events; use crate::Backend; mod buffer; +mod commands; /// Events pub mod events; mod screen; @@ -144,6 +146,13 @@ 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. + 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..7efa0d13 --- /dev/null +++ b/examples/terminal_background.rs @@ -0,0 +1,31 @@ +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(); + builder + .finish(&mut backend, |runtime, backend| runtime.run(backend)) + .unwrap(); +} From 22abe14367e9a8eacc917bcac3f59443cfff652c Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Tue, 15 Jul 2025 13:51:38 +0200 Subject: [PATCH 2/6] add missing windows methods --- anathema-backend/src/tui/commands.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/anathema-backend/src/tui/commands.rs b/anathema-backend/src/tui/commands.rs index 6e6482c6..b3b3a351 100644 --- a/anathema-backend/src/tui/commands.rs +++ b/anathema-backend/src/tui/commands.rs @@ -24,6 +24,11 @@ impl Command for SetTerminalBackground { 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. @@ -38,4 +43,9 @@ impl Command for ResetTerminalBackground { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, "\x1b]111\x07") } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { + Ok(()) + } } From 047f973291616da27ea04bc60d4669e8dce824b3 Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Tue, 15 Jul 2025 14:21:54 +0200 Subject: [PATCH 3/6] Windows returns an Err for 'stopping'. Ignore that. --- examples/terminal_background.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/terminal_background.rs b/examples/terminal_background.rs index 7efa0d13..cc523b8d 100644 --- a/examples/terminal_background.rs +++ b/examples/terminal_background.rs @@ -25,7 +25,5 @@ fn main() { let mut builder = Runtime::builder(doc, &backend); builder.template("index", template.to_template()).unwrap(); - builder - .finish(&mut backend, |runtime, backend| runtime.run(backend)) - .unwrap(); + let _ = builder.finish(&mut backend, |runtime, backend| runtime.run(backend)); } From 9773f668e85cf5bd50888370c48f1fd362a12f5a Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Tue, 15 Jul 2025 14:22:08 +0200 Subject: [PATCH 4/6] Add missing semicolon for OSC 111 --- anathema-backend/src/tui/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anathema-backend/src/tui/commands.rs b/anathema-backend/src/tui/commands.rs index b3b3a351..e2188860 100644 --- a/anathema-backend/src/tui/commands.rs +++ b/anathema-backend/src/tui/commands.rs @@ -41,7 +41,7 @@ pub struct ResetTerminalBackground(); impl Command for ResetTerminalBackground { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - write!(f, "\x1b]111\x07") + write!(f, "\x1b]111;\x07") } #[cfg(windows)] From b5ad116fda0b24a3062527f3788271fbb1350b24 Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Tue, 15 Jul 2025 14:33:48 +0200 Subject: [PATCH 5/6] Add osc feature flag --- Cargo.toml | 1 + anathema-backend/Cargo.toml | 1 + anathema-backend/src/tui/mod.rs | 2 ++ 3 files changed, 4 insertions(+) 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/mod.rs b/anathema-backend/src/tui/mod.rs index da1a2385..ee8657ab 100644 --- a/anathema-backend/src/tui/mod.rs +++ b/anathema-backend/src/tui/mod.rs @@ -8,6 +8,7 @@ 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; @@ -150,6 +151,7 @@ impl TuiBackend { /// 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); } From eaa1a2e71db8f95852401b817e1cd9706d403825 Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Fri, 25 Jul 2025 12:07:30 +0200 Subject: [PATCH 6/6] Make sure color is reset on different terminals --- anathema-backend/src/tui/commands.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/anathema-backend/src/tui/commands.rs b/anathema-backend/src/tui/commands.rs index e2188860..f3ad1dc3 100644 --- a/anathema-backend/src/tui/commands.rs +++ b/anathema-backend/src/tui/commands.rs @@ -41,6 +41,9 @@ 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") }