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}