clay_layout/
lib.rs

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
32// Is used to store the current callback for measuring text
33static mut MEASURE_TEXT_HANDLER: Option<MeasureTextFunction> = None;
34
35// Converts the args and calls `MEASURE_TEXT_HANDLER`. Passed to clay with `Clay_SetMeasureTextFunction`
36unsafe 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    /// Memory used internally by clay
61    #[cfg(feature = "std")]
62    _memory: Vec<u8>,
63    context: *mut Clay_Context,
64    /// Memory used internally by clay. The caller is responsible for managing this memory in
65    /// no_std case.
66    #[cfg(not(feature = "std"))]
67    _memory: *const core::ffi::c_void,
68    /// Phantom data to keep the lifetime of the memory
69    _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    /// Get a reference to the data to pass to clay or the builders. This is to ensure that the
101    /// data is not dropped before clay is done with it.
102    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    /// Wrapper for `Clay_MinMemorySize`, returns the minimum required memory by clay
131    pub fn required_memory_size() -> usize {
132        unsafe { Clay_MinMemorySize() as usize }
133    }
134
135    /// Sets the function used by clay to measure dimensions of strings of `Text` elements
136    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    /// Sets the maximum number of element that clay supports
144    /// **Use only if you know what you are doing or your getting errors from clay**
145    pub fn max_element_count(&self, max_element_count: u32) {
146        unsafe {
147            Clay_SetMaxElementCount(max_element_count as _);
148        }
149    }
150    /// Sets the capacity of the cache used for text in the measure text function
151    /// **Use only if you know what you are doing or your getting errors from clay**
152    pub fn max_measure_text_cache_word_count(&self, count: u32) {
153        unsafe {
154            Clay_SetMaxElementCount(count as _);
155        }
156    }
157
158    /// Enables or disables the debug mode of clay
159    pub fn enable_debug_mode(&self, enable: bool) {
160        unsafe {
161            Clay_SetDebugModeEnabled(enable);
162        }
163    }
164
165    /// Sets the dimensions of the global layout, use if, for example the window size you render to
166    /// changed
167    pub fn layout_dimensions(&self, dimensions: Dimensions) {
168        unsafe {
169            Clay_SetLayoutDimensions(dimensions.into());
170        }
171    }
172    /// Updates the state of the pointer for clay. Used to update scroll containers and for
173    /// interactions functions
174    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    /// Returns if the current element you are creating is hovered
191    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    /// Create an element, passing it's config and a function to add childrens
224    /// ```
225    /// // TODO: Add Example
226    /// ```
227    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    /// Adds a text element to the current open element or to the root layout
260    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    /*
296    #[test]
297    fn test_create() {
298        let _clay = Clay::new(800.0, 600.0);
299    }
300    */
301
302    #[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                // FloatingContainer::new().end(Id::new("tegfddgftds"))
320            ],
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    /*
378    #[test]
379    fn clay_floating_attach_point_type() {
380        assert_eq!(FloatingAttachPointType::LeftTop as c_uchar, 0 as c_uchar);
381    }
382
383    #[test]
384    fn clay_element_config_type() {
385        assert_eq!(ElementConfigType::BorderContainer as c_uchar, 2 as c_uchar);
386    }
387
388    */
389
390    #[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}