1use std::{ffi::CStr, mem::MaybeUninit, slice};
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}
224
225#[derive(Clone, Copy, Debug)]
227pub struct Palette(pub [RgbColor; 256]);
228
229pub(crate) type RawPalette = [ffi::ColorRgb; 256];
231
232impl From<RawPalette> for Palette {
233 fn from(v: RawPalette) -> Self {
234 Self(v.map(RgbColor::from))
235 }
236}
237impl From<Palette> for RawPalette {
238 fn from(v: Palette) -> Self {
239 v.0.map(ffi::ColorRgb::from)
240 }
241}
242
243impl Default for Palette {
244 fn default() -> Self {
249 let mut raw = [ffi::ColorRgb::default(); 256];
250 unsafe { ffi::ghostty_color_palette_default(raw.as_mut_ptr()) };
251 raw.into()
252 }
253}
254
255impl Palette {
256 pub fn get(&self, index: PaletteIndex) -> RgbColor {
258 self.0[index.0 as usize]
259 }
260
261 pub fn set(&mut self, index: PaletteIndex, color: RgbColor) {
263 self.0[index.0 as usize] = color;
264 }
265
266 pub fn parse_palette_entry(value: &str) -> Result<(PaletteIndex, RgbColor)> {
273 let mut index = MaybeUninit::uninit();
274 let mut rgb = MaybeUninit::uninit();
275 let result = unsafe {
276 ffi::ghostty_color_parse_palette_entry(
277 value.as_ptr().cast(),
278 value.len(),
279 index.as_mut_ptr(),
280 rgb.as_mut_ptr(),
281 )
282 };
283 crate::error::from_result(result)?;
284 Ok((
285 PaletteIndex(unsafe { index.assume_init() }),
286 unsafe { rgb.assume_init() }.into(),
287 ))
288 }
289
290 #[must_use]
303 pub fn generate(
304 base: Option<&Self>,
305 skip: Option<&PaletteMask>,
306 background: RgbColor,
307 foreground: RgbColor,
308 harmonious: bool,
309 ) -> Self {
310 let raw_base = base.map(|palette| palette.0.map(ffi::ColorRgb::from));
311 let base_ptr = raw_base
312 .as_ref()
313 .map_or(std::ptr::null(), |palette| palette.as_ptr());
314 let skip_ptr = skip
315 .as_ref()
316 .map_or(std::ptr::null(), |mask| &raw const mask.0);
317 let mut raw_out = [ffi::ColorRgb::default(); 256];
318 let bg: ffi::ColorRgb = background.into();
319 let fg: ffi::ColorRgb = foreground.into();
320
321 unsafe {
322 ffi::ghostty_color_palette_generate(
323 base_ptr,
324 skip_ptr,
325 &raw const bg,
326 &raw const fg,
327 harmonious,
328 raw_out.as_mut_ptr(),
329 );
330 }
331
332 raw_out.into()
333 }
334}
335
336#[derive(Clone, Copy, Debug, Default)]
341pub struct PaletteMask(ffi::ColorPaletteMask);
342
343impl PaletteMask {
344 #[must_use]
346 pub fn new() -> Self {
347 Self::default()
348 }
349
350 pub fn set(&mut self, index: PaletteIndex) {
352 let index = usize::from(index.0);
353 self.0.bits[index / 64] |= 1_u64 << (index % 64);
354 }
355
356 #[must_use]
358 pub fn is_set(self, index: PaletteIndex) -> bool {
359 let index = usize::from(index.0);
360 (self.0.bits[index / 64] & (1_u64 << (index % 64))) != 0
361 }
362}
363impl PartialEq for PaletteMask {
364 fn eq(&self, v: &Self) -> bool {
365 self.0.bits.eq(&v.0.bits)
366 }
367}
368impl Eq for PaletteMask {}
369
370impl RgbColor {
371 pub fn parse_x11_color(name: &str) -> Result<Self> {
377 let mut out = MaybeUninit::uninit();
378 let result = unsafe {
379 ffi::ghostty_color_parse_x11(name.as_ptr().cast(), name.len(), out.as_mut_ptr())
380 };
381 crate::error::from_result(result)?;
382 Ok(unsafe { out.assume_init() }.into())
383 }
384
385 pub fn parse(value: &str) -> Result<Self> {
393 let mut out = MaybeUninit::uninit();
394 let result = unsafe {
395 ffi::ghostty_color_parse(value.as_ptr().cast(), value.len(), out.as_mut_ptr())
396 };
397 crate::error::from_result(result)?;
398 Ok(unsafe { out.assume_init() }.into())
399 }
400
401 #[must_use]
406 pub fn luminance(self) -> f64 {
407 let this: ffi::ColorRgb = self.into();
408 unsafe { ffi::ghostty_color_luminance(&raw const this) }
409 }
410
411 #[must_use]
417 pub fn perceived_luminance(self) -> f64 {
418 let this: ffi::ColorRgb = self.into();
419 unsafe { ffi::ghostty_color_perceived_luminance(&raw const this) }
420 }
421
422 #[must_use]
427 pub fn contrast(self, other: RgbColor) -> f64 {
428 let this: ffi::ColorRgb = self.into();
429 let that: ffi::ColorRgb = other.into();
430 unsafe { ffi::ghostty_color_contrast(&raw const this, &raw const that) }
431 }
432}
433
434#[derive(Clone, Copy, Debug, PartialEq, Eq)]
436pub struct X11ColorName {
437 pub name: &'static CStr,
439 pub color: RgbColor,
441}
442
443#[derive(Clone, Copy, Debug)]
447pub struct X11ColorNames {
448 entries: &'static [ffi::ColorX11Entry],
449}
450
451impl Default for X11ColorNames {
452 fn default() -> Self {
453 Self::new()
454 }
455}
456
457#[derive(Clone, Debug)]
459pub struct X11ColorNamesIter {
460 entries: slice::Iter<'static, ffi::ColorX11Entry>,
461}
462
463impl X11ColorNames {
464 #[must_use]
471 pub fn new() -> Self {
472 let ptr = unsafe { ffi::ghostty_color_x11_names() };
473 let len = unsafe { ffi::ghostty_color_x11_name_count() };
474 let entries = unsafe { slice::from_raw_parts(ptr, len) };
475 Self { entries }
476 }
477
478 #[must_use]
482 pub fn len(self) -> usize {
483 self.entries.len()
484 }
485
486 #[must_use]
488 pub fn is_empty(self) -> bool {
489 self.entries.is_empty()
490 }
491
492 #[must_use]
494 pub fn get(self, index: usize) -> Option<X11ColorName> {
495 self.entries.get(index).map(x11_color_name_from_entry)
496 }
497
498 pub fn iter(self) -> X11ColorNamesIter {
500 X11ColorNamesIter {
501 entries: self.entries.iter(),
502 }
503 }
504}
505
506impl IntoIterator for X11ColorNames {
507 type Item = X11ColorName;
508 type IntoIter = X11ColorNamesIter;
509
510 fn into_iter(self) -> Self::IntoIter {
511 self.iter()
512 }
513}
514
515impl Iterator for X11ColorNamesIter {
516 type Item = X11ColorName;
517
518 fn next(&mut self) -> Option<Self::Item> {
519 self.entries.next().map(x11_color_name_from_entry)
520 }
521
522 fn size_hint(&self) -> (usize, Option<usize>) {
523 self.entries.size_hint()
524 }
525}
526impl DoubleEndedIterator for X11ColorNamesIter {
527 fn next_back(&mut self) -> Option<Self::Item> {
528 self.entries.next_back().map(x11_color_name_from_entry)
529 }
530}
531impl ExactSizeIterator for X11ColorNamesIter {}
532impl std::iter::FusedIterator for X11ColorNamesIter {}
533
534fn x11_color_name_from_entry(entry: &ffi::ColorX11Entry) -> X11ColorName {
535 let name = unsafe { CStr::from_ptr(entry.name) };
536 X11ColorName {
537 name,
538 color: entry.color.into(),
539 }
540}