ratatui_unity/ffi/layout.rs
1//! Layout splitting, inner-margin areas, and input hit-testing.
2
3use crate::commands::do_split;
4use crate::ffi::util::{slice_from, slice_mut_from, state_mut, state_ref};
5use std::ffi::c_void;
6
7/// Returns the id of the root area, which always covers the whole terminal.
8///
9/// The root id is the constant `0`; this getter exists for symmetry with the
10/// host-side area API.
11#[no_mangle]
12pub extern "C" fn ratatui_root_area(_handle: *const c_void) -> u32 { 0 }
13
14/// Splits an existing area into `count` child areas and writes their ids into
15/// `out_ids`.
16///
17/// # Parameters
18/// - `area_id`: id of the parent area to split.
19/// - `direction`: `0` = horizontal split (left → right), any other value =
20/// vertical split (top → bottom).
21/// - `constraint_types`: array of length `count` describing each child's
22/// constraint kind. Values: `0` = Length, `1` = Min, `2` = Max,
23/// `3` = Percentage, `4` (or any other) = Fill.
24/// - `constraint_values`: array of length `count` with the numeric value
25/// matching the constraint kind (cells for Length/Min/Max, 0..=100 for
26/// Percentage, weight for Fill).
27/// - `count`: number of child areas requested.
28/// - `out_ids`: caller-allocated buffer of length `count` that receives the
29/// ids of the newly registered child areas.
30///
31/// # Returns
32/// The number of child areas actually written. Returns `0` if any required
33/// pointer is null, `count` is zero, or the parent id is unknown.
34#[no_mangle]
35pub extern "C" fn ratatui_split(
36 handle: *mut c_void,
37 area_id: u32,
38 direction: u8,
39 constraint_types: *const u8,
40 constraint_values: *const u16,
41 count: u32,
42 out_ids: *mut u32,
43) -> u32 {
44 if count == 0
45 || constraint_types.is_null()
46 || constraint_values.is_null()
47 || out_ids.is_null()
48 {
49 return 0;
50 }
51 let Some(state) = state_mut(handle) else { return 0; };
52 let n = count as usize;
53 let types = slice_from(constraint_types, n);
54 let values = slice_from(constraint_values, n);
55 let Some(out) = slice_mut_from(out_ids, n) else { return 0; };
56 do_split(state, area_id, direction, types, values, out)
57}
58
59/// Returns a new area id covering the inner rectangle of `area_id` shrunk by
60/// the given margins on each side.
61///
62/// # Parameters
63/// - `area_id`: parent area id.
64/// - `horizontal`: cells to remove from the left and right edges.
65/// - `vertical`: cells to remove from the top and bottom edges.
66///
67/// # Returns
68/// The id of the newly registered inner area, or [`u32::MAX`] if `handle` is
69/// null or `area_id` is unknown.
70#[no_mangle]
71pub extern "C" fn ratatui_inner(
72 handle: *mut c_void,
73 area_id: u32,
74 horizontal: u16,
75 vertical: u16,
76) -> u32 {
77 let Some(state) = state_mut(handle) else { return u32::MAX; };
78 let area = match state.area_map.get(&area_id).copied() {
79 Some(r) => r,
80 None => return u32::MAX,
81 };
82 use ratatui::layout::Margin;
83 let inner = area.inner(Margin { horizontal, vertical });
84 state.register_area(inner)
85}
86
87/// Returns the most specific area id covering the given terminal cell.
88///
89/// When several registered areas contain `(col, row)` the one with the
90/// smallest cell count (the most deeply nested) wins. Returns `0` (root) when
91/// no registered area matches.
92///
93/// Useful for mapping pointer input back into the layout tree.
94#[no_mangle]
95pub extern "C" fn ratatui_hit_test(
96 handle: *mut c_void,
97 col: u16,
98 row: u16,
99) -> u32 {
100 let Some(state) = state_mut(handle) else { return 0; };
101 let mut best_id = 0u32;
102 let mut best_area = u32::MAX;
103
104 for (&id, &rect) in &state.area_map {
105 if col >= rect.x && col < rect.x + rect.width
106 && row >= rect.y && row < rect.y + rect.height
107 {
108 let area = (rect.width as u32) * (rect.height as u32);
109 if area < best_area {
110 best_area = area;
111 best_id = id;
112 }
113 }
114 }
115 best_id
116}
117
118/// Returns the cell-space rectangle of the given area as a packed `u64`.
119///
120/// The four `u16` fields are packed little-endian:
121///
122/// ```text
123/// bits 0..16 -> x
124/// bits 16..32 -> y
125/// bits 32..48 -> width
126/// bits 48..64 -> height
127/// ```
128///
129/// Returns `0` if `handle` is null or the area id is unknown.
130#[no_mangle]
131pub extern "C" fn ratatui_get_area_rect(
132 handle: *const c_void,
133 area_id: u32,
134) -> u64 {
135 let Some(state) = state_ref(handle) else { return 0; };
136 match state.area_map.get(&area_id) {
137 Some(rect) => {
138 (rect.x as u64)
139 | ((rect.y as u64) << 16)
140 | ((rect.width as u64) << 32)
141 | ((rect.height as u64) << 48)
142 }
143 None => 0,
144 }
145}