1use std::mem::MaybeUninit;
7
8use crate::{
9 error::{Error, Result},
10 ffi,
11};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct Id(pub(crate) ffi::StyleId);
19
20#[expect(
26 clippy::struct_excessive_bools,
27 reason = "style attributes should be just a bunch of bools"
28)]
29#[expect(missing_docs, reason = "self-explanatory")]
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct Style {
32 pub fg_color: StyleColor,
33 pub bg_color: StyleColor,
34 pub underline_color: StyleColor,
35 pub bold: bool,
36 pub italic: bool,
37 pub faint: bool,
38 pub blink: bool,
39 pub inverse: bool,
40 pub invisible: bool,
41 pub strikethrough: bool,
42 pub overline: bool,
43 pub underline: Underline,
44}
45
46impl Style {
47 #[must_use]
51 pub fn is_default(self) -> bool {
52 let raw = ffi::Style::from(self);
53 unsafe { ffi::ghostty_style_is_default(&raw const raw) }
54 }
55}
56
57impl Default for Style {
58 fn default() -> Self {
59 let mut style = MaybeUninit::zeroed();
60 unsafe {
61 ffi::ghostty_style_default(style.as_mut_ptr());
62 }
63
64 Self::try_from(unsafe { style.assume_init() })
66 .expect("ghostty_style_default to init valid Style")
67 }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub enum StyleColor {
73 None,
75 Palette(PaletteIndex),
77 Rgb(RgbColor),
79}
80
81#[repr(C)]
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
84pub struct RgbColor {
85 pub r: u8,
87 pub g: u8,
89 pub b: u8,
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
95pub struct PaletteIndex(pub ffi::ColorPaletteIndex);
96
97impl PaletteIndex {
98 #![expect(missing_docs, reason = "self-explanatory")]
99 pub const BLACK: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BLACK);
100 pub const RED: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_RED);
101 pub const GREEN: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_GREEN);
102 pub const YELLOW: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_YELLOW);
103 pub const BLUE: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BLUE);
104 pub const MAGENTA: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_MAGENTA);
105 pub const CYAN: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_CYAN);
106 pub const WHITE: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_WHITE);
107 pub const BRIGHT_BLACK: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_BLACK);
108 pub const BRIGHT_RED: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_RED);
109 pub const BRIGHT_GREEN: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_GREEN);
110 pub const BRIGHT_YELLOW: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_YELLOW);
111 pub const BRIGHT_BLUE: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_BLUE);
112 pub const BRIGHT_MAGENTA: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_MAGENTA);
113 pub const BRIGHT_CYAN: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_CYAN);
114 pub const BRIGHT_WHITE: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_WHITE);
115}
116
117#[repr(u32)]
119#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, int_enum::IntEnum)]
120#[non_exhaustive]
121#[expect(missing_docs, reason = "self-explanatory")]
122pub enum Underline {
123 None = ffi::SgrUnderline::NONE,
124 Single = ffi::SgrUnderline::SINGLE,
125 Double = ffi::SgrUnderline::DOUBLE,
126 Curly = ffi::SgrUnderline::CURLY,
127 Dotted = ffi::SgrUnderline::DOTTED,
128 Dashed = ffi::SgrUnderline::DASHED,
129}
130
131impl TryFrom<ffi::Style> for Style {
136 type Error = Error;
137 fn try_from(value: ffi::Style) -> Result<Self> {
138 Ok(Self {
139 fg_color: StyleColor::try_from(value.fg_color)?,
140 bg_color: StyleColor::try_from(value.bg_color)?,
141 underline_color: StyleColor::try_from(value.underline_color)?,
142 bold: value.bold,
143 italic: value.italic,
144 faint: value.faint,
145 blink: value.blink,
146 inverse: value.inverse,
147 invisible: value.invisible,
148 strikethrough: value.strikethrough,
149 overline: value.overline,
150 #[expect(clippy::cast_sign_loss, reason = "bindgen ain't perfect")]
151 underline: Underline::try_from(value.underline as u32)
152 .map_err(|_| Error::InvalidValue)?,
153 })
154 }
155}
156
157impl From<Style> for ffi::Style {
158 fn from(value: Style) -> Self {
159 Self {
160 size: std::mem::size_of::<Self>(),
161 fg_color: value.fg_color.into(),
162 bg_color: value.bg_color.into(),
163 underline_color: value.underline_color.into(),
164 bold: value.bold,
165 italic: value.italic,
166 faint: value.faint,
167 blink: value.blink,
168 inverse: value.inverse,
169 invisible: value.invisible,
170 strikethrough: value.strikethrough,
171 overline: value.overline,
172 #[expect(clippy::cast_possible_wrap, reason = "bindgen ain't perfect")]
173 underline: u32::from(value.underline) as i32,
174 }
175 }
176}
177
178impl TryFrom<ffi::StyleColor> for StyleColor {
179 type Error = Error;
180 fn try_from(value: ffi::StyleColor) -> Result<Self> {
181 Ok(match value.tag {
182 ffi::StyleColorTag::NONE => Self::None,
183 ffi::StyleColorTag::PALETTE => {
184 Self::Palette(PaletteIndex(unsafe { value.value.palette }))
185 }
186 ffi::StyleColorTag::RGB => Self::Rgb(unsafe { value.value.rgb }.into()),
187 _ => return Err(Error::InvalidValue),
188 })
189 }
190}
191
192impl From<StyleColor> for ffi::StyleColor {
193 fn from(value: StyleColor) -> Self {
194 match value {
195 StyleColor::None => Self {
196 tag: ffi::StyleColorTag::NONE,
197 value: ffi::StyleColorValue::default(),
198 },
199 StyleColor::Palette(PaletteIndex(palette)) => Self {
200 tag: ffi::StyleColorTag::PALETTE,
201 value: ffi::StyleColorValue { palette },
202 },
203 StyleColor::Rgb(rgb) => Self {
204 tag: ffi::StyleColorTag::RGB,
205 value: ffi::StyleColorValue { rgb: rgb.into() },
206 },
207 }
208 }
209}
210
211impl From<ffi::ColorRgb> for RgbColor {
212 fn from(value: ffi::ColorRgb) -> Self {
213 let ffi::ColorRgb { r, g, b } = value;
214 Self { r, g, b }
215 }
216}
217
218impl From<RgbColor> for ffi::ColorRgb {
219 fn from(value: RgbColor) -> Self {
220 let RgbColor { r, g, b } = value;
221 Self { r, g, b }
222 }
223}