1#![cfg_attr(not(feature = "std"), no_std)]
2
3pub mod bindings;
4pub mod color;
5pub mod elements;
6pub mod errors;
7pub mod id;
8pub mod layout;
9pub mod math;
10pub mod render_commands;
11pub mod text;
12
13mod mem;
14pub mod renderers;
15
16use core::marker::PhantomData;
17
18pub use crate::bindings::*;
19use errors::Error;
20use id::Id;
21use math::{BoundingBox, Dimensions, Vector2};
22use render_commands::RenderCommand;
23
24pub use color::Color;
25
26#[cfg(feature = "std")]
27use text::TextConfig;
28
29use text::TextElementConfig;
30#[derive(Copy, Clone)]
31pub struct Declaration<'render, ImageElementData: 'render, CustomElementData: 'render> {
32 inner: Clay_ElementDeclaration,
33 _phantom: PhantomData<(&'render CustomElementData, &'render ImageElementData)>,
34}
35
36impl<'render, ImageElementData: 'render, CustomElementData: 'render>
37 Declaration<'render, ImageElementData, CustomElementData>
38{
39 #[inline]
40 pub fn new() -> Self {
41 crate::mem::zeroed_init()
42 }
43
44 #[inline]
45 pub fn background_color(&mut self, color: Color) -> &mut Self {
46 self.inner.backgroundColor = color.into();
47 self
48 }
49
50 #[inline]
52 pub fn aspect_ratio(&mut self, aspect_ratio: f32) -> &mut Self {
53 self.inner.aspectRatio.aspectRatio = aspect_ratio;
54 self
55 }
56
57 #[inline]
58 pub fn clip(&mut self, horizontal: bool, vertical: bool, child_offset: Vector2) -> &mut Self {
59 self.inner.clip.horizontal = horizontal;
60 self.inner.clip.vertical = vertical;
61 self.inner.clip.childOffset = child_offset.into();
62 self
63 }
64
65 #[inline]
66 pub fn id(&mut self, id: Id) -> &mut Self {
67 self.inner.id = id.id;
68 self
69 }
70
71 #[inline]
72 pub fn custom_element(&mut self, data: &'render CustomElementData) -> &mut Self {
73 self.inner.custom.customData = data as *const CustomElementData as _;
74 self
75 }
76
77 #[inline]
78 pub fn layout(
79 &mut self,
80 ) -> layout::LayoutBuilder<'_, 'render, ImageElementData, CustomElementData> {
81 layout::LayoutBuilder::new(self)
82 }
83
84 #[inline]
85 pub fn image(
86 &mut self,
87 ) -> elements::ImageBuilder<'_, 'render, ImageElementData, CustomElementData> {
88 elements::ImageBuilder::new(self)
89 }
90
91 #[inline]
92 pub fn floating(
93 &mut self,
94 ) -> elements::FloatingBuilder<'_, 'render, ImageElementData, CustomElementData> {
95 elements::FloatingBuilder::new(self)
96 }
97
98 #[inline]
99 pub fn border(
100 &mut self,
101 ) -> elements::BorderBuilder<'_, 'render, ImageElementData, CustomElementData> {
102 elements::BorderBuilder::new(self)
103 }
104
105 #[inline]
106 pub fn corner_radius(
107 &mut self,
108 ) -> elements::CornerRadiusBuilder<'_, 'render, ImageElementData, CustomElementData> {
109 elements::CornerRadiusBuilder::new(self)
110 }
111}
112
113impl<ImageElementData, CustomElementData> Default
114 for Declaration<'_, ImageElementData, CustomElementData>
115{
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121#[cfg(feature = "std")]
122unsafe extern "C" fn measure_text_trampoline_user_data<'a, F, T>(
123 text_slice: Clay_StringSlice,
124 config: *mut Clay_TextElementConfig,
125 user_data: *mut core::ffi::c_void,
126) -> Clay_Dimensions
127where
128 F: Fn(&str, &TextConfig, &'a mut T) -> Dimensions + 'a,
129 T: 'a,
130{
131 let text = core::str::from_utf8_unchecked(core::slice::from_raw_parts(
132 text_slice.chars as *const u8,
133 text_slice.length as _,
134 ));
135
136 let closure_and_data: &mut (F, T) = &mut *(user_data as *mut (F, T));
137 let text_config = TextConfig::from(*config);
138 let (callback, data) = closure_and_data;
139 callback(text, &text_config, data).into()
140}
141
142#[cfg(feature = "std")]
143unsafe extern "C" fn measure_text_trampoline<'a, F>(
144 text_slice: Clay_StringSlice,
145 config: *mut Clay_TextElementConfig,
146 user_data: *mut core::ffi::c_void,
147) -> Clay_Dimensions
148where
149 F: Fn(&str, &TextConfig) -> Dimensions + 'a,
150{
151 let text = core::str::from_utf8_unchecked(core::slice::from_raw_parts(
152 text_slice.chars as *const u8,
153 text_slice.length as _,
154 ));
155
156 let tuple = &*(user_data as *const (F, usize));
157 let text_config = TextConfig::from(*config);
158 (tuple.0)(text, &text_config).into()
159}
160
161unsafe extern "C" fn error_handler(error_data: Clay_ErrorData) {
162 let error: Error = error_data.into();
163 panic!("Clay Error: (type: {:?}) {}", error.type_, error.text);
164}
165
166#[allow(dead_code)]
167pub struct Clay {
168 #[cfg(feature = "std")]
170 _memory: Vec<u8>,
171 context: *mut Clay_Context,
172 #[cfg(not(feature = "std"))]
175 _memory: *const core::ffi::c_void,
176 text_measure_callback: Option<*const core::ffi::c_void>,
178}
179
180pub struct ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData> {
181 clay: &'clay mut Clay,
182 _phantom: core::marker::PhantomData<(&'render ImageElementData, &'render CustomElementData)>,
183 dropped: bool,
184}
185
186impl<'render, 'clay: 'render, ImageElementData: 'render, CustomElementData: 'render>
187 ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>
188{
189 pub fn with<
191 F: FnOnce(&mut ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>),
192 >(
193 &mut self,
194 declaration: &Declaration<'render, ImageElementData, CustomElementData>,
195 f: F,
196 ) {
197 unsafe {
198 Clay_SetCurrentContext(self.clay.context);
199 Clay__OpenElement();
200 Clay__ConfigureOpenElement(declaration.inner);
201 }
202
203 f(self);
204
205 unsafe {
206 Clay__CloseElement();
207 }
208 }
209
210 pub fn with_styling<
211 G: FnOnce(
212 &ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>,
213 ) -> Declaration<'render, ImageElementData, CustomElementData>,
214 F: FnOnce(&ClayLayoutScope<'clay, 'render, ImageElementData, CustomElementData>),
215 >(
216 &self,
217 g: G,
218 f: F,
219 ) {
220 unsafe {
221 Clay_SetCurrentContext(self.clay.context);
222 Clay__OpenElement();
223 }
224
225 let declaration = g(self);
226
227 unsafe {
228 Clay__ConfigureOpenElement(declaration.inner);
229 }
230
231 f(self);
232
233 unsafe {
234 Clay__CloseElement();
235 }
236 }
237
238 pub fn end(
239 &mut self,
240 ) -> impl Iterator<Item = RenderCommand<'render, ImageElementData, CustomElementData>> {
241 let array = unsafe { Clay_EndLayout() };
242 self.dropped = true;
243 let slice = unsafe { core::slice::from_raw_parts(array.internalArray, array.length as _) };
244 slice
245 .iter()
246 .map(|command| unsafe { RenderCommand::from_clay_render_command(*command) })
247 }
248
249 #[inline]
253 pub fn id(&self, label: &'render str) -> id::Id {
254 id::Id::new(label)
255 }
256
257 #[inline]
261 pub fn id_index(&self, label: &'render str, index: u32) -> id::Id {
262 id::Id::new_index(label, index)
263 }
264
265 #[inline]
269 pub fn id_local(&self, label: &'render str) -> id::Id {
270 id::Id::new_index_local(label, 0)
271 }
272
273 #[inline]
277 pub fn id_index_local(&self, label: &'render str, index: u32) -> id::Id {
278 id::Id::new_index_local(label, index)
279 }
280
281 pub fn text(&self, text: &'render str, config: TextElementConfig) {
283 unsafe { Clay__OpenTextElement(text.into(), config.into()) };
284 }
285
286 pub fn hovered(&self) -> bool {
287 unsafe { Clay_Hovered() }
288 }
289
290 pub fn pointer_over(&self, cfg: Id) -> bool {
291 unsafe { Clay_PointerOver(cfg.id) }
292 }
293
294 pub fn scroll_container_data(&self, id: Id) -> Option<Clay_ScrollContainerData> {
295 self.clay.scroll_container_data(id)
296 }
297 pub fn bounding_box(&self, id: Id) -> Option<BoundingBox> {
298 self.clay.bounding_box(id)
299 }
300
301 pub fn scroll_offset(&self) -> Vector2 {
302 unsafe { Clay_GetScrollOffset().into() }
303 }
304}
305
306impl<ImageElementData, CustomElementData> Drop
307 for ClayLayoutScope<'_, '_, ImageElementData, CustomElementData>
308{
309 fn drop(&mut self) {
310 if !self.dropped {
311 unsafe {
312 Clay_EndLayout();
313 }
314 }
315 }
316}
317
318impl Clay {
319 pub fn begin<'render, ImageElementData: 'render, CustomElementData: 'render>(
320 &mut self,
321 ) -> ClayLayoutScope<'_, 'render, ImageElementData, CustomElementData> {
322 unsafe { Clay_BeginLayout() };
323 ClayLayoutScope {
324 clay: self,
325 _phantom: core::marker::PhantomData,
326 dropped: false,
327 }
328 }
329
330 #[cfg(feature = "std")]
331 pub fn new(dimensions: Dimensions) -> Self {
332 let memory_size = Self::required_memory_size();
333 let memory = vec![0; memory_size];
334 let context;
335
336 unsafe {
337 let arena =
338 Clay_CreateArenaWithCapacityAndMemory(memory_size as _, memory.as_ptr() as _);
339
340 context = Clay_Initialize(
341 arena,
342 dimensions.into(),
343 Clay_ErrorHandler {
344 errorHandlerFunction: Some(error_handler),
345 userData: std::ptr::null_mut(),
346 },
347 );
348 }
349
350 Self {
351 _memory: memory,
352 context,
353 text_measure_callback: None,
354 }
355 }
356
357 #[cfg(not(feature = "std"))]
358 pub unsafe fn new_with_memory(dimensions: Dimensions, memory: *mut core::ffi::c_void) -> Self {
359 let memory_size = Self::required_memory_size();
360 let arena = Clay_CreateArenaWithCapacityAndMemory(memory_size as _, memory);
361
362 let context = Clay_Initialize(
363 arena,
364 dimensions.into(),
365 Clay_ErrorHandler {
366 errorHandlerFunction: Some(error_handler),
367 userData: core::ptr::null_mut(),
368 },
369 );
370
371 Self {
372 _memory: memory,
373 context,
374 text_measure_callback: None,
375 }
376 }
377
378 pub fn required_memory_size() -> usize {
380 unsafe { Clay_MinMemorySize() as usize }
381 }
382
383 #[cfg(feature = "std")]
385 pub fn set_measure_text_function_user_data<'clay, F, T>(
386 &'clay mut self,
387 userdata: T,
388 callback: F,
389 ) where
390 F: Fn(&str, &TextConfig, &'clay mut T) -> Dimensions + 'static,
391 T: 'clay,
392 {
393 let boxed = Box::new((callback, userdata));
395
396 let user_data_ptr = Box::into_raw(boxed) as _;
398
399 unsafe {
401 Self::set_measure_text_function_unsafe(
402 measure_text_trampoline_user_data::<F, T>,
403 user_data_ptr,
404 );
405 }
406
407 self.text_measure_callback = Some(user_data_ptr as *const core::ffi::c_void);
409 }
410
411 #[cfg(feature = "std")]
413 pub fn set_measure_text_function<F>(&mut self, callback: F)
414 where
415 F: Fn(&str, &TextConfig) -> Dimensions + 'static,
416 {
417 let boxed = Box::new((callback, 0usize));
420
421 let user_data_ptr = Box::into_raw(boxed) as *mut core::ffi::c_void;
423
424 unsafe {
426 Self::set_measure_text_function_unsafe(measure_text_trampoline::<F>, user_data_ptr);
427 }
428
429 self.text_measure_callback = Some(user_data_ptr as *const core::ffi::c_void);
431 }
432
433 pub unsafe fn set_measure_text_function_unsafe(
437 callback: unsafe extern "C" fn(
438 Clay_StringSlice,
439 *mut Clay_TextElementConfig,
440 *mut core::ffi::c_void,
441 ) -> Clay_Dimensions,
442 user_data: *mut core::ffi::c_void,
443 ) {
444 Clay_SetMeasureTextFunction(Some(callback), user_data);
445 }
446
447 pub fn max_element_count(&mut self, max_element_count: u32) {
450 unsafe {
451 Clay_SetMaxElementCount(max_element_count as _);
452 }
453 }
454 pub fn max_measure_text_cache_word_count(&self, count: u32) {
457 unsafe {
458 Clay_SetMaxElementCount(count as _);
459 }
460 }
461
462 pub fn set_debug_mode(&self, enable: bool) {
464 unsafe {
465 Clay_SetDebugModeEnabled(enable);
466 }
467 }
468
469 pub fn set_layout_dimensions(&self, dimensions: Dimensions) {
472 unsafe {
473 Clay_SetLayoutDimensions(dimensions.into());
474 }
475 }
476 pub fn pointer_state(&self, position: Vector2, is_down: bool) {
479 unsafe {
480 Clay_SetPointerState(position.into(), is_down);
481 }
482 }
483 pub fn update_scroll_containers(
484 &self,
485 drag_scrolling_enabled: bool,
486 scroll_delta: Vector2,
487 delta_time: f32,
488 ) {
489 unsafe {
490 Clay_UpdateScrollContainers(drag_scrolling_enabled, scroll_delta.into(), delta_time);
491 }
492 }
493
494 pub fn hovered(&self) -> bool {
496 unsafe { Clay_Hovered() }
497 }
498
499 pub fn pointer_over(&self, cfg: Id) -> bool {
500 unsafe { Clay_PointerOver(cfg.id) }
501 }
502
503 fn element_data(id: Id) -> Clay_ElementData {
504 unsafe { Clay_GetElementData(id.id) }
505 }
506
507 pub fn bounding_box(&self, id: Id) -> Option<BoundingBox> {
508 let element_data = Self::element_data(id);
509
510 if element_data.found {
511 Some(element_data.boundingBox.into())
512 } else {
513 None
514 }
515 }
516 pub fn scroll_container_data(&self, id: Id) -> Option<Clay_ScrollContainerData> {
517 unsafe {
518 Clay_SetCurrentContext(self.context);
519 let scroll_container_data = Clay_GetScrollContainerData(id.id);
520
521 if scroll_container_data.found {
522 Some(scroll_container_data)
523 } else {
524 None
525 }
526 }
527 }
528}
529
530#[cfg(feature = "std")]
531impl Drop for Clay {
532 fn drop(&mut self) {
533 unsafe {
534 if let Some(ptr) = self.text_measure_callback {
535 let _ = Box::from_raw(ptr as *mut (usize, usize));
536 }
537
538 Clay_SetCurrentContext(core::ptr::null_mut() as _);
539 }
540 }
541}
542
543impl From<&str> for Clay_String {
544 fn from(value: &str) -> Self {
545 Self {
546 isStaticallyAllocated: false,
548 length: value.len() as _,
549 chars: value.as_ptr() as _,
550 }
551 }
552}
553
554impl From<Clay_String> for &str {
555 fn from(value: Clay_String) -> Self {
556 unsafe {
557 core::str::from_utf8_unchecked(core::slice::from_raw_parts(
558 value.chars as *const u8,
559 value.length as _,
560 ))
561 }
562 }
563}
564
565impl From<Clay_StringSlice> for &str {
566 fn from(value: Clay_StringSlice) -> Self {
567 unsafe {
568 core::str::from_utf8_unchecked(core::slice::from_raw_parts(
569 value.chars as *const u8,
570 value.length as _,
571 ))
572 }
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use color::Color;
580 use layout::{Padding, Sizing};
581
582 #[rustfmt::skip]
583 #[test]
584 fn test_begin() {
585 let mut callback_data = 0u32;
586
587 let mut clay = Clay::new(Dimensions::new(800.0, 600.0));
588
589 clay.set_measure_text_function_user_data(&mut callback_data, |text, _config, data| {
590 println!(
591 "set_measure_text_function_user_data {:?} count {:?}",
592 text, data
593 );
594 **data += 1;
595 Dimensions::default()
596 });
597
598 let mut clay = clay.begin::<(), ()>();
599
600 clay.with(&Declaration::new()
601 .id(clay.id("parent_rect"))
602 .layout()
603 .width(Sizing::Fixed(100.0))
604 .height(Sizing::Fixed(100.0))
605 .padding(Padding::all(10))
606 .end()
607 .background_color(Color::rgb(255., 255., 255.)), |clay|
608 {
609 clay.with(&Declaration::new()
610 .layout()
611 .width(Sizing::Fixed(100.0))
612 .height(Sizing::Fixed(100.0))
613 .padding(Padding::all(10))
614 .end()
615 .background_color(Color::rgb(255., 255., 255.)), |clay|
616 {
617 clay.with(&Declaration::new()
618 .id(clay.id("rect_under_rect"))
619 .layout()
620 .width(Sizing::Fixed(100.0))
621 .height(Sizing::Fixed(100.0))
622 .padding(Padding::all(10))
623 .end()
624 .background_color(Color::rgb(255., 255., 255.)), |clay|
625 {
626 clay.text("test", TextConfig::new()
627 .color(Color::rgb(255., 255., 255.))
628 .font_size(24)
629 .end());
630 },
631 );
632 });
633 });
634
635 clay.with(&Declaration::new()
636 .id(clay.id_index("border_container", 1))
637 .layout()
638 .padding(Padding::all(16))
639 .end()
640 .border()
641 .color(Color::rgb(255., 255., 0.))
642 .all_directions(2)
643 .end()
644 .corner_radius().all(10.0).end(), |clay|
645 {
646 clay.with(&Declaration::new()
647 .id(clay.id("rect_under_border"))
648 .layout()
649 .width(Sizing::Fixed(50.0))
650 .height(Sizing::Fixed(50.0))
651 .end()
652 .background_color(Color::rgb(0., 255., 255.)), |_clay| {},
653 );
654 });
655
656 let items = clay.end();
657
658 for item in items {
659 println!(
660 "id: {}\nbbox: {:?}\nconfig: {:?}",
661 item.id, item.bounding_box, item.config,
662 );
663 }
664 }
665
666 #[rustfmt::skip]
667 #[test]
668 fn test_simple_text_measure() {
669 let mut clay = Clay::new(Dimensions::new(800.0, 600.0));
670
671 clay.set_measure_text_function(|_text, _config| {
672 Dimensions::default()
673 });
674
675 let mut clay = clay.begin::<(), ()>();
676
677 clay.with(&Declaration::new()
678 .id(clay.id("parent_rect"))
679 .layout()
680 .width(Sizing::Fixed(100.0))
681 .height(Sizing::Fixed(100.0))
682 .padding(Padding::all(10))
683 .end()
684 .background_color(Color::rgb(255., 255., 255.)), |clay|
685 {
686 clay.text("test", TextConfig::new()
687 .color(Color::rgb(255., 255., 255.))
688 .font_size(24)
689 .end());
690 });
691
692 let _items = clay.end();
693 }
694}