use crate::{AnsiColors, Color, DynColor, DynColors};
use core::fmt;
#[cfg(doc)]
use crate::OwoColorize;
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone)]
pub enum Effect {
Bold,
Dimmed,
Italic,
Underline,
Blink,
BlinkFast,
Reversed,
Hidden,
Strikethrough,
}
macro_rules! color_methods {
($(
#[$fg_meta:meta] #[$bg_meta:meta] $color:ident $fg_method:ident $bg_method:ident
),* $(,)?) => {
$(
#[$fg_meta]
#[must_use]
pub fn $fg_method(mut self) -> Self {
self.fg = Some(DynColors::Ansi(AnsiColors::$color));
self
}
#[$fg_meta]
#[must_use]
pub fn $bg_method(mut self) -> Self {
self.bg = Some(DynColors::Ansi(AnsiColors::$color));
self
}
)*
};
}
macro_rules! style_methods {
($(#[$meta:meta] ($name:ident, $set_name:ident)),* $(,)?) => {
$(
#[$meta]
#[must_use]
pub fn $name(mut self) -> Self {
self.style_flags.$set_name(true);
self
}
)*
};
}
const _: () = ();
pub struct Styled<T> {
target: T,
style: Style,
}
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct Style {
fg: Option<DynColors>,
bg: Option<DynColors>,
bold: bool,
style_flags: StyleFlags,
}
#[repr(transparent)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
struct StyleFlags(u8);
const DIMMED_SHIFT: u8 = 0;
const ITALIC_SHIFT: u8 = 1;
const UNDERLINE_SHIFT: u8 = 2;
const BLINK_SHIFT: u8 = 3;
const BLINK_FAST_SHIFT: u8 = 4;
const REVERSED_SHIFT: u8 = 5;
const HIDDEN_SHIFT: u8 = 6;
const STRIKETHROUGH_SHIFT: u8 = 7;
macro_rules! style_flags_methods {
($(($shift:ident, $name:ident, $set_name:ident)),* $(,)?) => {
$(
fn $name(&self) -> bool {
((self.0 >> $shift) & 1) != 0
}
fn $set_name(&mut self, $name: bool) {
self.0 = (self.0 & !(1 << $shift)) | (($name as u8) << $shift);
}
)*
};
}
impl StyleFlags {
style_flags_methods! {
(DIMMED_SHIFT, dimmed, set_dimmed),
(ITALIC_SHIFT, italic, set_italic),
(UNDERLINE_SHIFT, underline, set_underline),
(BLINK_SHIFT, blink, set_blink),
(BLINK_FAST_SHIFT, blink_fast, set_blink_fast),
(REVERSED_SHIFT, reversed, set_reversed),
(HIDDEN_SHIFT, hidden, set_hidden),
(STRIKETHROUGH_SHIFT, strikethrough, set_strikethrough),
}
}
impl Style {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn style<T>(&self, target: T) -> Styled<T> {
Styled {
target,
style: *self,
}
}
#[must_use]
pub fn fg<C: Color>(mut self) -> Self {
self.fg = Some(C::into_dyncolors());
self
}
#[must_use]
pub fn bg<C: Color>(mut self) -> Self {
self.bg = Some(C::into_dyncolors());
self
}
#[must_use]
pub fn remove_fg(mut self) -> Self {
self.fg = None;
self
}
#[must_use]
pub fn remove_bg(mut self) -> Self {
self.bg = None;
self
}
color_methods! {
Black black on_black,
Red red on_red,
Green green on_green,
Yellow yellow on_yellow,
Blue blue on_blue,
Magenta magenta on_magenta,
Magenta purple on_purple,
Cyan cyan on_cyan,
White white on_white,
Default default_color on_default_color,
BrightBlack bright_black on_bright_black,
BrightRed bright_red on_bright_red,
BrightGreen bright_green on_bright_green,
BrightYellow bright_yellow on_bright_yellow,
BrightBlue bright_blue on_bright_blue,
BrightMagenta bright_magenta on_bright_magenta,
BrightMagenta bright_purple on_bright_purple,
BrightCyan bright_cyan on_bright_cyan,
BrightWhite bright_white on_bright_white,
}
#[must_use]
pub fn bold(mut self) -> Self {
self.bold = true;
self
}
style_methods! {
(dimmed, set_dimmed),
(italic, set_italic),
(underline, set_underline),
(blink, set_blink),
(blink_fast, set_blink_fast),
(reversed, set_reversed),
(hidden, set_hidden),
(strikethrough, set_strikethrough),
}
fn set_effect(&mut self, effect: Effect, to: bool) {
use Effect::*;
match effect {
Bold => self.bold = to,
Dimmed => self.style_flags.set_dimmed(to),
Italic => self.style_flags.set_italic(to),
Underline => self.style_flags.set_underline(to),
Blink => self.style_flags.set_blink(to),
BlinkFast => self.style_flags.set_blink_fast(to),
Reversed => self.style_flags.set_reversed(to),
Hidden => self.style_flags.set_hidden(to),
Strikethrough => self.style_flags.set_strikethrough(to),
}
}
fn set_effects(&mut self, effects: &[Effect], to: bool) {
for e in effects {
self.set_effect(*e, to)
}
}
#[must_use]
pub fn effect(mut self, effect: Effect) -> Self {
self.set_effect(effect, true);
self
}
#[must_use]
pub fn remove_effect(mut self, effect: Effect) -> Self {
self.set_effect(effect, false);
self
}
#[must_use]
pub fn effects(mut self, effects: &[Effect]) -> Self {
self.set_effects(effects, true);
self
}
#[must_use]
pub fn remove_effects(mut self, effects: &[Effect]) -> Self {
self.set_effects(effects, false);
self
}
#[must_use]
pub fn remove_all_effects(mut self) -> Self {
self.bold = false;
self.style_flags = StyleFlags::default();
self
}
#[must_use]
pub fn color<Color: DynColor>(mut self, color: Color) -> Self {
self.fg = Some(color.get_dyncolors_fg());
self
}
#[must_use]
pub fn on_color<Color: DynColor>(mut self, color: Color) -> Self {
self.bg = Some(color.get_dyncolors_bg());
self
}
#[must_use]
pub fn fg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self {
self.fg = Some(DynColors::Rgb(R, G, B));
self
}
#[must_use]
pub fn bg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self {
self.bg = Some(DynColors::Rgb(R, G, B));
self
}
#[must_use]
pub fn truecolor(mut self, r: u8, g: u8, b: u8) -> Self {
self.fg = Some(DynColors::Rgb(r, g, b));
self
}
#[must_use]
pub fn on_truecolor(mut self, r: u8, g: u8, b: u8) -> Self {
self.bg = Some(DynColors::Rgb(r, g, b));
self
}
}
pub fn style() -> Style {
Style::new()
}
macro_rules! text_effect_fmt {
($style:ident, $formatter:ident, $semicolon:ident, $(($attr:ident, $value:literal)),* $(,)?) => {
$(
if $style.style_flags.$attr() {
if $semicolon {
$formatter.write_str(";")?;
}
$formatter.write_str($value)?;
$semicolon = true;
}
)+
}
}
macro_rules! impl_fmt {
($($trait:path),* $(,)?) => {
$(
impl<T: $trait> $trait for Styled<T> {
#[allow(unused_assignments)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = &self.style;
let format_less_important_effects = s.style_flags != StyleFlags::default();
let format_effect = s.bold || format_less_important_effects;
let format_color = s.fg.is_some() || s.bg.is_some();
let format_any = format_color || format_effect;
let mut semicolon = false;
if format_any {
f.write_str("\x1b[")?;
}
if let Some(fg) = s.fg {
<DynColors as DynColor>::fmt_raw_ansi_fg(&fg, f)?;
semicolon = true;
}
if let Some(bg) = s.bg {
if s.fg.is_some() {
f.write_str(";")?;
}
<DynColors as DynColor>::fmt_raw_ansi_bg(&bg, f)?;
}
if format_effect {
if s.bold {
if semicolon {
f.write_str(";")?;
}
f.write_str("1")?;
semicolon = true;
}
if format_less_important_effects {
text_effect_fmt!{
s, f, semicolon,
(dimmed, "2"),
(italic, "3"),
(underline, "4"),
(blink, "5"),
(blink_fast, "6"),
(reversed, "7"),
(hidden, "8"),
(strikethrough, "9"),
}
}
}
if format_any {
f.write_str("m")?;
}
<T as $trait>::fmt(&self.target, f)?;
if format_any {
f.write_str("\x1b[0m")?;
}
Ok(())
}
}
)*
};
}
impl_fmt! {
fmt::Display,
fmt::Debug,
fmt::UpperHex,
fmt::LowerHex,
fmt::Binary,
fmt::UpperExp,
fmt::LowerExp,
fmt::Octal,
fmt::Pointer,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AnsiColors, OwoColorize};
#[test]
fn test_it() {
let style = Style::new()
.bright_white()
.on_blue()
.bold()
.dimmed()
.italic()
.underline()
.blink()
.strikethrough();
let s = style.style("TEST");
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[97;44;1;2;3;4;5;9mTEST\u{1b}[0m");
}
#[test]
fn test_effects() {
use Effect::*;
let style = Style::new().effects(&[Strikethrough, Underline]);
let s = style.style("TEST");
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[4;9mTEST\u{1b}[0m");
}
#[test]
fn test_color() {
let style = Style::new()
.color(AnsiColors::White)
.on_color(AnsiColors::Black);
let s = style.style("TEST");
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[37;40mTEST\u{1b}[0m");
}
#[test]
fn test_truecolor() {
let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0);
let s = style.style("TEST");
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[38;2;255;255;255;48;2;0;0;0mTEST\u{1b}[0m");
}
#[test]
fn test_string_reference() {
let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0);
let string = String::from("TEST");
let s = style.style(&string);
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[38;2;255;255;255;48;2;0;0;0mTEST\u{1b}[0m");
}
#[test]
fn test_owocolorize() {
let style = Style::new().bright_white().on_blue();
let s = "TEST".style(style);
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[97;44mTEST\u{1b}[0m");
}
}