use std::{convert::Into, marker::PhantomData, mem::MaybeUninit};
use crate::{
alloc::{Allocator, Object},
error::{Error, Result, from_result},
ffi,
screen::{Cell, Row},
style::{RgbColor, Style},
terminal::Terminal,
};
#[derive(Debug)]
pub struct RenderState<'alloc>(Object<'alloc, ffi::GhosttyRenderState>);
#[derive(Debug)]
pub struct Snapshot<'alloc, 's>(&'s mut RenderState<'alloc>);
#[derive(Debug)]
pub struct RowIterator<'alloc>(Object<'alloc, ffi::GhosttyRenderStateRowIterator>);
#[derive(Debug)]
pub struct RowIteration<'alloc, 's> {
iter: &'s mut RowIterator<'alloc>,
_phan: PhantomData<&'s Snapshot<'alloc, 's>>,
}
#[derive(Debug)]
pub struct CellIterator<'alloc>(Object<'alloc, ffi::GhosttyRenderStateRowCells>);
#[derive(Debug)]
pub struct CellIteration<'alloc, 's> {
iter: &'s mut CellIterator<'alloc>,
_phan: PhantomData<&'s RowIteration<'alloc, 's>>,
}
impl<'alloc> RenderState<'alloc> {
pub fn new() -> Result<Self> {
unsafe { Self::new_inner(std::ptr::null()) }
}
pub fn new_with_alloc<'ctx: 'alloc, Ctx>(alloc: &'alloc Allocator<'ctx, Ctx>) -> Result<Self> {
unsafe { Self::new_inner(alloc.to_raw()) }
}
unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator) -> Result<Self> {
let mut raw: ffi::GhosttyRenderState_ptr = std::ptr::null_mut();
let result = unsafe { ffi::ghostty_render_state_new(alloc, &raw mut raw) };
from_result(result)?;
Ok(Self(Object::new(raw)?))
}
pub fn update<'cb>(
&mut self,
terminal: &Terminal<'alloc, 'cb>,
) -> Result<Snapshot<'alloc, '_>> {
let result =
unsafe { ffi::ghostty_render_state_update(self.0.as_raw(), terminal.inner.as_raw()) };
from_result(result)?;
Ok(Snapshot(self))
}
}
impl Drop for RenderState<'_> {
fn drop(&mut self) {
unsafe { ffi::ghostty_render_state_free(self.0.as_raw()) }
}
}
impl Snapshot<'_, '_> {
fn get<T>(&self, tag: ffi::GhosttyRenderStateData) -> Result<T> {
let mut value = MaybeUninit::<T>::zeroed();
let result = unsafe {
ffi::ghostty_render_state_get(self.0.0.as_raw(), tag, value.as_mut_ptr().cast())
};
from_result(result)?;
Ok(unsafe { value.assume_init() })
}
fn set<T>(&self, tag: ffi::GhosttyRenderStateOption, value: &T) -> Result<()> {
let result = unsafe {
ffi::ghostty_render_state_set(self.0.0.as_raw(), tag, std::ptr::from_ref(&value).cast())
};
from_result(result)
}
pub fn dirty(&self) -> Result<Dirty> {
self.get::<ffi::GhosttyRenderStateDirty>(
ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_DIRTY,
)
.and_then(|v| v.try_into().map_err(|_| Error::InvalidValue))
}
pub fn cols(&self) -> Result<u16> {
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_COLS)
}
pub fn rows(&self) -> Result<u16> {
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_ROWS)
}
pub fn cursor_color(&self) -> Result<Option<RgbColor>> {
let has_value =
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_COLOR_CURSOR_HAS_VALUE)?;
if has_value {
let color =
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_COLOR_CURSOR)?;
Ok(Some(color))
} else {
Ok(None)
}
}
pub fn cursor_visible(&self) -> Result<bool> {
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_CURSOR_VISIBLE)
}
pub fn cursor_blinking(&self) -> Result<bool> {
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_CURSOR_BLINKING)
}
pub fn cursor_password_input(&self) -> Result<bool> {
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_CURSOR_PASSWORD_INPUT)
}
pub fn cursor_visual_style(&self) -> Result<CursorVisualStyle> {
self.get::<ffi::GhosttyRenderStateCursorVisualStyle>(
ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_CURSOR_VISUAL_STYLE,
)
.and_then(|v| v.try_into().map_err(|_| Error::InvalidValue))
}
pub fn cursor_viewport(&self) -> Result<Option<CursorViewport>> {
let has_value = self
.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE)?;
if has_value {
let x =
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_X)?;
let y =
self.get(ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_Y)?;
let at_wide_tail = self.get(
ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL,
)?;
Ok(Some(CursorViewport { x, y, at_wide_tail }))
} else {
Ok(None)
}
}
pub fn colors(&self) -> Result<Colors> {
let mut colors = ffi::sized!(ffi::GhosttyRenderStateColors);
let result =
unsafe { ffi::ghostty_render_state_colors_get(self.0.0.as_raw(), &raw mut colors) };
from_result(result)?;
Ok(Colors {
background: colors.background.into(),
foreground: colors.foreground.into(),
cursor: if colors.cursor_has_value {
Some(colors.cursor.into())
} else {
None
},
palette: colors.palette.map(Into::into),
})
}
pub fn set_dirty(&self, dirty: Dirty) -> Result<()> {
self.set(
ffi::GhosttyRenderStateOption_GHOSTTY_RENDER_STATE_OPTION_DIRTY,
&ffi::GhosttyRenderStateDirty::from(dirty),
)
}
}
impl<'alloc> RowIterator<'alloc> {
pub fn new() -> Result<Self> {
unsafe { Self::new_inner(std::ptr::null()) }
}
pub fn new_with_alloc<'ctx: 'alloc, Ctx>(alloc: &'alloc Allocator<'ctx, Ctx>) -> Result<Self> {
unsafe { Self::new_inner(alloc.to_raw()) }
}
unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator) -> Result<Self> {
let mut raw: ffi::GhosttyRenderStateRowIterator_ptr = std::ptr::null_mut();
let result = unsafe { ffi::ghostty_render_state_row_iterator_new(alloc, &raw mut raw) };
from_result(result)?;
Ok(Self(Object::new(raw)?))
}
pub fn update(
&mut self,
snapshot: &'_ Snapshot<'alloc, '_>,
) -> Result<RowIteration<'alloc, '_>> {
let result = unsafe {
ffi::ghostty_render_state_get(
snapshot.0.0.as_raw(),
ffi::GhosttyRenderStateData_GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR,
std::ptr::from_mut(&mut self.0.ptr).cast(),
)
};
from_result(result)?;
Ok(RowIteration {
iter: self,
_phan: PhantomData,
})
}
}
impl Drop for RowIterator<'_> {
fn drop(&mut self) {
unsafe { ffi::ghostty_render_state_row_iterator_free(self.0.as_raw()) }
}
}
impl RowIteration<'_, '_> {
#[expect(
clippy::should_implement_trait,
reason = "lending `next` cannot implement trait"
)]
pub fn next(&mut self) -> Option<&Self> {
if unsafe { ffi::ghostty_render_state_row_iterator_next(self.iter.0.as_raw()) } {
Some(self)
} else {
None
}
}
fn get<T>(&self, tag: ffi::GhosttyRenderStateRowData) -> Result<T> {
let mut value = MaybeUninit::<T>::zeroed();
let result = unsafe {
ffi::ghostty_render_state_row_get(self.iter.0.as_raw(), tag, value.as_mut_ptr().cast())
};
from_result(result)?;
Ok(unsafe { value.assume_init() })
}
fn set<T>(&self, tag: ffi::GhosttyRenderStateRowOption, value: &T) -> Result<()> {
let result = unsafe {
ffi::ghostty_render_state_row_set(
self.iter.0.as_raw(),
tag,
std::ptr::from_ref(&value).cast(),
)
};
from_result(result)
}
pub fn dirty(&self) -> Result<bool> {
self.get(ffi::GhosttyRenderStateRowData_GHOSTTY_RENDER_STATE_ROW_DATA_DIRTY)
}
pub fn raw_row(&self) -> Result<Row> {
self.get(ffi::GhosttyRenderStateRowData_GHOSTTY_RENDER_STATE_ROW_DATA_RAW)
.map(Row)
}
pub fn set_dirty(&self, dirty: bool) -> Result<()> {
self.set(
ffi::GhosttyRenderStateRowOption_GHOSTTY_RENDER_STATE_ROW_OPTION_DIRTY,
&dirty,
)
}
}
impl<'alloc> CellIterator<'alloc> {
pub fn new() -> Result<Self> {
unsafe { Self::new_inner(std::ptr::null()) }
}
pub fn new_with_alloc<'ctx: 'alloc, Ctx>(alloc: &'alloc Allocator<'ctx, Ctx>) -> Result<Self> {
unsafe { Self::new_inner(alloc.to_raw()) }
}
unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator) -> Result<Self> {
let mut raw: ffi::GhosttyRenderStateRowCells_ptr = std::ptr::null_mut();
let result = unsafe { ffi::ghostty_render_state_row_cells_new(alloc, &raw mut raw) };
from_result(result)?;
Ok(Self(Object::new(raw)?))
}
pub fn update(
&mut self,
row: &'_ RowIteration<'alloc, '_>,
) -> Result<CellIteration<'alloc, '_>> {
let result = unsafe {
ffi::ghostty_render_state_row_get(
row.iter.0.as_raw(),
ffi::GhosttyRenderStateRowData_GHOSTTY_RENDER_STATE_ROW_DATA_CELLS,
std::ptr::from_mut(&mut self.0.ptr).cast(),
)
};
from_result(result)?;
Ok(CellIteration {
iter: self,
_phan: PhantomData,
})
}
}
impl Drop for CellIterator<'_> {
fn drop(&mut self) {
unsafe { ffi::ghostty_render_state_row_cells_free(self.0.as_raw()) }
}
}
impl CellIteration<'_, '_> {
#[expect(
clippy::should_implement_trait,
reason = "lending `next` cannot implement trait"
)]
pub fn next(&mut self) -> Option<&Self> {
if unsafe { ffi::ghostty_render_state_row_cells_next(self.iter.0.as_raw()) } {
Some(self)
} else {
None
}
}
pub fn select(&mut self, x: u16) -> Result<()> {
let result = unsafe { ffi::ghostty_render_state_row_cells_select(self.iter.0.as_raw(), x) };
from_result(result)
}
fn get<T>(&self, tag: ffi::GhosttyRenderStateRowCellsData) -> Result<T> {
let mut value = MaybeUninit::<T>::zeroed();
let result = unsafe {
ffi::ghostty_render_state_row_cells_get(
self.iter.0.as_raw(),
tag,
value.as_mut_ptr().cast(),
)
};
from_result(result)?;
Ok(unsafe { value.assume_init() })
}
pub fn raw_cell(&self) -> Result<Cell> {
self.get(ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_RAW)
.map(Cell)
}
pub fn style(&self) -> Result<Style> {
let mut value = ffi::sized!(ffi::GhosttyStyle);
let result = unsafe {
ffi::ghostty_render_state_row_cells_get(
self.iter.0.as_raw(),
ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_STYLE,
std::ptr::from_mut(&mut value).cast(),
)
};
from_result(result)?;
Style::try_from(value)
}
pub fn fg_color(&self) -> Result<Option<RgbColor>> {
let res = self.get::<ffi::GhosttyColorRgb>(
ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_FG_COLOR,
);
match res {
Ok(o) => Ok(Some(o.into())),
Err(Error::InvalidValue) => Ok(None),
Err(e) => Err(e),
}
}
pub fn bg_color(&self) -> Result<Option<RgbColor>> {
let res = self.get::<ffi::GhosttyColorRgb>(
ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_BG_COLOR,
);
match res {
Ok(o) => Ok(Some(o.into())),
Err(Error::InvalidValue) => Ok(None),
Err(e) => Err(e),
}
}
pub fn graphemes(&self) -> Result<Vec<char>> {
let len = self.graphemes_len()?;
let mut graphemes = vec!['\0'; len];
self.graphemes_buf(&mut graphemes)?;
Ok(graphemes)
}
pub fn graphemes_len(&self) -> Result<usize> {
self.get(
ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN,
)
}
pub fn graphemes_buf(&self, buf: &mut [char]) -> Result<()> {
let result = unsafe {
ffi::ghostty_render_state_row_cells_get(
self.iter.0.as_raw(),
ffi::GhosttyRenderStateRowCellsData_GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF,
buf.as_mut_ptr().cast(),
)
};
from_result(result)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CursorViewport {
pub x: u16,
pub y: u16,
pub at_wide_tail: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Colors {
pub background: RgbColor,
pub foreground: RgbColor,
pub cursor: Option<RgbColor>,
pub palette: [RgbColor; 256],
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
pub enum Dirty {
Clean = ffi::GhosttyRenderStateDirty_GHOSTTY_RENDER_STATE_DIRTY_FALSE,
Partial = ffi::GhosttyRenderStateDirty_GHOSTTY_RENDER_STATE_DIRTY_PARTIAL,
Full = ffi::GhosttyRenderStateDirty_GHOSTTY_RENDER_STATE_DIRTY_FULL,
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
#[non_exhaustive]
pub enum CursorVisualStyle {
Bar = ffi::GhosttyRenderStateCursorVisualStyle_GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BAR,
Block = ffi::GhosttyRenderStateCursorVisualStyle_GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK,
Underline =
ffi::GhosttyRenderStateCursorVisualStyle_GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_UNDERLINE,
BlockHollow = ffi::GhosttyRenderStateCursorVisualStyle_GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK_HOLLOW,
}