Skip to main content

ratatui_unity/ffi/
lifecycle.rs

1//! Handle lifecycle, frame rendering, pixel-buffer queries, and version.
2
3use crate::commands::render_all_commands;
4use crate::ffi::util::{slice_from, state_mut, state_ref};
5use crate::terminal::TerminalState;
6use std::ffi::c_void;
7use std::os::raw::c_char;
8
9/// Creates a terminal instance and returns an opaque handle.
10///
11/// The resulting handle owns:
12/// - a ratatui [`Terminal`](ratatui::Terminal) backed by
13///   [`TestBackend`](ratatui::backend::TestBackend) sized `cols × rows`
14///   cells,
15/// - a glyph-cached `FontManager` at `font_size` pixels,
16/// - a pre-allocated RGB24 pixel buffer matching `cols × rows × cell_size`.
17///
18/// The handle must eventually be released with [`ratatui_destroy`].
19///
20/// # Parameters
21/// - `cols`: terminal width in character cells.
22/// - `rows`: terminal height in character cells.
23/// - `font_size`: glyph rasterization size in pixels (e.g. `14.0`).
24#[no_mangle]
25pub extern "C" fn ratatui_create(cols: u16, rows: u16, font_size: f32) -> *mut c_void {
26    let state = Box::new(TerminalState::new(cols, rows, font_size));
27    Box::into_raw(state) as *mut c_void
28}
29
30/// Destroys a terminal handle created by [`ratatui_create`].
31///
32/// After this call the handle and any previously returned pixel-buffer
33/// pointers are invalid and must not be used. A null handle is a no-op.
34#[no_mangle]
35pub extern "C" fn ratatui_destroy(handle: *mut c_void) {
36    if !handle.is_null() {
37        // SAFETY: handle is non-null (checked above) and, per contract, was
38        // produced by ratatui_create and not yet destroyed; reclaiming the
39        // Box frees the allocation exactly once.
40        unsafe { drop(Box::from_raw(handle as *mut TerminalState)); }
41    }
42}
43
44/// Replaces the embedded JetBrains Mono font with custom TTF bytes.
45///
46/// The cell width/height are recomputed from the new font's metrics, the
47/// glyph cache is dropped, and the pixel buffer is resized to the new
48/// `cols × cell_size` dimensions. After a successful call the values
49/// reported by [`ratatui_pixel_width`] / [`ratatui_pixel_height`] change
50/// accordingly; callers must re-query them and resize any host-side
51/// texture before the next `ratatui_end_frame*` call.
52///
53/// # Returns
54/// `1` on success, `0` if `handle` is null, `font_data` is null/empty, or the
55/// bytes are not a valid font.
56#[no_mangle]
57pub extern "C" fn ratatui_set_custom_font(
58    handle: *mut c_void,
59    font_data: *const u8,
60    font_len: u32,
61) -> u8 {
62    if font_data.is_null() || font_len == 0 { return 0; }
63    let Some(state) = state_mut(handle) else { return 0; };
64    let bytes = slice_from(font_data, font_len as usize);
65    let ok = state.font.set_custom_font(bytes);
66    if ok {
67        state.resync_pixel_dimensions();
68    }
69    u8::from(ok)
70}
71
72/// Begins a new frame.
73///
74/// Clears the queued widget command list, drops any in-progress builder state
75/// (styled paragraph, chart, canvas), resets the pending style to default, and
76/// resets the area map so that only the root area (id `0`) remains. Must be
77/// called before issuing widget commands for the new frame.
78#[no_mangle]
79pub extern "C" fn ratatui_begin_frame(handle: *mut c_void) {
80    let Some(state) = state_mut(handle) else { return; };
81    state.begin_frame();
82}
83
84/// Renders all queued widget commands and rasterizes the cell buffer.
85///
86/// The returned pointer addresses a flat RGB24 buffer of size
87/// `pixel_width * pixel_height * 3` bytes, owned by the handle. The pointer is
88/// valid until the next FFI call that mutates the handle (typically the next
89/// `ratatui_end_frame*` call), at which point the buffer may be overwritten.
90///
91/// Returns `null` only when `handle` is null.
92#[no_mangle]
93pub extern "C" fn ratatui_end_frame(handle: *mut c_void) -> *const u8 {
94    let Some(state) = state_mut(handle) else { return std::ptr::null(); };
95    render_all_commands(state);
96    state.rasterize();
97    state.pixel_buffer.as_ptr()
98}
99
100/// Like `ratatui_end_frame`, but skips rasterization when the cell buffer
101/// is unchanged from the previous frame (hash-based dirty check).
102/// Returns a valid pixel pointer when content changed, or null when unchanged.
103/// The previous frame's pixel buffer remains valid when null is returned.
104#[no_mangle]
105pub extern "C" fn ratatui_end_frame_hashed(handle: *mut c_void) -> *const u8 {
106    let Some(state) = state_mut(handle) else { return std::ptr::null(); };
107    render_all_commands(state);
108
109    let hash = {
110        let buffer = state.terminal.backend().buffer();
111        crate::renderer::compute_buffer_hash(buffer)
112    };
113
114    if state.last_buffer_hash == Some(hash) {
115        return std::ptr::null();
116    }
117
118    state.last_buffer_hash = Some(hash);
119    state.rasterize();
120    state.pixel_buffer.as_ptr()
121}
122
123/// Returns the width of the pixel buffer in pixels (`cols * cell_width`).
124///
125/// Returns `0` if `handle` is null.
126#[no_mangle]
127pub extern "C" fn ratatui_pixel_width(handle: *const c_void) -> u32 {
128    let Some(state) = state_ref(handle) else { return 0; };
129    state.pixel_width
130}
131
132/// Returns the height of the pixel buffer in pixels (`rows * cell_height`).
133///
134/// Returns `0` if `handle` is null.
135#[no_mangle]
136pub extern "C" fn ratatui_pixel_height(handle: *const c_void) -> u32 {
137    let Some(state) = state_ref(handle) else { return 0; };
138    state.pixel_height
139}
140
141/// Returns the library version as a static null-terminated C string.
142///
143/// The returned pointer is valid for the lifetime of the process and must
144/// not be freed by the caller. The value matches `CARGO_PKG_VERSION`.
145#[no_mangle]
146pub extern "C" fn ratatui_version() -> *const c_char {
147    concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
148}