1#![cfg_attr(not(feature = "std"), no_std)]
2
3pub mod bindings;
4
5pub mod color;
6pub mod elements;
7pub mod errors;
8pub mod id;
9pub mod layout;
10pub mod math;
11pub mod render_commands;
12
13mod mem;
14
15use elements::{text::TextElementConfig, ElementConfigType};
16use errors::Error;
17use math::{BoundingBox, Dimensions, Vector2};
18use render_commands::RenderCommand;
19
20use crate::bindings::*;
21
22#[derive(Debug, Clone, Copy)]
23#[repr(C)]
24pub struct TypedConfig {
25 pub config_memory: *const u8,
26 pub id: Clay_ElementId,
27 pub config_type: ElementConfigType,
28}
29
30pub type MeasureTextFunction = fn(text: &str, config: TextElementConfig) -> Dimensions;
31
32static mut MEASURE_TEXT_HANDLER: Option<MeasureTextFunction> = None;
34
35unsafe extern "C" fn measure_text_handle(
37 str: *mut Clay_String,
38 config: *mut Clay_TextElementConfig,
39) -> Clay_Dimensions {
40 match MEASURE_TEXT_HANDLER {
41 Some(func) => func((*str.as_ref().unwrap()).into(), config.into()).into(),
42 None => Clay_Dimensions {
43 width: 0.0,
44 height: 0.0,
45 },
46 }
47}
48
49unsafe extern "C" fn error_handler(error_data: Clay_ErrorData) {
50 let error: Error = error_data.into();
51 panic!("Clay Error: (type: {:?}) {}", error.type_, error.text);
52}
53
54pub struct DataRef<'a> {
55 pub(crate) ptr: *const core::ffi::c_void,
56 _phantom: core::marker::PhantomData<&'a ()>,
57}
58
59pub struct Clay<'a> {
60 #[cfg(feature = "std")]
62 _memory: Vec<u8>,
63 context: *mut Clay_Context,
64 #[cfg(not(feature = "std"))]
67 _memory: *const core::ffi::c_void,
68 _phantom: core::marker::PhantomData<&'a ()>,
70}
71
72impl<'a> Clay<'a> {
73 #[cfg(feature = "std")]
74 pub fn new(dimensions: Dimensions) -> Self {
75 let memory_size = Self::required_memory_size();
76 let memory = vec![0; memory_size];
77 let context;
78
79 unsafe {
80 let arena =
81 Clay_CreateArenaWithCapacityAndMemory(memory_size as _, memory.as_ptr() as _);
82
83 context = Clay_Initialize(
84 arena,
85 dimensions.into(),
86 Clay_ErrorHandler {
87 errorHandlerFunction: Some(error_handler),
88 userData: 0,
89 },
90 );
91 }
92
93 Self {
94 _memory: memory,
95 context,
96 _phantom: core::marker::PhantomData,
97 }
98 }
99
100 pub fn data<T>(&self, data: &T) -> DataRef<'a> {
103 DataRef {
104 ptr: data as *const T as *const core::ffi::c_void,
105 _phantom: core::marker::PhantomData,
106 }
107 }
108
109 #[cfg(not(feature = "std"))]
110 pub unsafe fn new_with_memory(dimensions: Dimensions, memory: *mut core::ffi::c_void) -> Self {
111 let memory_size = Self::required_memory_size();
112 let arena = Clay_CreateArenaWithCapacityAndMemory(memory_size as _, memory);
113
114 let context = Clay_Initialize(
115 arena,
116 dimensions.into(),
117 Clay_ErrorHandler {
118 errorHandlerFunction: Some(error_handler),
119 userData: 0,
120 },
121 );
122
123 Self {
124 _memory: memory,
125 context,
126 _phantom: core::marker::PhantomData,
127 }
128 }
129
130 pub fn required_memory_size() -> usize {
132 unsafe { Clay_MinMemorySize() as usize }
133 }
134
135 pub fn measure_text_function(&self, func: MeasureTextFunction) {
137 unsafe {
138 MEASURE_TEXT_HANDLER = Some(func);
139 Clay_SetMeasureTextFunction(Some(measure_text_handle));
140 }
141 }
142
143 pub fn max_element_count(&self, max_element_count: u32) {
146 unsafe {
147 Clay_SetMaxElementCount(max_element_count as _);
148 }
149 }
150 pub fn max_measure_text_cache_word_count(&self, count: u32) {
153 unsafe {
154 Clay_SetMaxElementCount(count as _);
155 }
156 }
157
158 pub fn enable_debug_mode(&self, enable: bool) {
160 unsafe {
161 Clay_SetDebugModeEnabled(enable);
162 }
163 }
164
165 pub fn layout_dimensions(&self, dimensions: Dimensions) {
168 unsafe {
169 Clay_SetLayoutDimensions(dimensions.into());
170 }
171 }
172 pub fn pointer_state(&self, position: Vector2, is_down: bool) {
175 unsafe {
176 Clay_SetPointerState(position.into(), is_down);
177 }
178 }
179 pub fn update_scroll_containers(
180 &self,
181 drag_scrolling_enabled: bool,
182 scroll_delta: Vector2,
183 delta_time: f32,
184 ) {
185 unsafe {
186 Clay_UpdateScrollContainers(drag_scrolling_enabled, scroll_delta.into(), delta_time);
187 }
188 }
189
190 pub fn hovered(&self) -> bool {
192 unsafe { Clay_Hovered() }
193 }
194
195 pub fn pointer_over(&self, cfg: TypedConfig) -> bool {
196 unsafe { Clay_PointerOver(cfg.id) }
197 }
198
199 fn get_element_data(id: TypedConfig) -> Clay_ElementData {
200 unsafe { Clay_GetElementData(id.id) }
201 }
202
203 pub fn get_bounding_box(&self, id: TypedConfig) -> Option<BoundingBox> {
204 let element_data = Self::get_element_data(id);
205
206 if element_data.found {
207 Some(element_data.boundingBox.into())
208 } else {
209 None
210 }
211 }
212
213 pub fn begin(&self) {
214 unsafe { Clay_BeginLayout() };
215 }
216
217 pub fn end(&self) -> impl Iterator<Item = RenderCommand> {
218 let array = unsafe { Clay_EndLayout() };
219 let slice = unsafe { core::slice::from_raw_parts(array.internalArray, array.length as _) };
220 slice.iter().map(|command| RenderCommand::from(*command))
221 }
222
223 pub fn with<F: FnOnce(&Clay), const N: usize>(&self, configs: [TypedConfig; N], f: F) {
228 unsafe {
229 Clay_SetCurrentContext(self.context);
230 Clay__OpenElement()
231 };
232
233 for config in configs {
234 if config.config_type == ElementConfigType::Id as _ {
235 unsafe { Clay__AttachId(config.id) };
236 } else if config.config_type == ElementConfigType::Layout as _ {
237 unsafe { Clay__AttachLayoutConfig(config.config_memory as _) };
238 } else {
239 unsafe {
240 Clay__AttachElementConfig(
241 core::mem::transmute::<*const u8, bindings::Clay_ElementConfigUnion>(
242 config.config_memory,
243 ),
244 config.config_type as _,
245 )
246 };
247 }
248 }
249
250 unsafe { Clay__ElementPostConfiguration() };
251
252 f(self);
253
254 unsafe {
255 Clay__CloseElement();
256 }
257 }
258
259 pub fn text(&self, text: &str, config: TextElementConfig) {
261 unsafe { Clay__OpenTextElement(text.into(), config.into()) };
262 }
263}
264
265impl From<&str> for Clay_String {
266 fn from(value: &str) -> Self {
267 Self {
268 length: value.len() as _,
269 chars: value.as_ptr() as _,
270 }
271 }
272}
273impl From<Clay_String> for &str {
274 fn from(value: Clay_String) -> Self {
275 unsafe {
276 core::str::from_utf8_unchecked(core::slice::from_raw_parts(
277 value.chars as *const u8,
278 value.length as _,
279 ))
280 }
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use color::Color;
287 use elements::{
288 containers::border::BorderContainer, rectangle::Rectangle, text::Text, CornerRadius,
289 };
290 use id::Id;
291 use layout::{padding::Padding, sizing::Sizing, Layout};
292
293 use super::*;
294
295 #[test]
303 fn test_begin() {
304 let clay = Clay::new(Dimensions::new(800.0, 600.0));
305
306 clay.measure_text_function(|_, _| Dimensions::default());
307
308 clay.begin();
309
310 clay.with(
311 [
312 Id::new("parent_rect"),
313 Layout::new()
314 .width(Sizing::Fixed(100.0))
315 .height(Sizing::Fixed(100.0))
316 .padding(Padding::all(10))
317 .end(),
318 Rectangle::new().color(Color::rgb(255., 255., 255.)).end(),
319 ],
321 |clay| {
322 clay.with(
323 [
324 Id::new("rect_under_rect"),
325 Layout::new()
326 .width(Sizing::Fixed(100.0))
327 .height(Sizing::Fixed(100.0))
328 .padding(Padding::all(10))
329 .end(),
330 Rectangle::new().color(Color::rgb(255., 255., 255.)).end(),
331 ],
332 |_clay| {},
333 );
334 clay.text(
335 "test",
336 Text::new()
337 .color(Color::rgb(255., 255., 255.))
338 .font_size(24)
339 .end(),
340 );
341 },
342 );
343 clay.with(
344 [
345 Id::new_index("Border_container", 1),
346 Layout::new().padding(Padding::all(16)).end(),
347 BorderContainer::new()
348 .all_directions(2, Color::rgb(255., 255., 0.))
349 .corner_radius(CornerRadius::All(25.))
350 .end(),
351 ],
352 |clay| {
353 clay.with(
354 [
355 Id::new("rect_under_border"),
356 Layout::new()
357 .width(Sizing::Fixed(50.0))
358 .height(Sizing::Fixed(50.0))
359 .end(),
360 Rectangle::new().color(Color::rgb(0., 255., 255.)).end(),
361 ],
362 |_clay| {},
363 );
364 },
365 );
366
367 let items = clay.end();
368
369 for item in items {
370 println!(
371 "id: {}\nbbox: {:?}\nconfig: {:?}",
372 item.id, item.bounding_box, item.config,
373 );
374 }
375 }
376
377 #[test]
391 fn size_of_union() {
392 assert_eq!(
393 core::mem::size_of::<Clay_SizingAxis__bindgen_ty_1>(),
394 core::mem::size_of::<Clay_SizingAxis__bindgen_ty_1>()
395 )
396 }
397}