Repository: clay-ui-rs/clay
Pull request #56: feat!: improve typesafety on image and custom elements
Author: login: coffeeispower
Created: 2025-03-03T18:53:51Z | Merged: 2025-03-04T13:29:36Z

This PR improves the typesafety of the render commands, more specifically, it ensures that what you put inside the declaration is what the renderer is expecting.

For that to be possible, 2 new generic types were added to the Clay struct, ImageElementData and CustomElementData, which then are forwarded to the ImageBuilder struct and Declaration::custom_data function and to the RenderCommand struct. With this change we can prevent undefined behavior when rendering images and custom elements at compile time.

This was not the only change, additionally, the custom_element function was also added back because it was missing from the Declaration struct


Diff:
diff --git a/examples/basic_rectangle.rs b/examples/basic_rectangle.rs
index e7be5df..2cc86db 100644
--- a/examples/basic_rectangle.rs
+++ b/examples/basic_rectangle.rs
@@ -3,10 +3,10 @@ use clay_layout::{fixed, Clay, Declaration};
 #[rustfmt::skip]
 fn main() {
     // Create the clay instance
-    let clay = Clay::new((800., 600.).into());
+    let mut clay = Clay::new((800., 600.).into());
 
     // Begin the layout
-    clay.begin();
+    let mut clay = clay.begin::<(), ()>();
 
     // Adds a red rectangle with a corner radius of 5.
     // The Layout makes the rectangle have a width and height of 50.
diff --git a/examples/raylib_renderer.rs b/examples/raylib_renderer.rs
index 0c11277..f68c2de 100644
--- a/examples/raylib_renderer.rs
+++ b/examples/raylib_renderer.rs
@@ -2,7 +2,7 @@ use clay_layout::{grow, raylib::clay_raylib_render, Clay, Declaration};
 use raylib::prelude::*;
 
 pub fn main() {
-    let clay = Clay::new((800., 600.).into());
+    let mut clay = Clay::new((800., 600.).into());
 
     let (mut rl, thread) = raylib::init()
         .resizable()
@@ -18,7 +18,7 @@ pub fn main() {
         let mut d = rl.begin_drawing(&thread);
         d.clear_background(Color::WHITE);
 
-        clay.begin();
+        let mut clay = clay.begin::<_, ()>();
 
         #[rustfmt::skip]
         clay.with(
@@ -58,8 +58,6 @@ pub fn main() {
 
         let commands = clay.end();
 
-        if let Err(e) = clay_raylib_render(&mut d, commands) {
-            eprintln!("Error: {}", e);
-        };
+        clay_raylib_render(&mut d, commands, |_, _| {})
     }
 }
diff --git a/src/bindings/bindings.rs b/src/bindings/bindings.rs
index 52603b5..168eec0 100644
--- a/src/bindings/bindings.rs
+++ b/src/bindings/bindings.rs
@@ -684,7 +684,7 @@ pub struct Clay__Clay_TextElementConfigWrapper {
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
 pub struct Clay_ImageElementConfig {
-    pub imageData: *mut ::core::ffi::c_void,
+    pub imageData: *const ::core::ffi::c_void,
     pub sourceDimensions: Clay_Dimensions,
 }
 #[repr(C)]
@@ -745,7 +745,7 @@ pub struct Clay__Clay_FloatingElementConfigWrapper {
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
 pub struct Clay_CustomElementConfig {
-    pub customData: *mut ::core::ffi::c_void,
+    pub customData: *const ::core::ffi::c_void,
 }
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
diff --git a/src/bindings/bindings_debug.rs b/src/bindings/bindings_debug.rs
index 52603b5..168eec0 100644
--- a/src/bindings/bindings_debug.rs
+++ b/src/bindings/bindings_debug.rs
@@ -684,7 +684,7 @@ pub struct Clay__Clay_TextElementConfigWrapper {
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
 pub struct Clay_ImageElementConfig {
-    pub imageData: *mut ::core::ffi::c_void,
+    pub imageData: *const ::core::ffi::c_void,
     pub sourceDimensions: Clay_Dimensions,
 }
 #[repr(C)]
@@ -745,7 +745,7 @@ pub struct Clay__Clay_FloatingElementConfigWrapper {
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
 pub struct Clay_CustomElementConfig {
-    pub customData: *mut ::core::ffi::c_void,
+    pub customData: *const ::core::ffi::c_void,
 }
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
diff --git a/src/elements.rs b/src/elements.rs
index 7c6c09c..904c91e 100644
--- a/src/elements.rs
+++ b/src/elements.rs
@@ -1,15 +1,23 @@
-use crate::{bindings::*, color::Color, DataRef, Declaration, Dimensions, Vector2};
-use core::ffi::c_void;
+use crate::{bindings::*, color::Color, Declaration, Dimensions, Vector2};
 
 /// Builder for configuring border properties of a `Declaration`.
-pub struct BorderBuilder<'a> {
-    parent: &'a mut Declaration,
+pub struct BorderBuilder<
+    'declaration,
+    'render,
+    ImageElementData: 'render,
+    CustomElementData: 'render,
+> {
+    parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
 }
 
-impl BorderBuilder<'_> {
+impl<'declaration, 'render, ImageElementData: 'render, CustomElementData: 'render>
+    BorderBuilder<'declaration, 'render, ImageElementData, CustomElementData>
+{
     /// Creates a new `BorderBuilder` with the given parent `Declaration`.
     #[inline]
-    pub fn new(parent: &mut Declaration) -> BorderBuilder {
+    pub fn new(
+        parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
+    ) -> Self {
         BorderBuilder { parent }
     }
 
@@ -67,20 +75,29 @@ impl BorderBuilder<'_> {
 
     /// Returns the modified `Declaration`.
     #[inline]
-    pub fn end(&mut self) -> &mut Declaration {
+    pub fn end(&mut self) -> &mut Declaration<'render, ImageElementData, CustomElementData> {
         self.parent
     }
 }
 
 /// Builder for configuring image properties in a `Declaration`.
-pub struct ImageBuilder<'a> {
-    parent: &'a mut Declaration,
+pub struct ImageBuilder<
+    'declaration,
+    'render,
+    ImageElementData: 'render,
+    CustomElementData: 'render,
+> {
+    parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
 }
 
-impl ImageBuilder<'_> {
+impl<'declaration, 'render, ImageElementData: 'render, CustomElementData: 'render>
+    ImageBuilder<'declaration, 'render, ImageElementData, CustomElementData>
+{
     /// Creates a new `ImageBuilder` with the given parent `Declaration`.
     #[inline]
-    pub fn new(parent: &mut Declaration) -> ImageBuilder {
+    pub fn new(
+        parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
+    ) -> Self {
         ImageBuilder { parent }
     }
 
@@ -94,24 +111,13 @@ impl ImageBuilder<'_> {
     /// Sets the image data.
     /// The data must be created using [`Clay::data`].
     #[inline]
-    pub fn data(&mut self, data: DataRef) -> &mut Self {
-        self.parent.inner.image.imageData = data.ptr as *mut c_void;
+    pub fn data(&mut self, data: &'render ImageElementData) -> &mut Self {
+        self.parent.inner.image.imageData = (data as *const ImageElementData).cast();
         self
     }
-
-    /// Sets the image data using a raw pointer.
-    ///
-    /// # Safety
-    /// This function is unsafe because it accepts a raw pointer.
-    #[inline]
-    pub unsafe fn data_ptr(&mut self, data: *const c_void) -> &mut Self {
-        self.parent.inner.image.imageData = data as _;
-        self
-    }
-
     /// Returns the modified `Declaration`.
     #[inline]
-    pub fn end(&mut self) -> &mut Declaration {
+    pub fn end(&mut self) -> &mut Declaration<'render, ImageElementData, CustomElementData> {
         self.parent
     }
 }
@@ -165,14 +171,23 @@ pub enum FloatingAttachToElement {
 }
 
 /// Builder for configuring floating element properties in a `Declaration`.
-pub struct FloatingBuilder<'a> {
-    parent: &'a mut Declaration,
+pub struct FloatingBuilder<
+    'declaration,
+    'render,
+    ImageElementData: 'render,
+    CustomElementData: 'render,
+> {
+    parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
 }
 
-impl FloatingBuilder<'_> {
+impl<'declaration, 'render, ImageElementData: 'render, CustomElementData: 'render>
+    FloatingBuilder<'declaration, 'render, ImageElementData, CustomElementData>
+{
     /// Creates a new `FloatingBuilder` with the given parent `Declaration`.
     #[inline]
-    pub fn new(parent: &mut Declaration) -> FloatingBuilder {
+    pub fn new(
+        parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
+    ) -> Self {
         FloatingBuilder { parent }
     }
 
@@ -237,20 +252,29 @@ impl FloatingBuilder<'_> {
 
     /// Returns the modified `Declaration`.
     #[inline]
-    pub fn end(&mut self) -> &mut Declaration {
+    pub fn end(&mut self) -> &mut Declaration<'render, ImageElementData, CustomElementData> {
         self.parent
     }
 }
 
 /// Builder for configuring corner radius properties in a `Declaration`.
-pub struct CornerRadiusBuilder<'a> {
-    parent: &'a mut Declaration,
+pub struct CornerRadiusBuilder<
+    'declaration,
+    'render,
+    ImageElementData: 'render,
+    CustomElementData: 'render,
+> {
+    parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
 }
 
-impl CornerRadiusBuilder<'_> {
+impl<'declaration, 'render, ImageElementData: 'render, CustomElementData: 'render>
+    CornerRadiusBuilder<'declaration, 'render, ImageElementData, CustomElementData>
+{
     /// Creates a new `CornerRadiusBuilder` with the given parent `Declaration`.
     #[inline]
-    pub fn new(parent: &mut Declaration) -> CornerRadiusBuilder {
+    pub fn new(
+        parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
+    ) -> Self {
         CornerRadiusBuilder { parent }
     }
 
@@ -294,7 +318,7 @@ impl CornerRadiusBuilder<'_> {
 
     /// Returns the modified `Declaration`.
     #[inline]
-    pub fn end(&mut self) -> &mut Declaration {
+    pub fn end(&mut self) -> &mut Declaration<'render, ImageElementData, CustomElementData> {
         self.parent
     }
 }
diff --git a/src/layout.rs b/src/layout.rs
index 2403793..24f9a77 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -151,14 +151,23 @@ pub enum LayoutDirection {
 }
 
 /// Builder for configuring layout properties in a `Declaration`.
-pub struct LayoutBuilder<'a> {
-    parent: &'a mut Declaration,
+pub struct LayoutBuilder<
+    'declaration,
+    'render,
+    ImageElementData: 'render,
+    CustomElementData: 'render,
+> {
+    parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
 }
 
-impl LayoutBuilder<'_> {
+impl<'declaration, 'render, ImageElementData: 'render, CustomElementData: 'render>
+    LayoutBuilder<'declaration, 'render, ImageElementData, CustomElementData>
+{
     /// Creates a new `LayoutBuilder` with the given parent `Declaration`.
     #[inline]
-    pub fn new(parent: &mut Declaration) -> LayoutBuilder {
+    pub fn new(
+        parent: &'declaration mut Declaration<'render, ImageElementData, CustomElementData>,
+    ) -> Self {
         LayoutBuilder { parent }
     }
 
@@ -210,7 +219,7 @@ impl LayoutBuilder<'_> {
 
     /// Returns the modified `Declaration`.
     #[inline]
-    pub fn end(&mut self) -> &mut Declaration {
+    pub fn end(&mut self) -> &mut Declaration<'render, ImageElementData, CustomElementData> {
         self.parent
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index a2884d2..552c5e5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,6 +15,8 @@ pub mod raylib;
 
 mod mem;
 
+use core::marker::PhantomData;
+
 use crate::bindings::*;
 use errors::Error;
 use id::Id;
@@ -27,13 +29,15 @@ pub use color::Color;
 use text::TextConfig;
 
 use text::TextElementConfig;
-
 #[derive(Copy, Clone)]
-pub struct Declaration {
+pub struct Declaration<'render, ImageElementData: 'render, CustomElementData: 'render> {
     inner: Clay_ElementDeclaration,
+    _phantom: PhantomData<(&'render CustomElementData, &'render ImageElementData)>,
 }
 
-impl Declaration {
+impl<'render, ImageElementData: 'render, CustomElementData: 'render>
+    Declaration<'render, ImageElementData, CustomElementData>
+{
     #[inline]
     pub fn new() -> Self {
         crate::mem::zeroed_init()
@@ -59,32 +63,50 @@ impl Declaration {
     }
 
     #[inline]
-    pub fn layout(&mut self) -> layout::LayoutBuilder {
+    pub fn custom_element(&mut self, data: &'render CustomElementData) -> &mut Self {
+        self.inner.custom.customData = (data as *const CustomElementData).cast();
+        self
+    }
+
+    #[inline]
+    pub fn layout(
+        &mut self,
+    ) -> layout::LayoutBuilder<'_, 'render, ImageElementData, CustomElementData> {
         layout::LayoutBuilder::new(self)
     }
 
     #[inline]
-    pub fn image(&mut self) -> elements::ImageBuilder {
+    pub fn image(
+        &mut self,
+    ) -> elements::ImageBuilder<'_, 'render, ImageElementData, CustomElementData> {
         elements::ImageBuilder::new(self)
     }
 
     #[inline]
-    pub fn floating(&mut self) -> elements::FloatingBuilder {
+    pub fn floating(
+        &mut self,
+    ) -> elements::FloatingBuilder<'_, 'render, ImageElementData, CustomElementData> {
         elements::FloatingBuilder::new(self)
     }
 
     #[inline]
-    pub fn border(&mut self) -> elements::BorderBuilder {
+    pub fn border(
+        &mut self,
+    ) -> elements::BorderBuilder<'_, 'render, ImageElementData, CustomElementData> {
         elements::BorderBuilder::new(self)
     }
 
     #[inline]
-    pub fn corner_radius(&mut self) -> elements::CornerRadiusBuilder {
+    pub fn corner_radius(
+        &mut self,
+    ) -> elements::CornerRadiusBuilder<'_, 'render, ImageElementData, CustomElementData> {
         elements::CornerRadiusBuilder::new(self)
     }
 }
 
-impl Default for Declaration {
+impl<ImageElementData, CustomElementData> Default
+    for Declaration<'_, ImageElementData, CustomElementData>
+{
     fn default() -> Self {
         Self::new()
     }
@@ -135,13 +157,8 @@ unsafe extern "C" fn error_handler(error_data: Clay_ErrorData) {
     panic!("Clay Error: (type: {:?}) {}", error.type_, error.text);
 }
 
-pub struct DataRef<'a> {
-    pub(crate) ptr: *const core::ffi::c_void,
-    _phantom: core::marker::PhantomData<&'a ()>,
-}
-
 #[allow(dead_code)]
-pub struct Clay<'a> {
+pub struct Clay {
     /// Memory used internally by clay
     #[cfg(feature = "std")]
     _memory: Vec<u8>,
@@ -150,13 +167,114 @@ pub struct Clay<'a> {
     /// no_std case.
     #[cfg(not(feature = "std"))]
     _memory: *const core::ffi::c_void,
-    /// Phantom data to keep the lifetime of the memory
-    _phantom: core::marker::PhantomData<&'a ()>,
     /// Stores the raw pointer to the callback data for later cleanup
     text_measure_callback: Option<*const core::ffi::c_void>,
 }
 
-impl<'a> Clay<'a> {
+pub struct ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData> {
+    clay: &'clay mut Clay,
+    _phantom: core::marker::PhantomData<(&'render ImageElementData, &'render CustomElementData)>,
+    dropped: bool,
+}
+
+impl<'render, 'clay: 'render, ImageElementData: 'render, CustomElementData: 'render>
+    ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>
+{
+    /// Create an element, passing it's config and a function to add childrens
+    /// ```
+    /// // TODO: Add Example
+    /// ```
+    pub fn with<
+        F: FnOnce(&mut ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>),
+    >(
+        &mut self,
+        declaration: &Declaration<'render, ImageElementData, CustomElementData>,
+        f: F,
+    ) {
+        unsafe {
+            Clay_SetCurrentContext(self.clay.context);
+            Clay__OpenElement();
+            Clay__ConfigureOpenElement(declaration.inner);
+        }
+
+        f(self);
+
+        unsafe {
+            Clay__CloseElement();
+        }
+    }
+
+    pub fn end(
+        mut self,
+    ) -> impl Iterator<Item = RenderCommand<'render, ImageElementData, CustomElementData>> {
+        let array = unsafe { Clay_EndLayout() };
+        self.dropped = true;
+        let slice = unsafe { core::slice::from_raw_parts(array.internalArray, array.length as _) };
+        slice
+            .iter()
+            .map(|command| unsafe { RenderCommand::from_clay_render_command(*command) })
+    }
+
+    /// Generates a unique ID based on the given `label`.
+    ///
+    /// This ID is global and must be unique across the entire scope.
+    #[inline]
+    pub fn id(&self, label: &'render str) -> id::Id {
+        id::Id::new(label)
+    }
+
+    /// Generates a unique indexed ID based on the given `label` and `index`.
+    ///
+    /// This is useful when multiple elements share the same label but need distinct IDs.
+    #[inline]
+    pub fn id_index(&self, label: &'render str, index: u32) -> id::Id {
+        id::Id::new_index(label, index)
+    }
+
+    /// Generates a locally unique ID based on the given `label`.
+    ///
+    /// The ID is unique within a specific local scope but not globally.
+    #[inline]
+    pub fn id_local(&self, label: &'render str) -> id::Id {
+        id::Id::new_index_local(label, 0)
+    }
+
+    /// Generates a locally unique indexed ID based on the given `label` and `index`.
+    ///
+    /// This is useful for differentiating elements within a local scope while keeping their labels consistent.
+    #[inline]
+    pub fn id_index_local(&self, label: &'render str, index: u32) -> id::Id {
+        id::Id::new_index_local(label, index)
+    }
+
+    /// Adds a text element to the current open element or to the root layout
+    pub fn text(&self, text: &'render str, config: TextElementConfig) {
+        unsafe { Clay__OpenTextElement(text.into(), config.into()) };
+    }
+}
+impl<ImageElementData, CustomElementData> Drop
+    for ClayLayoutScope<'_, '_, ImageElementData, CustomElementData>
+{
+    fn drop(&mut self) {
+        if !self.dropped {
+            unsafe {
+                Clay_EndLayout();
+            }
+        }
+    }
+}
+impl Clay {
+    pub fn begin<'render, ImageElementData: 'render, CustomElementData: 'render>(
+        &mut self,
+    ) -> ClayLayoutScope<'_, 'render, ImageElementData, CustomElementData> {
+        unsafe { Clay_BeginLayout() };
+        ClayLayoutScope {
+            clay: self,
+            _phantom: core::marker::PhantomData,
+            dropped: false,
+        }
+    }
+
     #[cfg(feature = "std")]
     pub fn new(dimensions: Dimensions) -> Self {
         let memory_size = Self::required_memory_size();
@@ -180,20 +298,10 @@ impl<'a> Clay<'a> {
         Self {
             _memory: memory,
             context,
-            _phantom: core::marker::PhantomData,
             text_measure_callback: None,
         }
     }
 
-    /// Get a reference to the data to pass to clay or the builders. This is to ensure that the
-    /// data is not dropped before clay is done with it.
-    pub fn data<T>(&self, data: &T) -> DataRef<'a> {
-        DataRef {
-            ptr: data as *const T as *const core::ffi::c_void,
-            _phantom: core::marker::PhantomData,
-        }
-    }
-
     #[cfg(not(feature = "std"))]
     pub unsafe fn new_with_memory(dimensions: Dimensions, memory: *mut core::ffi::c_void) -> Self {
         let memory_size = Self::required_memory_size();
@@ -211,7 +319,6 @@ impl<'a> Clay<'a> {
         Self {
             _memory: memory,
             context,
-            _phantom: core::marker::PhantomData,
             text_measure_callback: None,
         }
     }
@@ -223,10 +330,13 @@ impl<'a> Clay<'a> {
 
     /// Set the callback for text measurement with user data
     #[cfg(feature = "std")]
-    pub fn set_measure_text_function_user_data<F, T>(&mut self, userdata: T, callback: F)
-    where
-        F: Fn(&str, &TextConfig, &'a mut T) -> Dimensions + 'static,
-        T: 'a,
+    pub fn set_measure_text_function_user_data<'clay, F, T>(
+        &'clay mut self,
+        userdata: T,
+        callback: F,
+    ) where
+        F: Fn(&str, &TextConfig, &'clay mut T) -> Dimensions + 'static,
+        T: 'clay,
     {
         // Box the callback and userdata together
         let boxed = Box::new((callback, userdata));
@@ -281,55 +391,23 @@ impl<'a> Clay<'a> {
         Clay_SetMeasureTextFunction(Some(callback), user_data);
     }
 
-    /// Generates a unique ID based on the given `label`.
-    ///
-    /// This ID is global and must be unique across the entire scope.
-    #[inline]
-    pub fn id(&self, label: &'a str) -> id::Id {
-        id::Id::new(label)
-    }
-
-    /// Generates a unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful when multiple elements share the same label but need distinct IDs.
-    #[inline]
-    pub fn id_index(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index(label, index)
-    }
-
-    /// Generates a locally unique ID based on the given `label`.
-    ///
-    /// The ID is unique within a specific local scope but not globally.
-    #[inline]
-    pub fn id_local(&self, label: &'a str) -> id::Id {
-        id::Id::new_index_local(label, 0)
-    }
-
-    /// Generates a locally unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful for differentiating elements within a local scope while keeping their labels consistent.
-    #[inline]
-    pub fn id_index_local(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index_local(label, index)
-    }
-
     /// Sets the maximum number of element that clay supports
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_element_count(&self, max_element_count: u32) {
+    pub fn max_element_count(&mut self, max_element_count: u32) {
         unsafe {
             Clay_SetMaxElementCount(max_element_count as _);
         }
     }
     /// Sets the capacity of the cache used for text in the measure text function
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_measure_text_cache_word_count(&self, count: u32) {
+    pub fn max_measure_text_cache_word_count(&mut self, count: u32) {
         unsafe {
             Clay_SetMaxElementCount(count as _);
         }
     }
 
     /// Enables or disables the debug mode of clay
-    pub fn enable_debug_mode(&self, enable: bool) {
+    pub fn enable_debug_mode(&mut self, enable: bool) {
         unsafe {
             Clay_SetDebugModeEnabled(enable);
         }
@@ -337,20 +415,20 @@ impl<'a> Clay<'a> {
 
     /// Sets the dimensions of the global layout, use if, for example the window size you render to
     /// changed
-    pub fn layout_dimensions(&self, dimensions: Dimensions) {
+    pub fn layout_dimensions(&mut self, dimensions: Dimensions) {
         unsafe {
             Clay_SetLayoutDimensions(dimensions.into());
         }
     }
     /// Updates the state of the pointer for clay. Used to update scroll containers and for
     /// interactions functions
-    pub fn pointer_state(&self, position: Vector2, is_down: bool) {
+    pub fn pointer_state(&mut self, position: Vector2, is_down: bool) {
         unsafe {
             Clay_SetPointerState(position.into(), is_down);
         }
     }
     pub fn update_scroll_containers(
-        &self,
+        &mut self,
         drag_scrolling_enabled: bool,
         scroll_delta: Vector2,
         delta_time: f32,
@@ -382,45 +460,10 @@ impl<'a> Clay<'a> {
             None
         }
     }
-
-    #[inline]
-    pub fn begin(&self) {
-        unsafe { Clay_BeginLayout() };
-    }
-
-    #[inline]
-    pub fn end(&self) -> impl Iterator<Item = RenderCommand> {
-        let array = unsafe { Clay_EndLayout() };
-        let slice = unsafe { core::slice::from_raw_parts(array.internalArray, array.length as _) };
-        slice.iter().map(|command| RenderCommand::from(*command))
-    }
-
-    /// Create an element, passing it's config and a function to add childrens
-    /// ```
-    /// // TODO: Add Example
-    /// ```
-    pub fn with<F: FnOnce(&Clay)>(&self, declaration: &Declaration, f: F) {
-        unsafe {
-            Clay_SetCurrentContext(self.context);
-            Clay__OpenElement();
-            Clay__ConfigureOpenElement(declaration.inner);
-        };
-
-        f(self);
-
-        unsafe {
-            Clay__CloseElement();
-        }
-    }
-
-    /// Adds a text element to the current open element or to the root layout
-    pub fn text(&self, text: &str, config: TextElementConfig) {
-        unsafe { Clay__OpenTextElement(text.into(), config.into()) };
-    }
 }
 
 #[cfg(feature = "std")]
-impl Drop for Clay<'_> {
+impl Drop for Clay {
     fn drop(&mut self) {
         unsafe {
             if let Some(ptr) = self.text_measure_callback {
@@ -483,7 +526,7 @@ mod tests {
             Dimensions::default()
         });
 
-        clay.begin();
+        let mut clay = clay.begin::<(), ()>();
 
         clay.with(&Declaration::new()
             .id(clay.id("parent_rect"))
diff --git a/src/raylib.rs b/src/raylib.rs
index 7718fb4..55d457c 100644
--- a/src/raylib.rs
+++ b/src/raylib.rs
@@ -3,7 +3,6 @@ use raylib::{
     ffi::{BeginScissorMode, EndScissorMode},
     prelude::*,
 };
-use thiserror::Error;
 
 macro_rules! clay_to_raylib_color {
     ($color:expr) => {
@@ -27,17 +26,12 @@ macro_rules! clay_to_raylib_rect {
     };
 }
 
-#[derive(Error, Debug)]
-pub enum RaylibRendererError {
-    #[error("Failed to get image data")]
-    ImageDataError,
-}
-
 #[doc = "This is a direct* port of Clay's raylib renderer. See [the C implementation](https://github.com/nicbarker/clay/blob/main/renderers/raylib/clay_renderer_raylib.c) for more info."]
-pub fn clay_raylib_render<'a>(
-    d: &mut RaylibDrawHandle<'_>,
-    render_commands: impl Iterator<Item = RenderCommand<'a>>,
-) -> Result<(), RaylibRendererError> {
+pub fn clay_raylib_render<'rl, 'a, CustomElementData: 'a>(
+    d: &mut RaylibDrawHandle<'rl>,
+    render_commands: impl Iterator<Item = RenderCommand<'a, Texture2D, CustomElementData>>,
+    mut handle_custom_element: impl FnMut(&CustomElementData, &mut RaylibDrawHandle<'rl>),
+) {
     for command in render_commands {
         match command.config {
             RenderCommandConfig::Text(text) => {
@@ -52,13 +46,7 @@ pub fn clay_raylib_render<'a>(
             }
 
             RenderCommandConfig::Image(image) => {
-                // safety: as safe as the reference implementation.
-                let texture = unsafe {
-                    let image_data = image.data as *const Texture2D;
-                    image_data
-                        .as_ref()
-                        .ok_or_else(|| RaylibRendererError::ImageDataError)?
-                };
+                let texture = image.data;
 
                 d.draw_texture_ex(
                     texture,
@@ -235,10 +223,8 @@ pub fn clay_raylib_render<'a>(
                     );
                 }
             }
-
-            _ => {}
+            RenderCommandConfig::Custom(custom) => handle_custom_element(custom.data, &mut *d),
+            RenderCommandConfig::None() => {}
         }
     }
-
-    Ok(())
 }
diff --git a/src/render_commands.rs b/src/render_commands.rs
index 9b6c18c..217a17a 100644
--- a/src/render_commands.rs
+++ b/src/render_commands.rs
@@ -71,22 +71,22 @@ pub struct Border {
 
 /// Represents an image with defined dimensions and data.
 #[derive(Debug, Clone)]
-pub struct Image {
+pub struct Image<'a, ImageElementData> {
     /// The dimensions of the image.
     pub dimensions: Dimensions,
     /// A pointer to the image data.
-    pub data: *const core::ffi::c_void,
+    pub data: &'a ImageElementData,
 }
 
 /// Represents a custom element with a background color, corner radii, and associated data.
 #[derive(Debug, Clone)]
-pub struct Custom {
+pub struct Custom<'a, CustomElementData> {
     /// The background color of the custom element.
     pub background_color: Color,
     /// The corner radii for rounded edges.
     pub corner_radii: CornerRadii,
     /// A pointer to additional custom data.
-    pub data: *const core::ffi::c_void,
+    pub data: &'a CustomElementData,
 }
 
 impl From<Clay_RectangleRenderData> for Rectangle {
@@ -118,11 +118,11 @@ impl From<Clay_TextRenderData> for Text<'_> {
     }
 }
 
-impl From<Clay_ImageRenderData> for Image {
-    fn from(value: Clay_ImageRenderData) -> Self {
+impl<ImageElementData> Image<'_, ImageElementData> {
+    pub(crate) unsafe fn from_clay_image_render_data(value: Clay_ImageRenderData) -> Self {
         Self {
             dimensions: value.sourceDimensions.into(),
-            data: value.imageData,
+            data: unsafe { &*value.imageData.cast() },
         }
     }
 }
@@ -155,31 +155,33 @@ impl From<Clay_BorderRenderData> for Border {
     }
 }
 
-impl From<Clay_CustomRenderData> for Custom {
-    fn from(value: Clay_CustomRenderData) -> Self {
+impl<CustomElementData> Custom<'_, CustomElementData> {
+    pub(crate) unsafe fn from_clay_custom_element_data(value: Clay_CustomRenderData) -> Self {
         Self {
             background_color: value.backgroundColor.into(),
             corner_radii: value.cornerRadius.into(),
-            data: value.customData,
+            data: unsafe { &*value.customData.cast() },
         }
     }
 }
 
 #[derive(Debug, Clone)]
-pub enum RenderCommandConfig<'a> {
+pub enum RenderCommandConfig<'a, ImageElementData, CustomElementData> {
     None(),
     Rectangle(Rectangle),
     Border(Border),
     Text(Text<'a>),
-    Image(Image),
+    Image(Image<'a, ImageElementData>),
     ScissorStart(),
     ScissorEnd(),
-    Custom(Custom),
+    Custom(Custom<'a, CustomElementData>),
 }
 
-impl From<&Clay_RenderCommand> for RenderCommandConfig<'_> {
+impl<ImageElementData, CustomElementData>
+    RenderCommandConfig<'_, ImageElementData, CustomElementData>
+{
     #[allow(non_upper_case_globals)]
-    fn from(value: &Clay_RenderCommand) -> Self {
+    pub(crate) unsafe fn from_clay_render_command(value: &Clay_RenderCommand) -> Self {
         match value.commandType {
             Clay_RenderCommandType_CLAY_RENDER_COMMAND_TYPE_NONE => Self::None(),
             Clay_RenderCommandType_CLAY_RENDER_COMMAND_TYPE_RECTANGLE => {
@@ -192,13 +194,13 @@ impl From<&Clay_RenderCommand> for RenderCommandConfig<'_> {
                 Self::Border(Border::from(*unsafe { &value.renderData.border }))
             }
             Clay_RenderCommandType_CLAY_RENDER_COMMAND_TYPE_IMAGE => {
-                Self::Image(Image::from(*unsafe { &value.renderData.image }))
+                Self::Image(unsafe { Image::from_clay_image_render_data(value.renderData.image) })
             }
             Clay_RenderCommandType_CLAY_RENDER_COMMAND_TYPE_SCISSOR_START => Self::ScissorStart(),
             Clay_RenderCommandType_CLAY_RENDER_COMMAND_TYPE_SCISSOR_END => Self::ScissorEnd(),
-            Clay_RenderCommandType_CLAY_RENDER_COMMAND_TYPE_CUSTOM => {
-                Self::Custom(Custom::from(*unsafe { &value.renderData.custom }))
-            }
+            Clay_RenderCommandType_CLAY_RENDER_COMMAND_TYPE_CUSTOM => Self::Custom(unsafe {
+                Custom::from_clay_custom_element_data(value.renderData.custom)
+            }),
             _ => unreachable!(),
         }
     }
@@ -206,11 +208,11 @@ impl From<&Clay_RenderCommand> for RenderCommandConfig<'_> {
 
 /// Represents a render command for drawing an element on the screen.
 #[derive(Debug, Clone)]
-pub struct RenderCommand<'a> {
+pub struct RenderCommand<'a, ImageElementData, CustomElementData> {
     /// The bounding box defining the area occupied by the element.
     pub bounding_box: BoundingBox,
     /// The specific configuration for rendering this command.
-    pub config: RenderCommandConfig<'a>,
+    pub config: RenderCommandConfig<'a, ImageElementData, CustomElementData>,
     /// A unique identifier for the render command.
     pub id: u32,
     /// The z-index determines the stacking order of elements.
@@ -218,13 +220,13 @@ pub struct RenderCommand<'a> {
     pub z_index: i16,
 }
 
-impl From<Clay_RenderCommand> for RenderCommand<'_> {
-    fn from(value: Clay_RenderCommand) -> Self {
+impl<ImageElementData, CustomElementData> RenderCommand<'_, ImageElementData, CustomElementData> {
+    pub(crate) unsafe fn from_clay_render_command(value: Clay_RenderCommand) -> Self {
         Self {
             id: value.id,
             z_index: value.zIndex,
             bounding_box: value.boundingBox.into(),
-            config: (&value).into(),
+            config: unsafe { RenderCommandConfig::from_clay_render_command(&value) },
         }
     }
 }


Reviews:
user: login: Mathys-Gasnier
body: Seems like the workflows doesn't pass, could you get them to pass please ?
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-04T07:21:18Z
user: login: emoon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:29:21Z
user: login: emoon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:30:23Z
user: login: coffeeispower
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:19:33Z
user: login: coffeeispower
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:20:29Z
user: login: emoon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:32:42Z
user: login: emoon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:37:19Z
user: login: coffeeispower
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:44:54Z
user: login: coffeeispower
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:55:51Z
user: login: emoon
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T09:27:37Z
user: login: Mathys-Gasnier
body: Last little thing, but it looks good
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T10:02:22Z
user: login: coffeeispower
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T11:00:55Z
user: login: Mathys-Gasnier
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-04T13:26:45Z

Review comments:
diff_hunk: @@ -281,76 +372,44 @@ impl<'a> Clay<'a> {
         Clay_SetMeasureTextFunction(Some(callback), user_data);
     }
 
-    /// Generates a unique ID based on the given `label`.
-    ///
-    /// This ID is global and must be unique across the entire scope.
-    #[inline]
-    pub fn id(&self, label: &'a str) -> id::Id {
-        id::Id::new(label)
-    }
-
-    /// Generates a unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful when multiple elements share the same label but need distinct IDs.
-    #[inline]
-    pub fn id_index(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index(label, index)
-    }
-
-    /// Generates a locally unique ID based on the given `label`.
-    ///
-    /// The ID is unique within a specific local scope but not globally.
-    #[inline]
-    pub fn id_local(&self, label: &'a str) -> id::Id {
-        id::Id::new_index_local(label, 0)
-    }
-
-    /// Generates a locally unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful for differentiating elements within a local scope while keeping their labels consistent.
-    #[inline]
-    pub fn id_index_local(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index_local(label, index)
-    }
-
     /// Sets the maximum number of element that clay supports
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_element_count(&self, max_element_count: u32) {
+    pub fn max_element_count(&mut self, max_element_count: u32) {
         unsafe {
             Clay_SetMaxElementCount(max_element_count as _);
         }
     }
     /// Sets the capacity of the cache used for text in the measure text function
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_measure_text_cache_word_count(&self, count: u32) {
+    pub fn max_measure_text_cache_word_count(&mut self, count: u32) {
         unsafe {
             Clay_SetMaxElementCount(count as _);
         }
     }
 
     /// Enables or disables the debug mode of clay
-    pub fn enable_debug_mode(&self, enable: bool) {
+    pub fn enable_debug_mode(&mut self, enable: bool) {
         unsafe {
             Clay_SetDebugModeEnabled(enable);
         }
     }
 
     /// Sets the dimensions of the global layout, use if, for example the window size you render to
     /// changed
-    pub fn layout_dimensions(&self, dimensions: Dimensions) {
+    pub fn layout_dimensions(&mut self, dimensions: Dimensions) {
         unsafe {
             Clay_SetLayoutDimensions(dimensions.into());
         }
     }
     /// Updates the state of the pointer for clay. Used to update scroll containers and for
     /// interactions functions
-    pub fn pointer_state(&self, position: Vector2, is_down: bool) {
+    pub fn pointer_state(&mut self, position: Vector2, is_down: bool) {
path: src/lib.rs
user: login: emoon
body: Why is this changed to mutable?
created_at: 2025-03-04T07:29:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,28 +93,18 @@ impl ImageBuilder<'_> {
     /// Sets the image data.
     /// The data must be created using [`Clay::data`].
     #[inline]
-    pub fn data(&mut self, data: DataRef) -> &mut Self {
-        self.parent.inner.image.imageData = data.ptr as *mut c_void;
+    pub fn data(&mut self, data: &'render mut ImageElementData) -> &mut Self {
path: src/elements.rs
user: login: emoon
body: This doesn't have to be mutable
created_at: 2025-03-04T07:30:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,28 +93,18 @@ impl ImageBuilder<'_> {
     /// Sets the image data.
     /// The data must be created using [`Clay::data`].
     #[inline]
-    pub fn data(&mut self, data: DataRef) -> &mut Self {
-        self.parent.inner.image.imageData = data.ptr as *mut c_void;
+    pub fn data(&mut self, data: &'render mut ImageElementData) -> &mut Self {
path: src/elements.rs
user: login: coffeeispower
body: it's because it was already mutable so I just kept it
created_at: 2025-03-04T08:19:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -281,76 +372,44 @@ impl<'a> Clay<'a> {
         Clay_SetMeasureTextFunction(Some(callback), user_data);
     }
 
-    /// Generates a unique ID based on the given `label`.
-    ///
-    /// This ID is global and must be unique across the entire scope.
-    #[inline]
-    pub fn id(&self, label: &'a str) -> id::Id {
-        id::Id::new(label)
-    }
-
-    /// Generates a unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful when multiple elements share the same label but need distinct IDs.
-    #[inline]
-    pub fn id_index(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index(label, index)
-    }
-
-    /// Generates a locally unique ID based on the given `label`.
-    ///
-    /// The ID is unique within a specific local scope but not globally.
-    #[inline]
-    pub fn id_local(&self, label: &'a str) -> id::Id {
-        id::Id::new_index_local(label, 0)
-    }
-
-    /// Generates a locally unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful for differentiating elements within a local scope while keeping their labels consistent.
-    #[inline]
-    pub fn id_index_local(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index_local(label, index)
-    }
-
     /// Sets the maximum number of element that clay supports
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_element_count(&self, max_element_count: u32) {
+    pub fn max_element_count(&mut self, max_element_count: u32) {
         unsafe {
             Clay_SetMaxElementCount(max_element_count as _);
         }
     }
     /// Sets the capacity of the cache used for text in the measure text function
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_measure_text_cache_word_count(&self, count: u32) {
+    pub fn max_measure_text_cache_word_count(&mut self, count: u32) {
         unsafe {
             Clay_SetMaxElementCount(count as _);
         }
     }
 
     /// Enables or disables the debug mode of clay
-    pub fn enable_debug_mode(&self, enable: bool) {
+    pub fn enable_debug_mode(&mut self, enable: bool) {
         unsafe {
             Clay_SetDebugModeEnabled(enable);
         }
     }
 
     /// Sets the dimensions of the global layout, use if, for example the window size you render to
     /// changed
-    pub fn layout_dimensions(&self, dimensions: Dimensions) {
+    pub fn layout_dimensions(&mut self, dimensions: Dimensions) {
         unsafe {
             Clay_SetLayoutDimensions(dimensions.into());
         }
     }
     /// Updates the state of the pointer for clay. Used to update scroll containers and for
     /// interactions functions
-    pub fn pointer_state(&self, position: Vector2, is_down: bool) {
+    pub fn pointer_state(&mut self, position: Vector2, is_down: bool) {
path: src/lib.rs
user: login: coffeeispower
body: because it mutates the internal state of clay
created_at: 2025-03-04T08:20:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -281,76 +372,44 @@ impl<'a> Clay<'a> {
         Clay_SetMeasureTextFunction(Some(callback), user_data);
     }
 
-    /// Generates a unique ID based on the given `label`.
-    ///
-    /// This ID is global and must be unique across the entire scope.
-    #[inline]
-    pub fn id(&self, label: &'a str) -> id::Id {
-        id::Id::new(label)
-    }
-
-    /// Generates a unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful when multiple elements share the same label but need distinct IDs.
-    #[inline]
-    pub fn id_index(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index(label, index)
-    }
-
-    /// Generates a locally unique ID based on the given `label`.
-    ///
-    /// The ID is unique within a specific local scope but not globally.
-    #[inline]
-    pub fn id_local(&self, label: &'a str) -> id::Id {
-        id::Id::new_index_local(label, 0)
-    }
-
-    /// Generates a locally unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful for differentiating elements within a local scope while keeping their labels consistent.
-    #[inline]
-    pub fn id_index_local(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index_local(label, index)
-    }
-
     /// Sets the maximum number of element that clay supports
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_element_count(&self, max_element_count: u32) {
+    pub fn max_element_count(&mut self, max_element_count: u32) {
         unsafe {
             Clay_SetMaxElementCount(max_element_count as _);
         }
     }
     /// Sets the capacity of the cache used for text in the measure text function
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_measure_text_cache_word_count(&self, count: u32) {
+    pub fn max_measure_text_cache_word_count(&mut self, count: u32) {
         unsafe {
             Clay_SetMaxElementCount(count as _);
         }
     }
 
     /// Enables or disables the debug mode of clay
-    pub fn enable_debug_mode(&self, enable: bool) {
+    pub fn enable_debug_mode(&mut self, enable: bool) {
         unsafe {
             Clay_SetDebugModeEnabled(enable);
         }
     }
 
     /// Sets the dimensions of the global layout, use if, for example the window size you render to
     /// changed
-    pub fn layout_dimensions(&self, dimensions: Dimensions) {
+    pub fn layout_dimensions(&mut self, dimensions: Dimensions) {
         unsafe {
             Clay_SetLayoutDimensions(dimensions.into());
         }
     }
     /// Updates the state of the pointer for clay. Used to update scroll containers and for
     /// interactions functions
-    pub fn pointer_state(&self, position: Vector2, is_down: bool) {
+    pub fn pointer_state(&mut self, position: Vector2, is_down: bool) {
path: src/lib.rs
user: login: emoon
body: That is hidden from the perspective of Rust tho.
created_at: 2025-03-04T08:32:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,28 +93,18 @@ impl ImageBuilder<'_> {
     /// Sets the image data.
     /// The data must be created using [`Clay::data`].
     #[inline]
-    pub fn data(&mut self, data: DataRef) -> &mut Self {
-        self.parent.inner.image.imageData = data.ptr as *mut c_void;
+    pub fn data(&mut self, data: &'render mut ImageElementData) -> &mut Self {
path: src/elements.rs
user: login: emoon
body: This has to be changed be non-mutable because this will just cause more head ace for users.
created_at: 2025-03-04T08:37:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,28 +93,18 @@ impl ImageBuilder<'_> {
     /// Sets the image data.
     /// The data must be created using [`Clay::data`].
     #[inline]
-    pub fn data(&mut self, data: DataRef) -> &mut Self {
-        self.parent.inner.image.imageData = data.ptr as *mut c_void;
+    pub fn data(&mut self, data: &'render mut ImageElementData) -> &mut Self {
path: src/elements.rs
user: login: coffeeispower
body: I agree
created_at: 2025-03-04T08:44:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -281,76 +372,44 @@ impl<'a> Clay<'a> {
         Clay_SetMeasureTextFunction(Some(callback), user_data);
     }
 
-    /// Generates a unique ID based on the given `label`.
-    ///
-    /// This ID is global and must be unique across the entire scope.
-    #[inline]
-    pub fn id(&self, label: &'a str) -> id::Id {
-        id::Id::new(label)
-    }
-
-    /// Generates a unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful when multiple elements share the same label but need distinct IDs.
-    #[inline]
-    pub fn id_index(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index(label, index)
-    }
-
-    /// Generates a locally unique ID based on the given `label`.
-    ///
-    /// The ID is unique within a specific local scope but not globally.
-    #[inline]
-    pub fn id_local(&self, label: &'a str) -> id::Id {
-        id::Id::new_index_local(label, 0)
-    }
-
-    /// Generates a locally unique indexed ID based on the given `label` and `index`.
-    ///
-    /// This is useful for differentiating elements within a local scope while keeping their labels consistent.
-    #[inline]
-    pub fn id_index_local(&self, label: &'a str, index: u32) -> id::Id {
-        id::Id::new_index_local(label, index)
-    }
-
     /// Sets the maximum number of element that clay supports
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_element_count(&self, max_element_count: u32) {
+    pub fn max_element_count(&mut self, max_element_count: u32) {
         unsafe {
             Clay_SetMaxElementCount(max_element_count as _);
         }
     }
     /// Sets the capacity of the cache used for text in the measure text function
     /// **Use only if you know what you are doing or your getting errors from clay**
-    pub fn max_measure_text_cache_word_count(&self, count: u32) {
+    pub fn max_measure_text_cache_word_count(&mut self, count: u32) {
         unsafe {
             Clay_SetMaxElementCount(count as _);
         }
     }
 
     /// Enables or disables the debug mode of clay
-    pub fn enable_debug_mode(&self, enable: bool) {
+    pub fn enable_debug_mode(&mut self, enable: bool) {
         unsafe {
             Clay_SetDebugModeEnabled(enable);
         }
     }
 
     /// Sets the dimensions of the global layout, use if, for example the window size you render to
     /// changed
-    pub fn layout_dimensions(&self, dimensions: Dimensions) {
+    pub fn layout_dimensions(&mut self, dimensions: Dimensions) {
         unsafe {
             Clay_SetLayoutDimensions(dimensions.into());
         }
     }
     /// Updates the state of the pointer for clay. Used to update scroll containers and for
     /// interactions functions
-    pub fn pointer_state(&self, position: Vector2, is_down: bool) {
+    pub fn pointer_state(&mut self, position: Vector2, is_down: bool) {
path: src/lib.rs
user: login: coffeeispower
body: it's to make sure that there's no 2 places in the code updating the pointer state at the same time causing data races
created_at: 2025-03-04T08:55:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -150,13 +167,114 @@ pub struct Clay<'a> {
     /// no_std case.
     #[cfg(not(feature = "std"))]
     _memory: *const core::ffi::c_void,
-    /// Phantom data to keep the lifetime of the memory
-    _phantom: core::marker::PhantomData<&'a ()>,
     /// Stores the raw pointer to the callback data for later cleanup
     text_measure_callback: Option<*const core::ffi::c_void>,
 }
 
-impl<'a> Clay<'a> {
+pub struct ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData> {
+    clay: &'clay mut Clay,
+    _phantom: core::marker::PhantomData<(&'render ImageElementData, &'render CustomElementData)>,
+    dropped: bool,
+}
+
+impl<'render, 'clay: 'render, ImageElementData: 'render, CustomElementData: 'render>
+    ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>
+{
+    /// Create an element, passing it's config and a function to add childrens
+    /// ```
+    /// // TODO: Add Example
+    /// ```
+    pub fn with<
+        F: FnOnce(&mut ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>),
+    >(
+        &mut self,
+        declaration: &Declaration<'render, ImageElementData, CustomElementData>,
+        f: F,
+    ) {
+        unsafe {
+            Clay_SetCurrentContext(self.clay.context);
+            Clay__OpenElement();
+            Clay__ConfigureOpenElement(declaration.inner);
+        }
+
+        f(self);
+
+        unsafe {
+            Clay__CloseElement();
+        }
+    }
+
+    pub fn end(
+        mut self,
+    ) -> impl Iterator<Item = RenderCommand<'render, ImageElementData, CustomElementData>> {
+        let array = unsafe { Clay_EndLayout() };
+        self.dropped = true;
+        let slice = unsafe { core::slice::from_raw_parts(array.internalArray, array.length as _) };
+        slice
+            .iter()
+            .map(|command| unsafe { RenderCommand::from_clay_render_command(*command) })
+    }
+
+    /// Generates a unique ID based on the given `label`.
+    ///
+    /// This ID is global and must be unique across the entire scope.
+    #[inline]
+    pub fn id(&self, label: &'clay str) -> id::Id {
+        id::Id::new(label)
+    }
+
+    /// Generates a unique indexed ID based on the given `label` and `index`.
+    ///
+    /// This is useful when multiple elements share the same label but need distinct IDs.
+    #[inline]
+    pub fn id_index(&self, label: &'clay str, index: u32) -> id::Id {
+        id::Id::new_index(label, index)
+    }
+
+    /// Generates a locally unique ID based on the given `label`.
+    ///
+    /// The ID is unique within a specific local scope but not globally.
+    #[inline]
+    pub fn id_local(&self, label: &'clay str) -> id::Id {
+        id::Id::new_index_local(label, 0)
+    }
+
+    /// Generates a locally unique indexed ID based on the given `label` and `index`.
+    ///
+    /// This is useful for differentiating elements within a local scope while keeping their labels consistent.
+    #[inline]
+    pub fn id_index_local(&self, label: &'clay str, index: u32) -> id::Id {
+        id::Id::new_index_local(label, index)
+    }
+
+    /// Adds a text element to the current open element or to the root layout
+    pub fn text(&self, text: &str, config: TextElementConfig) {
path: src/lib.rs
user: login: Mathys-Gasnier
body: I think we need to ensure the lifetime of `text` here.
In the first version of the bindings i had a lot of issues with text...
created_at: 2025-03-04T10:00:50Z
author_association: MEMBER
diff_hunk: @@ -150,13 +167,114 @@ pub struct Clay<'a> {
     /// no_std case.
     #[cfg(not(feature = "std"))]
     _memory: *const core::ffi::c_void,
-    /// Phantom data to keep the lifetime of the memory
-    _phantom: core::marker::PhantomData<&'a ()>,
     /// Stores the raw pointer to the callback data for later cleanup
     text_measure_callback: Option<*const core::ffi::c_void>,
 }
 
-impl<'a> Clay<'a> {
+pub struct ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData> {
+    clay: &'clay mut Clay,
+    _phantom: core::marker::PhantomData<(&'render ImageElementData, &'render CustomElementData)>,
+    dropped: bool,
+}
+
+impl<'render, 'clay: 'render, ImageElementData: 'render, CustomElementData: 'render>
+    ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>
+{
+    /// Create an element, passing it's config and a function to add childrens
+    /// ```
+    /// // TODO: Add Example
+    /// ```
+    pub fn with<
+        F: FnOnce(&mut ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>),
+    >(
+        &mut self,
+        declaration: &Declaration<'render, ImageElementData, CustomElementData>,
+        f: F,
+    ) {
+        unsafe {
+            Clay_SetCurrentContext(self.clay.context);
+            Clay__OpenElement();
+            Clay__ConfigureOpenElement(declaration.inner);
+        }
+
+        f(self);
+
+        unsafe {
+            Clay__CloseElement();
+        }
+    }
+
+    pub fn end(
+        mut self,
+    ) -> impl Iterator<Item = RenderCommand<'render, ImageElementData, CustomElementData>> {
+        let array = unsafe { Clay_EndLayout() };
+        self.dropped = true;
+        let slice = unsafe { core::slice::from_raw_parts(array.internalArray, array.length as _) };
+        slice
+            .iter()
+            .map(|command| unsafe { RenderCommand::from_clay_render_command(*command) })
+    }
+
+    /// Generates a unique ID based on the given `label`.
+    ///
+    /// This ID is global and must be unique across the entire scope.
+    #[inline]
+    pub fn id(&self, label: &'clay str) -> id::Id {
+        id::Id::new(label)
+    }
+
+    /// Generates a unique indexed ID based on the given `label` and `index`.
+    ///
+    /// This is useful when multiple elements share the same label but need distinct IDs.
+    #[inline]
+    pub fn id_index(&self, label: &'clay str, index: u32) -> id::Id {
+        id::Id::new_index(label, index)
+    }
+
+    /// Generates a locally unique ID based on the given `label`.
+    ///
+    /// The ID is unique within a specific local scope but not globally.
+    #[inline]
+    pub fn id_local(&self, label: &'clay str) -> id::Id {
+        id::Id::new_index_local(label, 0)
+    }
+
+    /// Generates a locally unique indexed ID based on the given `label` and `index`.
+    ///
+    /// This is useful for differentiating elements within a local scope while keeping their labels consistent.
+    #[inline]
+    pub fn id_index_local(&self, label: &'clay str, index: u32) -> id::Id {
+        id::Id::new_index_local(label, index)
+    }
+
+    /// Adds a text element to the current open element or to the root layout
+    pub fn text(&self, text: &str, config: TextElementConfig) {
path: src/lib.rs
user: login: coffeeispower
body: oh yeah for sure
created_at: 2025-03-04T11:00:55Z
author_association: CONTRIBUTOR

PR comments:
user: login: Mathys-Gasnier
created_at: 2025-03-03T18:58:41Z
body: I think having the generic for custom elements directly on the Clay struct limits a bit what can be done with it...
We might not have any other choice, but it forces ALL custom elements to contain the same data, which might not be true when using clay in C...
author_association: MEMBER
user: login: coffeeispower
created_at: 2025-03-03T20:32:11Z
body: >  We might not have any other choice, but it forces ALL custom elements to contain the same data, which might not be true when using clay in C...

But how would you check it in the renderer? You would probably use a tagged union anyway, and in rust tagged unions are called, you guessed it, enums.
author_association: CONTRIBUTOR
user: login: coffeeispower
created_at: 2025-03-03T22:55:07Z
body: I think I finished all changes I wanted to do to this library, @emoon @Mathys-Gasnier can you guys take a look?
author_association: CONTRIBUTOR
user: login: emoon
created_at: 2025-03-04T08:04:55Z
body: I'm not sure I'm a big fan of the `BegunClay` name. Can we perhaps call it `ClayScope` or something like that instead?

author_association: CONTRIBUTOR
user: login: coffeeispower
created_at: 2025-03-04T08:21:14Z
body: > I'm not sure I'm a big fan of the BegunClay name. Can we perhaps call it ClayScope or something like that instead?

Me neither, i just used that name because i haven't thought of a better name, will change that
author_association: CONTRIBUTOR
user: login: emoon
created_at: 2025-03-04T08:49:11Z
body: > I think having the generic for custom elements directly on the Clay struct limits a bit what can be done with it...
> We might not have any other choice, but it forces ALL custom elements to contain the same data, which might not be true when using clay in C...

While this is true, you can still just use a handle (u64) or a pointer if you want as input type and use it how you wish at the render stage. In my program I just use u64 values for the Images because I load them in an async fashion on threads so I just get the real data at the render stage instead.
author_association: CONTRIBUTOR
user: login: emoon
created_at: 2025-03-04T09:19:53Z
body: Changes looks good to me now now :+1: I will do another pass on them once it's merged.
author_association: CONTRIBUTOR
user: login: emoon
created_at: 2025-03-04T13:30:36Z
body: Thanks for doing this. Like I said; I will do another pass on the code and do my own PRs if some changes are needed (I need to fix some other stuff anyway)
author_association: CONTRIBUTOR