ratatui_unity/ffi/
widgets.rs

1//! Single-call widget entry points (block, paragraph, list, gauge, tabs,
2//! sparkline, tables, bar chart, line gauge, scrollbar, calendar).
3
4use crate::ffi::util::{cstr_to_string, slice_from, state_mut};
5use crate::terminal::WidgetCommand;
6use std::ffi::c_void;
7use std::os::raw::c_char;
8
9/// Queues a [`Block`](ratatui::widgets::Block) widget with an optional title
10/// and per-edge borders.
11///
12/// `borders` is a bit field — `0x01` Top, `0x02` Bottom, `0x04` Left,
13/// `0x08` Right. The value `0x0F` is treated as "all borders".
14///
15/// The pending style (see [`ratatui_set_style`](crate::ratatui_set_style)) is
16/// consumed and applied to the block.
17#[no_mangle]
18pub extern "C" fn ratatui_block(
19    handle: *mut c_void,
20    area_id: u32,
21    title: *const c_char,
22    borders: u8,
23) {
24    let Some(state) = state_mut(handle) else { return; };
25    let style = state.take_style();
26    state.commands.push(WidgetCommand::Block {
27        area_id,
28        title: cstr_to_string(title),
29        borders,
30        style,
31    });
32}
33
34/// Queues a uniformly styled [`Paragraph`](ratatui::widgets::Paragraph).
35///
36/// # Parameters
37/// - `text`: paragraph contents. Embedded `\n` produces line breaks.
38/// - `alignment`: `0` Left, `1` Center, `2` Right.
39/// - `wrap`: non-zero to enable word wrapping (`trim: false`).
40///
41/// For multi-style text use the styled-paragraph builder
42/// ([`ratatui_styled_para_begin`](crate::ratatui_styled_para_begin) /
43/// [`ratatui_styled_para_span`](crate::ratatui_styled_para_span) /
44/// [`ratatui_styled_para_newline`](crate::ratatui_styled_para_newline) /
45/// [`ratatui_styled_para_end`](crate::ratatui_styled_para_end)).
46#[no_mangle]
47pub extern "C" fn ratatui_paragraph(
48    handle: *mut c_void,
49    area_id: u32,
50    text: *const c_char,
51    alignment: u8,
52    wrap: u8,
53) {
54    let Some(state) = state_mut(handle) else { return; };
55    let style = state.take_style();
56    state.commands.push(WidgetCommand::Paragraph {
57        area_id,
58        text: cstr_to_string(text),
59        alignment,
60        wrap: wrap != 0,
61        style,
62    });
63}
64
65/// Queues a [`List`](ratatui::widgets::List) widget.
66///
67/// # Parameters
68/// - `items`: newline-separated list entries.
69/// - `selected`: zero-based index of the highlighted row, or `-1` for no
70///   selection. The highlight uses `"> "` as the prefix and a bold modifier.
71#[no_mangle]
72pub extern "C" fn ratatui_list(
73    handle: *mut c_void,
74    area_id: u32,
75    items: *const c_char,
76    selected: i32,
77) {
78    let Some(state) = state_mut(handle) else { return; };
79    let style = state.take_style();
80    state.commands.push(WidgetCommand::List {
81        area_id,
82        items: cstr_to_string(items),
83        selected,
84        style,
85    });
86}
87
88/// Queues a block-style [`Gauge`](ratatui::widgets::Gauge).
89///
90/// # Parameters
91/// - `ratio`: progress in `[0.0, 1.0]`. Values outside the range are clamped.
92/// - `label`: optional text overlaid on the gauge (pass `null` or empty
93///   for none).
94#[no_mangle]
95pub extern "C" fn ratatui_gauge(
96    handle: *mut c_void,
97    area_id: u32,
98    ratio: f32,
99    label: *const c_char,
100) {
101    let Some(state) = state_mut(handle) else { return; };
102    let style = state.take_style();
103    state.commands.push(WidgetCommand::Gauge {
104        area_id,
105        ratio: ratio as f64,
106        label: cstr_to_string(label),
107        style,
108    });
109}
110
111/// Queues a [`Tabs`](ratatui::widgets::Tabs) bar.
112///
113/// # Parameters
114/// - `titles`: newline-separated tab labels.
115/// - `selected`: zero-based index of the active tab.
116///
117/// The pending style's foreground color (or cyan if unset) is used as the
118/// highlight background of the active tab.
119#[no_mangle]
120pub extern "C" fn ratatui_tabs(
121    handle: *mut c_void,
122    area_id: u32,
123    titles: *const c_char,
124    selected: u32,
125) {
126    let Some(state) = state_mut(handle) else { return; };
127    let style = state.take_style();
128    state.commands.push(WidgetCommand::Tabs {
129        area_id,
130        titles: cstr_to_string(titles),
131        selected,
132        style,
133    });
134}
135
136/// Queues a [`Sparkline`](ratatui::widgets::Sparkline) from raw `u64` samples.
137///
138/// # Parameters
139/// - `data`: pointer to `len` `u64` samples.
140/// - `len`: number of samples.
141#[no_mangle]
142pub extern "C" fn ratatui_sparkline(
143    handle: *mut c_void,
144    area_id: u32,
145    data: *const u64,
146    len: u32,
147) {
148    if data.is_null() { return; }
149    let Some(state) = state_mut(handle) else { return; };
150    let style = state.take_style();
151    let data_vec = slice_from(data, len as usize).to_vec();
152    state.commands.push(WidgetCommand::Sparkline { area_id, data: data_vec, style });
153}
154
155/// Queues a [`Table`](ratatui::widgets::Table) with equal-width columns.
156///
157/// `data` format:
158/// - First line: tab-separated header cells.
159/// - Subsequent lines: one row per line; cells separated by tabs.
160///
161/// For typed column widths and row selection use [`ratatui_table_ex`].
162#[no_mangle]
163pub extern "C" fn ratatui_table(
164    handle: *mut c_void,
165    area_id: u32,
166    data: *const c_char,
167) {
168    let Some(state) = state_mut(handle) else { return; };
169    let style = state.take_style();
170    state.commands.push(WidgetCommand::Table {
171        area_id,
172        data: cstr_to_string(data),
173        style,
174    });
175}
176
177/// Queues a [`BarChart`](ratatui::widgets::BarChart).
178///
179/// `data` format: one bar per line, label and value separated by a tab.
180/// Malformed lines (missing tab or non-numeric value) are silently skipped.
181///
182/// # Parameters
183/// - `bar_width`: width of each bar in cells.
184/// - `bar_gap`: gap between bars in cells.
185#[no_mangle]
186pub extern "C" fn ratatui_barchart(
187    handle: *mut c_void,
188    area_id: u32,
189    data: *const c_char,
190    bar_width: u16,
191    bar_gap: u16,
192) {
193    let Some(state) = state_mut(handle) else { return; };
194    let style = state.take_style();
195    let data_str = cstr_to_string(data);
196    let bars: Vec<(String, u64)> = data_str
197        .lines()
198        .filter_map(|line| {
199            let mut parts = line.splitn(2, '\t');
200            let label = parts.next()?.to_string();
201            let value: u64 = parts.next()?.trim().parse().ok()?;
202            Some((label, value))
203        })
204        .collect();
205    state.commands.push(WidgetCommand::BarChart { area_id, bars, bar_width, bar_gap, style });
206}
207
208/// Queues a horizontal single-line [`LineGauge`](ratatui::widgets::LineGauge).
209///
210/// # Parameters
211/// - `ratio`: progress in `[0.0, 1.0]`; values outside the range are clamped.
212/// - `label`: text shown next to the gauge (pass `null` or empty for none).
213#[no_mangle]
214pub extern "C" fn ratatui_line_gauge(
215    handle: *mut c_void,
216    area_id: u32,
217    ratio: f32,
218    label: *const c_char,
219) {
220    let Some(state) = state_mut(handle) else { return; };
221    let style = state.take_style();
222    state.commands.push(WidgetCommand::LineGauge {
223        area_id,
224        ratio: ratio as f64,
225        label: cstr_to_string(label),
226        style,
227    });
228}
229
230/// Queues a [`Scrollbar`](ratatui::widgets::Scrollbar).
231///
232/// # Parameters
233/// - `content_length`: total scrollable length in cells.
234/// - `position`: current scroll offset in cells (`0..=content_length`).
235/// - `viewport_length`: visible portion of the content in cells.
236/// - `orientation`: `0` VerticalRight, `1` VerticalLeft, `2` HorizontalBottom,
237///   `3` HorizontalTop.
238#[no_mangle]
239pub extern "C" fn ratatui_scrollbar(
240    handle: *mut c_void,
241    area_id: u32,
242    content_length: u32,
243    position: u32,
244    viewport_length: u32,
245    orientation: u8,
246) {
247    let Some(state) = state_mut(handle) else { return; };
248    state.commands.push(WidgetCommand::Scrollbar {
249        area_id,
250        content_length,
251        position,
252        viewport_length,
253        orientation,
254    });
255}
256
257/// Queues a monthly calendar
258/// ([`Monthly`](ratatui::widgets::calendar::Monthly)).
259///
260/// Invalid dates fall back to January 1 of `year`, and if that also fails,
261/// to 2024-01-01. The `widget-calendar` Cargo feature must be enabled
262/// (it is, by default, in this crate).
263///
264/// # Parameters
265/// - `year`: full year (e.g. `2026`).
266/// - `month`: `1..=12`.
267/// - `day`: `1..=28` (later days are clamped to `28` to avoid month overflow).
268#[no_mangle]
269pub extern "C" fn ratatui_calendar(
270    handle: *mut c_void,
271    area_id: u32,
272    year: i32,
273    month: u8,
274    day: u8,
275) {
276    let Some(state) = state_mut(handle) else { return; };
277    state.commands.push(WidgetCommand::Calendar { area_id, year, month, day });
278}
279
280/// Queues an extended [`Table`](ratatui::widgets::Table) with typed column
281/// widths and optional row highlighting.
282///
283/// `data` follows the same format as [`ratatui_table`] (first line headers,
284/// subsequent lines rows; tab-separated cells).
285///
286/// # Parameters
287/// - `col_types` / `col_values`: parallel arrays of length `col_count`
288///   describing each column's constraint kind and value. Same encoding as
289///   [`ratatui_split`](crate::ratatui_split). Pass `null` (or
290///   `col_count == 0`) for equal-width distribution.
291/// - `selected_row`: zero-based index of the highlighted row, or `-1` for
292///   no selection. The highlight uses a bold modifier.
293#[no_mangle]
294pub extern "C" fn ratatui_table_ex(
295    handle: *mut c_void,
296    area_id: u32,
297    data: *const c_char,
298    col_types: *const u8,
299    col_values: *const u16,
300    col_count: u32,
301    selected_row: i32,
302) {
303    let Some(state) = state_mut(handle) else { return; };
304    let style = state.take_style();
305    let col_constraints: Vec<(u8, u16)> =
306        if col_types.is_null() || col_values.is_null() || col_count == 0 {
307            Vec::new()
308        } else {
309            let n = col_count as usize;
310            let types = slice_from(col_types, n);
311            let values = slice_from(col_values, n);
312            types.iter().zip(values.iter()).map(|(&t, &v)| (t, v)).collect()
313        };
314    state.commands.push(WidgetCommand::TableEx {
315        area_id,
316        data: cstr_to_string(data),
317        col_constraints,
318        selected_row,
319        style,
320    });
321}