initial commit
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,6 @@ | |||
| cmake-build-debug/ | ||||
| cmake-build-release/ | ||||
| .DS_Store | ||||
| .idea/ | ||||
| build/ | ||||
| node_modules/ | ||||
							
								
								
									
										7
									
								
								CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,7 @@ | |||
| cmake_minimum_required(VERSION 3.28) | ||||
| project(clay C) | ||||
| 
 | ||||
| set(CMAKE_C_STANDARD 99) | ||||
| 
 | ||||
| add_subdirectory("examples/raylib-sidebar-scrolling-container") | ||||
| add_subdirectory("examples/clay-official-website") | ||||
							
								
								
									
										22
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,22 @@ | |||
| zlib/libpng license | ||||
| 
 | ||||
| Copyright (c) 2024 Nic Barker | ||||
| 
 | ||||
| This software is provided 'as-is', without any express or implied warranty. | ||||
| In no event will the authors be held liable for any damages arising from the | ||||
| use of this software. | ||||
| 
 | ||||
| Permission is granted to anyone to use this software for any purpose, | ||||
| including commercial applications, and to alter it and redistribute it | ||||
| freely, subject to the following restrictions: | ||||
| 
 | ||||
|     1. The origin of this software must not be misrepresented; you must not | ||||
|     claim that you wrote the original software. If you use this software in a | ||||
|     product, an acknowledgment in the product documentation would be | ||||
|     appreciated but is not required. | ||||
| 
 | ||||
|     2. Altered source versions must be plainly marked as such, and must not | ||||
|     be misrepresented as being the original software. | ||||
| 
 | ||||
|     3. This notice may not be removed or altered from any source | ||||
|     distribution. | ||||
							
								
								
									
										17
									
								
								examples/clay-official-website/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,17 @@ | |||
| cmake_minimum_required(VERSION 3.28) | ||||
| project(clay_official_website C) | ||||
| 
 | ||||
| set(CMAKE_C_STANDARD 99) | ||||
| 
 | ||||
| add_executable(clay_official_website main.c) | ||||
| 
 | ||||
| target_compile_options(clay_official_website PUBLIC -DCLAY_OVERFLOW_TRAP -Wno-initializer-overrides) | ||||
| target_include_directories(clay_official_website PUBLIC .) | ||||
| 
 | ||||
| set(CMAKE_CXX_FLAGS_RELEASE "-O3") | ||||
| 
 | ||||
| add_custom_command( | ||||
|         TARGET clay_official_website POST_BUILD | ||||
|         COMMAND ${CMAKE_COMMAND} -E copy_directory | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/resources | ||||
|         ${CMAKE_CURRENT_BINARY_DIR}/resources) | ||||
							
								
								
									
										17
									
								
								examples/clay-official-website/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						|  | @ -0,0 +1,17 @@ | |||
| mkdir -p build/clay                                                       \ | ||||
| && clang                                                                  \ | ||||
| -Os                                                                       \ | ||||
| -DCLAY_WASM                                                               \ | ||||
| -mbulk-memory                                                             \ | ||||
| --target=wasm32                                                           \ | ||||
| -nostdlib                                                                 \ | ||||
| -Wl,--strip-all                                                           \ | ||||
| -Wl,--export-dynamic                                                      \ | ||||
| -Wl,--no-entry                                                            \ | ||||
| -Wl,--export=__heap_base                                                  \ | ||||
| -Wl,--export=ACTIVE_RENDERER_INDEX                                        \ | ||||
| -Wl,--initial-memory=6553600                                              \ | ||||
| -o build/clay/index.wasm                                                  \ | ||||
| main.c                                                                    \ | ||||
| && cp index.html build/clay/index.html && cp -r fonts/ build/clay/fonts   \ | ||||
| && cp index.html build/clay/index.html && cp -r images/ build/clay/images | ||||
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/fonts/Calistoga-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/fonts/Quicksand-Semibold.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/images/check_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/images/check_2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/images/check_3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/images/check_4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/images/check_5.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/images/declarative.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 193 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/clay-official-website/images/renderer.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 310 KiB | 
							
								
								
									
										688
									
								
								examples/clay-official-website/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,688 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <title>Clay - UI Layout Library</title> | ||||
|     <style> | ||||
|         html, body { | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|             overflow: hidden; | ||||
|             padding: 0; | ||||
|             margin: 0; | ||||
|             pointer-events: none; | ||||
|         } | ||||
|         /* Import the font using @font-face */ | ||||
|         @font-face { | ||||
|           font-family: 'Calistoga'; | ||||
|           font-style: normal; | ||||
|           font-weight: 400; | ||||
|           src: url('/clay/fonts/Calistoga-Regular.ttf') format('truetype'); | ||||
|         } | ||||
| 
 | ||||
|         @font-face { | ||||
|           font-family: 'Quicksand'; | ||||
|           font-style: normal; | ||||
|           font-weight: 400; | ||||
|           src: url('/clay/fonts/Quicksand-Semibold.ttf') format('truetype'); | ||||
|         } | ||||
| 
 | ||||
|         body > canvas { | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|         } | ||||
| 
 | ||||
|         div, a, img { | ||||
|             position: absolute; | ||||
|             box-sizing: border-box; | ||||
|             -webkit-backface-visibility: hidden; | ||||
|         } | ||||
| 
 | ||||
|         a { | ||||
|             cursor: pointer; | ||||
|             pointer-events: all; | ||||
|         } | ||||
| 
 | ||||
|         .text { | ||||
|             pointer-events: all; | ||||
|             white-space: nowrap; | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| <script type="module"> | ||||
|     const CLAY_RENDER_COMMAND_TYPE_NONE = 0; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_RECTANGLE = 1; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_BORDER = 2; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_TEXT = 3; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_IMAGE = 4; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_SCISSOR_START = 5; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_SCISSOR_END = 6; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_CUSTOM = 7; | ||||
|     const GLOBAL_FONT_SCALING_FACTOR = 0.8; | ||||
|     let renderCommandSize = 0; | ||||
|     let scratchSpaceAddress = 0; | ||||
|     let heapSpaceAddress = 0; | ||||
|     let memoryDataView; | ||||
|     let textDecoder = new TextDecoder("utf-8"); | ||||
|     let previousFrameTime; | ||||
|     let fontsById = [ | ||||
|         'Calistoga', | ||||
|         'Quicksand', | ||||
|         'Quicksand', | ||||
|         'Quicksand', | ||||
|         'Quicksand', | ||||
|     ]; | ||||
|     let elementCache = {}; | ||||
|     let imageCache = {}; | ||||
|     let colorDefinition = { type: 'struct', members: [ | ||||
|         {name: 'r', type: 'float' }, | ||||
|         {name: 'g', type: 'float' }, | ||||
|         {name: 'b', type: 'float' }, | ||||
|         {name: 'a', type: 'float' }, | ||||
|     ]}; | ||||
|     let stringDefinition = { type: 'struct', members: [ | ||||
|         {name: 'length', type: 'uint32_t' }, | ||||
|         {name: 'chars', type: 'uint32_t' }, | ||||
|     ]}; | ||||
|     let borderDefinition = { type: 'struct', members: [ | ||||
|         {name: 'width', type: 'uint32_t'}, | ||||
|         {name: 'color', ...colorDefinition}, | ||||
|     ]}; | ||||
|     let cornerRadiusDefinition = { type: 'struct', members: [ | ||||
|         {name: 'topLeft', type: 'float'}, | ||||
|         {name: 'topRight', type: 'float'}, | ||||
|         {name: 'bottomLeft', type: 'float'}, | ||||
|         {name: 'bottomRight', type: 'float'}, | ||||
|     ]}; | ||||
|     let rectangleConfigDefinition = { name: 'rectangle', type: 'struct', members: [ | ||||
|         { name: 'color', ...colorDefinition }, | ||||
|         { name: 'cornerRadius', ...cornerRadiusDefinition }, | ||||
|         { name: 'link', ...stringDefinition }, | ||||
|         { name: 'cursorPointer', type: 'uint8_t' }, | ||||
|     ]}; | ||||
|     let borderConfigDefinition = { name: 'text', type: 'struct', members: [ | ||||
|         { name: 'left', ...borderDefinition }, | ||||
|         { name: 'right', ...borderDefinition }, | ||||
|         { name: 'top', ...borderDefinition }, | ||||
|         { name: 'bottom', ...borderDefinition }, | ||||
|         { name: 'betweenChildren', ...borderDefinition }, | ||||
|         { name: 'cornerRadius', ...cornerRadiusDefinition } | ||||
|     ]}; | ||||
|     let textConfigDefinition = { name: 'text', type: 'struct', members: [ | ||||
|        { name: 'textColor', ...colorDefinition }, | ||||
|        { name: 'fontId', type: 'uint16_t' }, | ||||
|        { name: 'fontSize', type: 'uint16_t' }, | ||||
|        { name: 'letterSpacing', type: 'uint16_t' }, | ||||
|        { name: 'lineSpacing', type: 'uint16_t' }, | ||||
|        { name: 'disablePointerEvents', type: 'uint8_t' } | ||||
|     ]}; | ||||
|     let imageConfigDefinition = { name: 'image', type: 'struct', members: [ | ||||
|         { name: 'imageData', type: 'uint32_t' }, | ||||
|         { name: 'sourceDimensions', type: 'struct', members: [ | ||||
|             { name: 'width', type: 'float' }, | ||||
|             { name: 'height', type: 'float' }, | ||||
|         ]}, | ||||
|         { name: 'sourceURL', ...stringDefinition } | ||||
|     ]}; | ||||
|     let customConfigDefinition = { name: 'custom', type: 'struct', members: [ | ||||
|         { name: 'customData', type: 'uint32_t' }, | ||||
|     ]} | ||||
|     let renderCommandDefinition = { | ||||
|         name: 'CLay_RenderCommand', | ||||
|         type: 'struct', | ||||
|         members: [ | ||||
|             { name: 'boundingBox', type: 'struct', members: [ | ||||
|                 { name: 'x', type: 'float' }, | ||||
|                 { name: 'y', type: 'float' }, | ||||
|                 { name: 'width', type: 'float' }, | ||||
|                 { name: 'height', type: 'float' }, | ||||
|             ]}, | ||||
|             { name: 'config', type: 'uint32_t'}, | ||||
|             { name: 'text', ...stringDefinition }, | ||||
|             { name: 'id', type: 'uint32_t' }, | ||||
|             { name: 'commandType', type: 'uint32_t', }, | ||||
|         ] | ||||
|     }; | ||||
| 
 | ||||
|     function getStructTotalSize(definition) { | ||||
|         switch(definition.type) { | ||||
|             case 'union': | ||||
|             case 'struct': { | ||||
|                 let totalSize = 0; | ||||
|                 for (const member of definition.members) { | ||||
|                     let result = getStructTotalSize(member); | ||||
|                     if (definition.type === 'struct') { | ||||
|                         totalSize += result; | ||||
|                     } else { | ||||
|                         totalSize = Math.max(totalSize, result); | ||||
|                     } | ||||
|                 } | ||||
|                 return totalSize; | ||||
|             } | ||||
|             case 'float': return 4; | ||||
|             case 'uint32_t': return 4; | ||||
|             case 'uint16_t': return 2; | ||||
|             case 'uint8_t': return 1; | ||||
|             default: { | ||||
|                 throw "Unimplemented C data type " + definition.type | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function readStructAtAddress(address, definition) { | ||||
|         switch(definition.type) { | ||||
|             case 'union': | ||||
|             case 'struct': { | ||||
|                 let struct = { __size: 0 }; | ||||
|                 for (const member of definition.members) { | ||||
|                     let result = readStructAtAddress(address, member); | ||||
|                     struct[member.name] = result; | ||||
|                     if (definition.type === 'struct') { | ||||
|                         struct.__size += result.__size; | ||||
|                         address += result.__size; | ||||
|                     } else { | ||||
|                         struct.__size = Math.max(struct.__size, result.__size); | ||||
|                     } | ||||
|                 } | ||||
|                 return struct; | ||||
|             } | ||||
|             case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 }; | ||||
|             case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 }; | ||||
|             case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 }; | ||||
|             case 'uint8_t': return { value: memoryDataView.getUint16(address, true), __size: 1 }; | ||||
|             default: { | ||||
|                 throw "Unimplemented C data type " + definition.type | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function getTextDimensions(text, font) { | ||||
|         // re-use canvas object for better performance | ||||
|         window.canvasContext.font = font; | ||||
|         let metrics = window.canvasContext.measureText(text); | ||||
|         return { width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent }; | ||||
|     } | ||||
| 
 | ||||
|     function createMainArena(arenaStructAddress, arenaMemoryAddress) { | ||||
|         let memorySize = instance.exports.Clay_MinMemorySize(); | ||||
|         // Last arg is address to store return value | ||||
|         instance.exports.Clay_CreateArenaWithCapacityAndMemory(arenaStructAddress, memorySize, arenaMemoryAddress); | ||||
|     } | ||||
|     async function init() { | ||||
|         window.htmlRoot = document.body.appendChild(document.createElement('div')); | ||||
|         window.canvasRoot = document.body.appendChild(document.createElement('canvas')); | ||||
|         window.canvasContext = window.canvasRoot.getContext("2d"); | ||||
|         window.mousePositionXThisFrame = 0; | ||||
|         window.mousePositionYThisFrame = 0; | ||||
|         window.mouseWheelXThisFrame = 0; | ||||
|         window.mouseWheelYThisFrame = 0; | ||||
|         window.touchDown = false; | ||||
|         let zeroTimeout = null; | ||||
|         addEventListener("wheel", (event) => { | ||||
|             window.mouseWheelXThisFrame = event.deltaX * -0.1; | ||||
|             window.mouseWheelYThisFrame = event.deltaY * -0.1; | ||||
|             clearTimeout(zeroTimeout); | ||||
|             zeroTimeout = setTimeout(() => { | ||||
|                 window.mouseWheelXThisFrame = 0; | ||||
|                 window.mouseWheelYThisFrame = 0; | ||||
|             }, 10); | ||||
|         }); | ||||
| 
 | ||||
|         function handleTouch (event) { | ||||
|             if (event.touches.length === 1) { | ||||
|                 window.touchDown = true; | ||||
|                 window.mousePositionXThisFrame = event.changedTouches[0].pageX; | ||||
|                 window.mousePositionYThisFrame = event.changedTouches[0].pageY; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         document.addEventListener("touchstart", handleTouch); | ||||
|         document.addEventListener("touchmove", handleTouch); | ||||
|         document.addEventListener("touchend", () => { | ||||
|             window.touchDown = false; | ||||
|             window.mousePositionXThisFrame = 0; | ||||
|             window.mousePositionYThisFrame = 0; | ||||
|         }) | ||||
| 
 | ||||
|         document.addEventListener("mousemove", (event) => { | ||||
|             window.mousePositionXThisFrame = event.x; | ||||
|             window.mousePositionYThisFrame = event.y; | ||||
|         }); | ||||
| 
 | ||||
|         document.addEventListener("mousedown", (event) => { | ||||
|             window.mouseDown = true; | ||||
|         }); | ||||
| 
 | ||||
|         const importObject = { | ||||
|             clay: { measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => { | ||||
|                 let stringLength = memoryDataView.getUint32(textToMeasure, true); | ||||
|                 let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true); | ||||
|                 let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition); | ||||
|                 let textDecoder = new TextDecoder("utf-8"); | ||||
|                 let text = textDecoder.decode(memoryDataView.buffer.slice(pointerToString, pointerToString + stringLength)); | ||||
|                 let sourceDimensions = getTextDimensions(text, `${Math.round(textConfig.fontSize.value * GLOBAL_FONT_SCALING_FACTOR)}px ${fontsById[textConfig.fontId.value]}`); | ||||
|                 memoryDataView.setFloat32(addressOfDimensions, sourceDimensions.width, true); | ||||
|                 memoryDataView.setFloat32(addressOfDimensions + 4, sourceDimensions.height, true); | ||||
|             }}, | ||||
|         }; | ||||
|         const { instance } = await WebAssembly.instantiateStreaming( | ||||
|             fetch("/clay/index.wasm"), importObject | ||||
|         ); | ||||
|         memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer); | ||||
|         scratchSpaceAddress = instance.exports.__heap_base.value; | ||||
|         heapSpaceAddress = instance.exports.__heap_base.value + 1024; | ||||
|         let arenaAddress = scratchSpaceAddress; | ||||
|         window.instance = instance; | ||||
|         createMainArena(arenaAddress, heapSpaceAddress); | ||||
|         instance.exports.Clay_Initialize(arenaAddress); | ||||
|         renderCommandSize = getStructTotalSize(renderCommandDefinition); | ||||
|         renderLoop(); | ||||
|     } | ||||
| 
 | ||||
|     function MemoryIsDifferent(one, two, length) { | ||||
|         for (let i = 0; i < length; i++) { | ||||
|             if (one[i] !== two[i]) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     function renderLoopHTML() { | ||||
|         let capacity = memoryDataView.getUint32(scratchSpaceAddress, true); | ||||
|         let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true); | ||||
|         let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true); | ||||
|         let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }]; | ||||
|         for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) { | ||||
|             let entireRenderCommandMemory = new Uint32Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize)); | ||||
|             let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition); | ||||
|             let parentElement = scissorStack[scissorStack.length - 1]; | ||||
|             let element = null; | ||||
|             if (!elementCache[renderCommand.id.value]) { | ||||
|                 let elementType = 'div'; | ||||
|                 switch (renderCommand.commandType.value) { | ||||
|                     case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { | ||||
|                         if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) { | ||||
|                             elementType = 'a'; | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                     case CLAY_RENDER_COMMAND_TYPE_IMAGE: { elementType = 'img'; break; } | ||||
|                     default: break; | ||||
|                 } | ||||
|                 element = document.createElement(elementType); | ||||
|                 element.id = renderCommand.id.value; | ||||
|                 elementCache[renderCommand.id.value] = { | ||||
|                     exists: true, | ||||
|                     element: element, | ||||
|                     previousMemoryCommand: new Uint8Array(0), | ||||
|                     previousMemoryConfig: new Uint8Array(0), | ||||
|                     previousMemoryText: new Uint8Array(0) | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             let dirty = false; | ||||
|             let elementData = elementCache[renderCommand.id.value]; | ||||
|             element = elementData.element; | ||||
|             if (Array.prototype.indexOf.call(parentElement.element.children, element) !== parentElement.nextElementIndex) { | ||||
|                 if (parentElement.nextElementIndex === 0) { | ||||
|                     parentElement.element.insertAdjacentElement('afterbegin', element); | ||||
|                 } else { | ||||
|                     parentElement.element.childNodes[parentElement.nextElementIndex - 1].insertAdjacentElement('afterend', element); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             elementData.exists = true; | ||||
|             // Don't get me started. Cheaper to compare the render command memory than to update HTML elements | ||||
|             if (renderCommand.commandType.value !== CLAY_RENDER_COMMAND_TYPE_SCISSOR_START && renderCommand.commandType.value !== CLAY_RENDER_COMMAND_TYPE_SCISSOR_END) { | ||||
|                 dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize); | ||||
|                 parentElement.nextElementIndex++; | ||||
|             } else { | ||||
|                 dirty = true; | ||||
|             } | ||||
| 
 | ||||
|             elementData.previousMemoryCommand = entireRenderCommandMemory; | ||||
|             let offsetX = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.x : 0; | ||||
|             let offsetY = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.y : 0; | ||||
|             if (dirty) { | ||||
|                 element.style.transform = `translate(${Math.round(renderCommand.boundingBox.x.value - offsetX)}px, ${Math.round(renderCommand.boundingBox.y.value - offsetY)}px)` | ||||
|                 element.style.width = Math.round(renderCommand.boundingBox.width.value) + 'px'; | ||||
|                 element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px'; | ||||
|             } | ||||
| 
 | ||||
|             switch(renderCommand.commandType.value) { | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_NONE): { | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition); | ||||
|                     let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); | ||||
|                     let linkContents = config.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.link.chars.value, config.link.chars.value + config.link.length.value))) : 0; | ||||
|                     if (linkContents.length > 0 && (window.mouseDown || window.touchDown) && instance.exports.Clay_PointerOver(renderCommand.id.value)) { | ||||
|                         window.location.href = linkContents; | ||||
|                     } | ||||
|                     if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) { | ||||
|                         break; | ||||
|                     } | ||||
|                     if (linkContents.length > 0) { | ||||
|                         element.href = linkContents; | ||||
|                     } | ||||
| 
 | ||||
|                     if (linkContents.length > 0 || config.cursorPointer.value) { | ||||
|                         element.style.pointerEvents = 'all'; | ||||
|                         element.style.cursor = 'pointer'; | ||||
|                     } | ||||
|                     elementData.previousMemoryConfig = configMemory; | ||||
|                     let color = config.color; | ||||
|                     element.style.backgroundColor = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                     if (config.cornerRadius.topLeft.value > 0) { | ||||
|                         element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.topRight.value > 0) { | ||||
|                         element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.bottomLeft.value > 0) { | ||||
|                         element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.bottomRight.value > 0) { | ||||
|                         element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px'; | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_BORDER): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition); | ||||
|                     let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); | ||||
|                     if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) { | ||||
|                         break; | ||||
|                     } | ||||
|                     elementData.previousMemoryConfig = configMemory; | ||||
|                     if (config.left.width.value > 0) { | ||||
|                         let color = config.left.color; | ||||
|                         element.style.borderLeft = `${config.left.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})` | ||||
|                     } | ||||
|                     if (config.right.width.value > 0) { | ||||
|                         let color = config.right.color; | ||||
|                         element.style.borderRight = `${config.right.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})` | ||||
|                     } | ||||
|                     if (config.top.width.value > 0) { | ||||
|                         let color = config.top.color; | ||||
|                         element.style.borderTop = `${config.top.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})` | ||||
|                     } | ||||
|                     if (config.bottom.width.value > 0) { | ||||
|                         let color = config.bottom.color; | ||||
|                         element.style.borderBottom = `${config.bottom.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})` | ||||
|                     } | ||||
|                     if (config.cornerRadius.topLeft.value > 0) { | ||||
|                         element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.topRight.value > 0) { | ||||
|                         element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.bottomLeft.value > 0) { | ||||
|                         element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.bottomRight.value > 0) { | ||||
|                         element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px'; | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_TEXT): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition); | ||||
|                     let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); | ||||
|                     let textContents = renderCommand.text; | ||||
|                     let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value)); | ||||
|                     if (MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) { | ||||
|                         element.className = 'text'; | ||||
|                         let textColor = config.textColor; | ||||
|                         let fontSize = Math.round(config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR); | ||||
|                         element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`; | ||||
|                         element.style.fontFamily = fontsById[config.fontId.value]; | ||||
|                         element.style.fontSize = fontSize + 'px'; | ||||
|                         element.style.pointerEvents = config.disablePointerEvents.value ? 'none' : 'all'; | ||||
|                         elementData.previousMemoryConfig = configMemory; | ||||
|                     } | ||||
|                     if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) { | ||||
|                         element.innerHTML = textDecoder.decode(stringContents); | ||||
|                     } | ||||
|                     elementData.previousMemoryText = stringContents; | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): { | ||||
|                     scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 }); | ||||
|                     element.style.overflow = 'hidden'; | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): { | ||||
|                     scissorStack.splice(scissorStack.length - 1, 1); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_IMAGE): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition); | ||||
|                     let srcContents = new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value)); | ||||
|                     if (srcContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(srcContents, elementData.previousMemoryText, srcContents.length)) { | ||||
|                         element.src = textDecoder.decode(srcContents); | ||||
|                     } | ||||
|                     elementData.previousMemoryText = srcContents; | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (const key of Object.keys(elementCache)) { | ||||
|             if (elementCache[key].exists) { | ||||
|                 elementCache[key].exists = false; | ||||
|             } else { | ||||
|                 elementCache[key].element.remove(); | ||||
|                 delete elementCache[key]; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function renderLoopCanvas() { | ||||
|     // Note: Rendering to canvas needs to be scaled up by window.devicePixelRatio in both width and height. | ||||
|     // e.g. if we're working on a device where devicePixelRatio is 2, we need to render | ||||
|     // everything at width^2 x height^2 resolution, then scale back down with css to get the correct pixel density. | ||||
|         let capacity = memoryDataView.getUint32(scratchSpaceAddress, true); | ||||
|         let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true); | ||||
|         let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true); | ||||
|         window.canvasRoot.width = window.innerWidth * window.devicePixelRatio; | ||||
|         window.canvasRoot.height = window.innerHeight * window.devicePixelRatio; | ||||
|         window.canvasRoot.style.width = window.innerWidth; | ||||
|         window.canvasRoot.style.height = window.innerHeight; | ||||
|         let ctx = window.canvasContext; | ||||
|         let scale = window.devicePixelRatio; | ||||
|         for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) { | ||||
|             let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition); | ||||
|             let boundingBox = renderCommand.boundingBox; | ||||
|             switch(renderCommand.commandType.value) { | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_NONE): { | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition); | ||||
|                     let color = config.color; | ||||
|                     ctx.beginPath(); | ||||
|                     window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                     window.canvasContext.roundRect( | ||||
|                         boundingBox.x.value * scale, // x | ||||
|                         boundingBox.y.value * scale, // y | ||||
|                         boundingBox.width.value * scale, // width | ||||
|                         boundingBox.height.value * scale, | ||||
|                         [config.cornerRadius.topLeft.value * scale, config.cornerRadius.topRight.value * scale, config.cornerRadius.bottomRight.value * scale, config.cornerRadius.bottomLeft.value * scale]) // height; | ||||
|                     ctx.fill(); | ||||
|                     ctx.closePath(); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_BORDER): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition); | ||||
|                     ctx.beginPath(); | ||||
|                     ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale); | ||||
|                     // Top Left Corner | ||||
|                     if (config.cornerRadius.topLeft.value > 0) { | ||||
|                         let lineWidth = config.top.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale); | ||||
|                         let color = config.top.color; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, config.cornerRadius.topLeft.value * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Top border | ||||
|                     if (config.top.width.value > 0) { | ||||
|                         let lineWidth = config.top.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         let color = config.top.color; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.moveTo((boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale); | ||||
|                         ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Top Right Corner | ||||
|                     if (config.cornerRadius.topRight.value > 0) { | ||||
|                         let lineWidth = config.top.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale); | ||||
|                         let color = config.top.color; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale, config.cornerRadius.topRight.value * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Right border | ||||
|                     if (config.right.width.value > 0) { | ||||
|                         let color = config.right.color; | ||||
|                         let lineWidth = config.right.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale); | ||||
|                         ctx.lineTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.topRight.value - halfLineWidth) * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Bottom Right Corner | ||||
|                     if (config.cornerRadius.bottomRight.value > 0) { | ||||
|                         let color = config.top.color; | ||||
|                         let lineWidth = config.top.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale); | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, config.cornerRadius.bottomRight.value * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Bottom Border | ||||
|                     if (config.bottom.width.value > 0) { | ||||
|                         let color = config.bottom.color; | ||||
|                         let lineWidth = config.bottom.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale); | ||||
|                         ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Bottom Left Corner | ||||
|                     if (config.cornerRadius.bottomLeft.value > 0) { | ||||
|                         let color = config.bottom.color; | ||||
|                         let lineWidth = config.bottom.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale); | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale, config.cornerRadius.bottomLeft.value * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Left Border | ||||
|                     if (config.left.width.value > 0) { | ||||
|                         let color = config.left.color; | ||||
|                         let lineWidth = config.left.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale); | ||||
|                         ctx.lineTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.bottomRight.value + halfLineWidth) * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     ctx.closePath(); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_TEXT): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition); | ||||
|                     let textContents = renderCommand.text; | ||||
|                     let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value)); | ||||
|                     let fontSize = config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR * scale; | ||||
|                     ctx.font = `${fontSize}px ${fontsById[config.fontId.value]}`; | ||||
|                     let color = config.textColor; | ||||
|                     ctx.textBaseline = 'middle'; | ||||
|                     ctx.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                     ctx.fillText(textDecoder.decode(stringContents), boundingBox.x.value * scale, (boundingBox.y.value + boundingBox.height.value / 2 + 1) * scale); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): { | ||||
|                     window.canvasContext.beginPath(); | ||||
|                     window.canvasContext.rect(boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale); | ||||
|                     window.canvasContext.clip(); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): { | ||||
|                     window.canvasContext.restore(); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_IMAGE): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition); | ||||
|                     let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value))); | ||||
|                     if (!imageCache[src]) { | ||||
|                         imageCache[src] = { | ||||
|                             image: new Image(), | ||||
|                             loaded: false, | ||||
|                         } | ||||
|                         imageCache[src].image.onload = () => imageCache[src].loaded = true; | ||||
|                         imageCache[src].image.src = src; | ||||
|                     } else if (imageCache[src].loaded) { | ||||
|                         ctx.drawImage(imageCache[src].image, boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale); | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function renderLoop(currentTime) { | ||||
|         const elapsed = currentTime - previousFrameTime; | ||||
|         previousFrameTime = currentTime; | ||||
|         let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true); | ||||
|         instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, elapsed / 1000); | ||||
|         let rendererChanged = activeRendererIndex !== window.previousActiveRendererIndex; | ||||
|         switch (activeRendererIndex) { | ||||
|             case 0: { | ||||
|                 renderLoopHTML(); | ||||
|                 if (rendererChanged) { | ||||
|                     window.htmlRoot.style.display = 'block'; | ||||
|                     window.canvasRoot.style.display = 'none'; | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|             case 1: { | ||||
|                 renderLoopCanvas(); | ||||
|                 if (rendererChanged) { | ||||
|                     window.htmlRoot.style.display = 'none'; | ||||
|                     window.canvasRoot.style.display = 'block'; | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         window.previousActiveRendererIndex = activeRendererIndex; | ||||
|         requestAnimationFrame(renderLoop); | ||||
|         window.mouseDown = false; | ||||
|     } | ||||
|     init(); | ||||
| </script> | ||||
| <body> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										349
									
								
								examples/clay-official-website/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,349 @@ | |||
| #define CLAY_EXTEND_CONFIG_RECTANGLE Clay_String link; bool cursorPointer; | ||||
| #define CLAY_EXTEND_CONFIG_IMAGE Clay_String sourceURL; | ||||
| #define CLAY_EXTEND_CONFIG_TEXT bool disablePointerEvents; | ||||
| #include "../../clay.h" | ||||
| 
 | ||||
| double windowWidth = 1024, windowHeight = 768; | ||||
| float modelPageOneZRotation = 0; | ||||
| int ACTIVE_RENDERER_INDEX = 0; | ||||
| 
 | ||||
| const uint32_t FONT_ID_TITLE_56 = 0; | ||||
| const uint32_t FONT_ID_BODY_24 = 1; | ||||
| const uint32_t FONT_ID_BODY_16 = 2; | ||||
| const uint32_t FONT_ID_BODY_36 = 3; | ||||
| const uint32_t FONT_ID_TITLE_36 = 4; | ||||
| const uint32_t FONT_ID_MONOSPACE_24 = 5; | ||||
| 
 | ||||
| const Clay_Color COLOR_LIGHT = (Clay_Color) {244, 235, 230, 255}; | ||||
| Clay_Color COLOR_LIGHT_HOVER = (Clay_Color) {224, 215, 210, 255}; | ||||
| Clay_Color COLOR_BUTTON_HOVER = (Clay_Color) {238, 227, 225, 255}; | ||||
| Clay_Color COLOR_BROWN = (Clay_Color) {61, 26, 5, 255}; | ||||
| //Clay_Color COLOR_RED = (Clay_Color) {252, 67, 27, 255};
 | ||||
| Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; | ||||
| Clay_Color COLOR_RED_HOVER = (Clay_Color) {148, 46, 8, 255}; | ||||
| Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; | ||||
| Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255}; | ||||
| Clay_Color COLOR_TEAL = (Clay_Color) {111, 173, 162, 255}; | ||||
| Clay_Color COLOR_BLUE_DARK = (Clay_Color) {2, 32, 82, 255}; | ||||
| 
 | ||||
| // Colors for top stripe
 | ||||
| Clay_Color COLOR_TOP_BORDER_1 = (Clay_Color) {168, 66, 28, 255}; | ||||
| Clay_Color COLOR_TOP_BORDER_2 = (Clay_Color) {223, 110, 44, 255}; | ||||
| Clay_Color COLOR_TOP_BORDER_3 = (Clay_Color) {225, 138, 50, 255}; | ||||
| Clay_Color COLOR_TOP_BORDER_4 = (Clay_Color) {236, 189, 80, 255}; | ||||
| Clay_Color COLOR_TOP_BORDER_5 = (Clay_Color) {240, 213, 137, 255}; | ||||
| 
 | ||||
| Clay_Color COLOR_BLOB_BORDER_1 = (Clay_Color) {168, 66, 28, 255}; | ||||
| Clay_Color COLOR_BLOB_BORDER_2 = (Clay_Color) {203, 100, 44, 255}; | ||||
| Clay_Color COLOR_BLOB_BORDER_3 = (Clay_Color) {225, 138, 50, 255}; | ||||
| Clay_Color COLOR_BLOB_BORDER_4 = (Clay_Color) {236, 159, 70, 255}; | ||||
| Clay_Color COLOR_BLOB_BORDER_5 = (Clay_Color) {240, 189, 100, 255}; | ||||
| 
 | ||||
| #define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = vector.x, .y = vector.y } | ||||
| 
 | ||||
| Clay_TextElementConfig headerTextConfig = (Clay_TextElementConfig) { .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }; | ||||
| Clay_TextElementConfig blobTextConfig = (Clay_TextElementConfig) { .fontId = FONT_ID_BODY_24, .fontSize = 30, .textColor = COLOR_LIGHT }; | ||||
| 
 | ||||
| void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, Clay_String imageURL) { | ||||
|     CLAY_BORDER_CONTAINER(CLAY_IDI("HeroBlob", index), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = {16, 16}, .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, color, 10), { | ||||
|         CLAY_IMAGE(CLAY_IDI("CheckImage", index), CLAY_LAYOUT(.sizing = { CLAY_SIZING_FIXED(32) }), CLAY_IMAGE_CONFIG(.sourceDimensions = { 128, 128 }, .sourceURL = imageURL), {}); | ||||
|         CLAY_TEXT(CLAY_IDI("HeroBlobText", index), text, CLAY_TEXT_CONFIG(.fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color)); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void LandingPageDesktop() { | ||||
|     CLAY_CONTAINER(CLAY_ID("LandingPage1Desktop"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { .x = 50 }), { | ||||
|         CLAY_BORDER_CONTAINER(CLAY_ID("LandingPage1"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { | ||||
|             CLAY_CONTAINER(CLAY_ID("LeftText"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_PERCENT(0.55f) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { | ||||
|                 CLAY_TEXT(CLAY_ID("LeftTextTitle"), CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG(.fontSize = 56, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); | ||||
|                 CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {}); | ||||
|                 CLAY_TEXT(CLAY_ID("LeftTextTagline"), CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE)); | ||||
|             }); | ||||
|             CLAY_CONTAINER(CLAY_ID("HeroImageOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_PERCENT(0.45f) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16), { | ||||
|                 LandingPageBlob(1, 32, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png")); | ||||
|                 LandingPageBlob(2, 32, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png")); | ||||
|                 LandingPageBlob(3, 32, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png")); | ||||
|                 LandingPageBlob(4, 32, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png")); | ||||
|                 LandingPageBlob(5, 32, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png")); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void LandingPageMobile() { | ||||
|     CLAY_CONTAINER(CLAY_ID("LandingPage1Mobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 32 }, .childGap = 32), { | ||||
|         CLAY_CONTAINER(CLAY_ID("LeftText"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { | ||||
|             CLAY_TEXT(CLAY_ID("LeftTextTitle"), CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); | ||||
|             CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {}); | ||||
|             CLAY_TEXT(CLAY_ID("LeftTextTagline"), CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG(.fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE)); | ||||
|         }); | ||||
|         CLAY_CONTAINER(CLAY_ID("HeroImageOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW() }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16), { | ||||
|             LandingPageBlob(1, 28, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png")); | ||||
|             LandingPageBlob(2, 28, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png")); | ||||
|             LandingPageBlob(3, 28, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png")); | ||||
|             LandingPageBlob(4, 28, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png")); | ||||
|             LandingPageBlob(5, 28, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png")); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void FeatureBlocksDesktop() { | ||||
|     CLAY_CONTAINER(CLAY_ID("FeatureBlocksOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }), { | ||||
|         CLAY_BORDER_CONTAINER(CLAY_ID("FeatureBlocksInner"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } ), CLAY_BORDER_CONFIG(.betweenChildren = { .width = 2, .color = COLOR_RED }), { | ||||
|             Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED ); | ||||
|             CLAY_CONTAINER(CLAY_ID("HFileBoxOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 32}, .childGap = 8), { | ||||
|                 CLAY_RECTANGLE(CLAY_ID("HFileIncludeOuter"), CLAY_LAYOUT(.padding = {8, 4}), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8)), { | ||||
|                     CLAY_TEXT(CLAY_IDI("HFileBoxText", 2), CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT)); | ||||
|                 }); | ||||
|                 CLAY_TEXT(CLAY_ID("HFileSecondLine"), CLAY_STRING("~2000 lines of C99."), textConfig); | ||||
|                 CLAY_TEXT(CLAY_IDI("HFileBoxText", 5), CLAY_STRING("Zero dependencies, including no C standard library."), textConfig); | ||||
|             }); | ||||
|             CLAY_CONTAINER(CLAY_ID("BringYourOwnRendererOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50, .y = 32}, .childGap = 8), { | ||||
|                 CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 1), CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG(.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE)); | ||||
|                 CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 2), CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig); | ||||
|                 CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 3), CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void FeatureBlocksMobile() { | ||||
|     CLAY_BORDER_CONTAINER(CLAY_ID("FeatureBlocksInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, ), CLAY_BORDER_CONFIG(.betweenChildren = { .width = 2, .color = COLOR_RED }), { | ||||
|         Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED ); | ||||
|         CLAY_CONTAINER(CLAY_ID("HFileBoxOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 32}, .childGap = 8), { | ||||
|             CLAY_RECTANGLE(CLAY_ID("HFileIncludeOuter"), CLAY_LAYOUT(.padding = {8, 4}), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8)), { | ||||
|                 CLAY_TEXT(CLAY_IDI("HFileBoxText", 2), CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT)); | ||||
|             }); | ||||
|             CLAY_TEXT(CLAY_ID("HFileSecondLine"), CLAY_STRING("~2000 lines of C99."), textConfig); | ||||
|             CLAY_TEXT(CLAY_IDI("HFileBoxText", 5), CLAY_STRING("Zero dependencies, including no C standard library."), textConfig); | ||||
|         }); | ||||
|         CLAY_CONTAINER(CLAY_ID("BringYourOwnRendererOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, .y = 32}, .childGap = 8), { | ||||
|             CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 1), CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG(.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE)); | ||||
|             CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 2), CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig); | ||||
|             CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 3), CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void DeclarativeSyntaxPageDesktop() { | ||||
|     CLAY_CONTAINER(CLAY_ID("SyntaxPageDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50}), { | ||||
|         CLAY_BORDER_CONTAINER(CLAY_ID("SyntaxPage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { | ||||
|             CLAY_CONTAINER(CLAY_ID("SyntaxPageLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { | ||||
|                 CLAY_TEXT(CLAY_ID("SyntaxPageTextTitle"), CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); | ||||
|                 CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); | ||||
|                 CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle1"), CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|                 CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle2"), CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|                 CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle3"), CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|             }); | ||||
|             CLAY_CONTAINER(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER}), { | ||||
|                 CLAY_IMAGE(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 568) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1136, 1194}, .sourceURL = CLAY_STRING("/clay/images/declarative.png")), {}); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void DeclarativeSyntaxPageMobile() { | ||||
|     CLAY_CONTAINER(CLAY_ID("SyntaxPageDesktop"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 32}, .childGap = 16), { | ||||
|         CLAY_CONTAINER(CLAY_ID("SyntaxPageLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { | ||||
|             CLAY_TEXT(CLAY_ID("SyntaxPageTextTitle"), CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); | ||||
|             CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); | ||||
|             CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle1"), CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|             CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle2"), CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|             CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle3"), CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|         }); | ||||
|         CLAY_CONTAINER(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER}), { | ||||
|             CLAY_IMAGE(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 568) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1136, 1194}, .sourceURL = CLAY_STRING("/clay/images/declarative.png")), {}); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) { | ||||
|     return (Clay_Color) { | ||||
|         .r = a.r + (b.r - a.r) * amount, | ||||
|         .g = a.g + (b.g - a.g) * amount, | ||||
|         .b = a.b + (b.b - a.b) * amount, | ||||
|         .a = a.a + (b.a - a.a) * amount, | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| Clay_String LOREM_IPSUM_TEXT = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); | ||||
| 
 | ||||
| void HighPerformancePageDesktop(float lerpValue) { | ||||
|     CLAY_RECTANGLE(CLAY_ID("PerformanceDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 82, 32}, .childGap = 64), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED), { | ||||
|         CLAY_CONTAINER(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { | ||||
|             CLAY_TEXT(CLAY_ID("PerformanceTextTitle"), CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); | ||||
|             CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); | ||||
|             CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 1), CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); | ||||
|             CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 2), CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); | ||||
|             CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 3), CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); | ||||
|         }); | ||||
|         CLAY_CONTAINER(CLAY_ID("PerformanceRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER}), { | ||||
|             CLAY_BORDER_CONTAINER(CLAY_ID(""), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(400) }), CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_LIGHT), { | ||||
|                 CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.3f + 0.4f * lerpValue), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {32, 32}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)), { | ||||
|                     CLAY_TEXT(CLAY_ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); | ||||
|                 }); | ||||
|                 CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER},  .padding = {32, 32}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)), { | ||||
|                     CLAY_TEXT(CLAY_ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void HighPerformancePageMobile(float lerpValue) { | ||||
|     CLAY_RECTANGLE(CLAY_ID("PerformanceMobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED), { | ||||
|         CLAY_CONTAINER(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { | ||||
|             CLAY_TEXT(CLAY_ID("PerformanceTextTitle"), CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); | ||||
|             CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); | ||||
|             CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 1), CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); | ||||
|             CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 2), CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); | ||||
|             CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 3), CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); | ||||
|         }); | ||||
|         CLAY_CONTAINER(CLAY_ID("PerformanceRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = {CLAY_ALIGN_X_CENTER}), { | ||||
|             CLAY_BORDER_CONTAINER(CLAY_ID(""), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(400) }), CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_LIGHT), { | ||||
|                 CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.35f + 0.3f * lerpValue), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)), { | ||||
|                     CLAY_TEXT(CLAY_ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); | ||||
|                 }); | ||||
|                 CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER},  .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)), { | ||||
|                     CLAY_TEXT(CLAY_ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void RendererButtonActive(uint32_t id, int index, Clay_String text) { | ||||
|     CLAY_RECTANGLE(id, CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(id) ? COLOR_RED_HOVER : COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(10)), { | ||||
|         CLAY_TEXT(CLAY_ID("RendererButtonActiveText"), text, CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void RendererButtonInactive(uint32_t id, int index, Clay_String text) { | ||||
|     CLAY_BORDER_CONTAINER(id, CLAY_LAYOUT(), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, COLOR_RED, 10), { | ||||
|         CLAY_RECTANGLE(CLAY_IDI("RendererButtonInactiveInner", index), CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(id) ? COLOR_LIGHT_HOVER : COLOR_LIGHT, .cornerRadius = CLAY_CORNER_RADIUS(10), .cursorPointer = true), { | ||||
|             CLAY_TEXT(CLAY_IDI("RendererButtonInactiveText", index), text, CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void RendererPageDesktop() { | ||||
|     CLAY_CONTAINER(CLAY_ID("RendererPageDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50}), { | ||||
|         CLAY_BORDER_CONTAINER(CLAY_ID("RendererPage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { | ||||
|             CLAY_CONTAINER(CLAY_ID("RendererLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { | ||||
|                 CLAY_TEXT(CLAY_ID("RendererTextTitle"), CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); | ||||
|                 CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); | ||||
|                 CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 1), CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|                 CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 2), CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|                 CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 3), CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|             }); | ||||
|             CLAY_CONTAINER(CLAY_ID("RendererRightText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .childAlignment = {CLAY_ALIGN_X_CENTER}, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), { | ||||
|                 CLAY_TEXT(CLAY_ID("RendererTextRightTitle"), CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE)); | ||||
|                 CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 32) }), {}); | ||||
|                 if (ACTIVE_RENDERER_INDEX == 0) { | ||||
|                     RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 0, CLAY_STRING("HTML Renderer")); | ||||
|                     RendererButtonInactive(CLAY_ID("RendererSelectButtonCanvas"), 1, CLAY_STRING("Canvas Renderer")); | ||||
|                 } else { | ||||
|                     RendererButtonInactive(CLAY_ID("RendererSelectButtonHTML"), 0, CLAY_STRING("HTML Renderer")); | ||||
|                     RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 1, CLAY_STRING("Canvas Renderer")); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void RendererPageMobile() { | ||||
|     CLAY_RECTANGLE(CLAY_ID("RendererMobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { | ||||
|         CLAY_CONTAINER(CLAY_ID("RendererLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { | ||||
|             CLAY_TEXT(CLAY_ID("RendererTextTitle"), CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); | ||||
|             CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); | ||||
|             CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 1), CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|             CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 2), CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|             CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 3), CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); | ||||
|         }); | ||||
|         CLAY_CONTAINER(CLAY_ID("RendererRightText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), { | ||||
|             CLAY_TEXT(CLAY_ID("RendererTextRightTitle"), CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE)); | ||||
|             CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 32) }), {}); | ||||
|             if (ACTIVE_RENDERER_INDEX == 0) { | ||||
|                 RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 0, CLAY_STRING("HTML Renderer")); | ||||
|                 RendererButtonInactive(CLAY_ID("RendererSelectButtonCanvas"), 1, CLAY_STRING("Canvas Renderer")); | ||||
|             } else { | ||||
|                 RendererButtonInactive(CLAY_ID("RendererSelectButtonHTML"), 0, CLAY_STRING("HTML Renderer")); | ||||
|                 RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 1, CLAY_STRING("Canvas Renderer")); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| Clay_RenderCommandArray CreateLayout(float lerpValue) { | ||||
|     bool mobileScreen = windowWidth < 750; | ||||
|     Clay_BeginLayout((int)windowWidth, (int)windowHeight); | ||||
|     CLAY_RECTANGLE(CLAY_ID("OuterContainer"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { | ||||
|         CLAY_CONTAINER(CLAY_ID("Header"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(50) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .childGap = 24, .padding = { 32 }), { | ||||
|             CLAY_TEXT(CLAY_ID("Logo"), CLAY_STRING("Clay"), &headerTextConfig); | ||||
|             CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }), {}); | ||||
| 
 | ||||
|             if (!mobileScreen) { | ||||
|                 CLAY_TEXT(CLAY_ID("LinkFeatures"), CLAY_STRING("Features"), &headerTextConfig); | ||||
|                 CLAY_TEXT(CLAY_ID("LinkDocs"), CLAY_STRING("Docs"), &headerTextConfig); | ||||
|             } | ||||
|             uint32_t githubButtonId = CLAY_ID("HeaderButtonGithub"); | ||||
|             CLAY_BORDER_CONTAINER(CLAY_ID("LinkGithubOuter"), CLAY_LAYOUT(), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, COLOR_RED, 10), { | ||||
|                 CLAY_RECTANGLE(githubButtonId, CLAY_LAYOUT(.padding = {16, 6}), CLAY_RECTANGLE_CONFIG(.cornerRadius = CLAY_CORNER_RADIUS(10), .link = CLAY_STRING("https://github.com/nicbarker/clay"), .color = Clay_PointerOver(githubButtonId) ? COLOR_LIGHT_HOVER : COLOR_LIGHT), { | ||||
|                     CLAY_TEXT(CLAY_ID("LinkGithubText"), CLAY_STRING("Github"), CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255})); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|         CLAY_RECTANGLE(CLAY_ID("TopBorder1"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_5), {}); | ||||
|         CLAY_RECTANGLE(CLAY_ID("TopBorder2"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_4), {}); | ||||
|         CLAY_RECTANGLE(CLAY_ID("TopBorder3"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_3), {}); | ||||
|         CLAY_RECTANGLE(CLAY_ID("TopBorder4"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_2), {}); | ||||
|         CLAY_RECTANGLE(CLAY_ID("TopBorder5"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_1), {}); | ||||
|         CLAY_RECTANGLE(CLAY_ID("ScrollContainerBackgroundRectangle"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { | ||||
|             CLAY_SCROLL_CONTAINER(CLAY_ID("OuterScrollContainer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_SCROLL_CONFIG(.vertical = true), { | ||||
|                 CLAY_BORDER_CONTAINER(CLAY_ID("ScrollContainerInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }), CLAY_BORDER_CONFIG(.betweenChildren = {2, COLOR_RED}), { | ||||
|                     if (mobileScreen) { | ||||
|                         LandingPageMobile(); | ||||
|                         FeatureBlocksMobile(); | ||||
|                         DeclarativeSyntaxPageMobile(); | ||||
|                         HighPerformancePageMobile(lerpValue); | ||||
|                         RendererPageMobile(); | ||||
|                     } else { | ||||
|                         LandingPageDesktop(); | ||||
|                         FeatureBlocksDesktop(); | ||||
|                         DeclarativeSyntaxPageDesktop(); | ||||
|                         HighPerformancePageDesktop(lerpValue); | ||||
|                         RendererPageDesktop(); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|     return Clay_EndLayout((int)windowWidth, (int)windowHeight); | ||||
| } | ||||
| 
 | ||||
| float animationLerpValue = -1.0f; | ||||
| 
 | ||||
| CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(float width, float height, float mouseWheelX, float mouseWheelY, float mousePositionX, float mousePositionY, bool isTouchDown, bool isMouseDown, float deltaTime) { | ||||
|     windowWidth = width; | ||||
|     windowHeight = height; | ||||
|     if (deltaTime == deltaTime) { // NaN propagation can cause pain here
 | ||||
|         animationLerpValue += deltaTime; | ||||
|         if (animationLerpValue > 1) { | ||||
|             animationLerpValue -= 2; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (isTouchDown || isMouseDown) { | ||||
|         if (Clay_PointerOver(CLAY_ID("RendererSelectButtonHTML"))) { | ||||
|             ACTIVE_RENDERER_INDEX = 0; | ||||
|         } else if (Clay_PointerOver(CLAY_ID("RendererSelectButtonCanvas"))) { | ||||
|             ACTIVE_RENDERER_INDEX = 1; | ||||
|         } | ||||
|     } | ||||
|     //----------------------------------------------------------------------------------
 | ||||
|     // Handle scroll containers
 | ||||
|     Clay_SetPointerPosition((Clay_Vector2) {mousePositionX, mousePositionY}); | ||||
|     Clay_UpdateScrollContainers(isTouchDown, (Clay_Vector2) {mouseWheelX, mouseWheelY}, deltaTime); | ||||
|     return CreateLayout(animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue)); | ||||
|     //----------------------------------------------------------------------------------
 | ||||
| } | ||||
							
								
								
									
										33
									
								
								examples/raylib-sidebar-scrolling-container/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,33 @@ | |||
| cmake_minimum_required(VERSION 3.28) | ||||
| project(clay_examples_raylib_sidebar_scrolling_container C) | ||||
| 
 | ||||
| # Adding Raylib | ||||
| include(FetchContent) | ||||
| set(FETCHCONTENT_QUIET FALSE) | ||||
| set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples | ||||
| set(BUILD_GAMES    OFF CACHE BOOL "" FORCE) # don't build the supplied example games | ||||
| 
 | ||||
| FetchContent_Declare( | ||||
|     raylib | ||||
|     GIT_REPOSITORY "https://github.com/raysan5/raylib.git" | ||||
|     GIT_TAG "master" | ||||
|     GIT_PROGRESS TRUE | ||||
| ) | ||||
| 
 | ||||
| FetchContent_MakeAvailable(raylib) | ||||
| 
 | ||||
| set(CMAKE_C_STANDARD 99) | ||||
| 
 | ||||
| add_executable(clay_examples_raylib_sidebar_scrolling_container main.c) | ||||
| 
 | ||||
| target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC -DCLAY_OVERFLOW_TRAP -Wno-initializer-overrides) | ||||
| target_include_directories(clay_examples_raylib_sidebar_scrolling_container PUBLIC .) | ||||
| 
 | ||||
| target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC raylib) | ||||
| set(CMAKE_CXX_FLAGS_RELEASE "-O3") | ||||
| 
 | ||||
| add_custom_command( | ||||
|         TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD | ||||
|         COMMAND ${CMAKE_COMMAND} -E copy_directory | ||||
|         ${CMAKE_CURRENT_SOURCE_DIR}/resources | ||||
|         ${CMAKE_CURRENT_BINARY_DIR}/resources) | ||||
							
								
								
									
										168
									
								
								examples/raylib-sidebar-scrolling-container/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 101 KiB | 
							
								
								
									
										7
									
								
								generator/array_add.template.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,7 @@ | |||
| $TYPE$ *$NAME$_Add($NAME$ *array, $TYPE$ item) { | ||||
| 	if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) { | ||||
| 		array->internalArray[array->length++] = item; | ||||
| 		return &array->internalArray[array->length - 1]; | ||||
| 	} | ||||
| 	return $DEFAULT_VALUE$; | ||||
| } | ||||
							
								
								
									
										10
									
								
								generator/array_define.template.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,10 @@ | |||
| typedef struct | ||||
| { | ||||
| 	uint32_t capacity; | ||||
| 	uint32_t length; | ||||
| 	$TYPE$ *internalArray; | ||||
| } $NAME$; | ||||
| 
 | ||||
| $NAME$ $NAME$_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { | ||||
|     return ($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), arena)}; | ||||
| } | ||||
							
								
								
									
										3
									
								
								generator/array_get.template.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,3 @@ | |||
| $TYPE$ *$NAME$_Get($NAME$ *array, int index) { | ||||
|     return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : $DEFAULT_VALUE$; | ||||
| } | ||||
							
								
								
									
										9
									
								
								generator/array_remove_swapback.template.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,9 @@ | |||
| $TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int index) { | ||||
| 	if (Clay__Array_RangeCheck(index, array->length)) { | ||||
| 		array->length--; | ||||
| 		$TYPE$ removed = array->internalArray[index]; | ||||
| 		array->internalArray[index] = array->internalArray[array->length]; | ||||
| 		return removed; | ||||
| 	} | ||||
| 	return $DEFAULT_VALUE$; | ||||
| } | ||||
							
								
								
									
										11
									
								
								generator/array_set.template.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,11 @@ | |||
| void $NAME$_Set($NAME$ *array, int index, $TYPE$ value) { | ||||
| 	if (index < array->capacity && index >= 0) { | ||||
| 		array->internalArray[index] = value; | ||||
| 		array->length = index < array->length ? array->length : index + 1; | ||||
| 	} else { | ||||
|         Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow.")); | ||||
|         #ifdef CLAY_OVERFLOW_TRAP | ||||
|         raise(SIGTRAP); | ||||
|         #endif | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										69
									
								
								generator/generate_templates.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,69 @@ | |||
| const fs = require('fs'); | ||||
| const path = require('path'); | ||||
| 
 | ||||
| let files = ['../clay.h']; | ||||
| 
 | ||||
| let templates = ['./']; | ||||
| function readCTemplatesRecursive(directory) { | ||||
|     fs.readdirSync(directory).forEach(template => { | ||||
|         const absolute = path.join(directory, template); | ||||
|         if (fs.statSync(absolute).isDirectory()) return readCTemplatesRecursive(absolute); | ||||
|         else if (template.endsWith('template.c')) { | ||||
|             return templates.push(absolute); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| readCTemplatesRecursive(__dirname); | ||||
| 
 | ||||
| for (const file of files) { | ||||
|     const contents = fs.readFileSync(file, 'utf8'); | ||||
|     const lines = contents.split('\n'); | ||||
|     for (let i = 0; i < lines.length; i++) { | ||||
|         const line = lines[i]; | ||||
|         if (line.startsWith('// __GENERATED__ template')) { | ||||
|             const [comment, generated, templateOpen, templateNames, ...args] = line.split(" "); | ||||
|             let matchingEndingLine = -1; | ||||
|             for (let j = i + 1; j < lines.length; j++) { | ||||
|                 if (lines[j].startsWith('// __GENERATED__ template')) { | ||||
|                     matchingEndingLine = j; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (matchingEndingLine !== -1) { | ||||
|                 i++; | ||||
|                 lines.splice(i, matchingEndingLine - (i)); | ||||
|                 lines.splice(i, 0, ['#pragma region generated']); | ||||
|                 i++; | ||||
|                 for (const templateName of templateNames.split(',')) { | ||||
|                     var matchingTemplate = templates.find(t => t.endsWith(`${templateName}.template.c`)); | ||||
|                     if (matchingTemplate) { | ||||
|                         let templateContents = fs.readFileSync(matchingTemplate, 'utf8'); | ||||
|                         for (const arg of args) { | ||||
|                             [argName, argValue] = arg.split('='); | ||||
|                             templateContents = templateContents.replaceAll(`\$${argName}\$`, argValue); | ||||
|                         } | ||||
|                         let remainingTokens = templateContents.split('$'); | ||||
|                         if (remainingTokens.length > 1) { | ||||
|                             console.log(`Error at ${file}:${i}: Template is missing parameter ${remainingTokens[1]}`) | ||||
|                             process.exit(); | ||||
|                         } else { | ||||
|                             templateContents = templateContents.split('\n'); | ||||
|                             lines.splice(i, 0, ...templateContents); | ||||
|                             i += templateContents.length; | ||||
|                         } | ||||
|                     } else { | ||||
|                         console.log(`Error at ${file}:${i + 1}: no template with name ${templateName}.template.c was found.`); | ||||
|                         process.exit(); | ||||
|                     } | ||||
|                 } | ||||
|                 lines.splice(i, 0, ['#pragma endregion']); | ||||
|                 i++; | ||||
|             } else { | ||||
|                 console.log(`Error at ${file}:${i + 1}: template was opened and not closed again.`); | ||||
|                 process.exit(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fs.writeFileSync(file, lines.join('\n')); | ||||
| } | ||||
							
								
								
									
										228
									
								
								renderers/raylib/clay_renderer_raylib.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,228 @@ | |||
| #include "raylib.h" | ||||
| #include "raymath.h" | ||||
| #include "stdint.h" | ||||
| #include "string.h" | ||||
| #include "stdio.h" | ||||
| #include "stdlib.h" | ||||
| #include "signal.h" | ||||
| 
 | ||||
| #define CLAY_RECTANGLE_TO_RAYLIB_RECTANGLE(rectangle) (Rectangle) { .x = rectangle.x, .y = rectangle.y, .width = rectangle.width, .height = rectangle.height } | ||||
| #define CLAY_COLOR_TO_RAYLIB_COLOR(color) (Color) { .r = (unsigned char)roundf(color.r), .g = (unsigned char)roundf(color.g), .b = (unsigned char)roundf(color.b), .a = (unsigned char)roundf(color.a) } | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|     uint32_t fontId; | ||||
|     Font font; | ||||
| } Raylib_Font; | ||||
| 
 | ||||
| Raylib_Font Raylib_fonts[10]; | ||||
| Camera Raylib_camera; | ||||
| 
 | ||||
| typedef enum | ||||
| { | ||||
|     CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL | ||||
| } CustomLayoutElementType; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|     Model model; | ||||
|     float scale; | ||||
|     Vector3 position; | ||||
|     Matrix rotation; | ||||
| } CustomLayoutElement_3DModel; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|     CustomLayoutElementType type; | ||||
|     union { | ||||
|         CustomLayoutElement_3DModel model; | ||||
|     }; | ||||
| } CustomLayoutElement; | ||||
| 
 | ||||
| // Get a ray trace from the screen position (i.e mouse) within a specific section of the screen
 | ||||
| Ray GetScreenToWorldPointWithZDistance(Vector2 position, Camera camera, int screenWidth, int screenHeight, float zDistance) | ||||
| { | ||||
|     Ray ray = { 0 }; | ||||
| 
 | ||||
|     // Calculate normalized device coordinates
 | ||||
|     // NOTE: y value is negative
 | ||||
|     float x = (2.0f*position.x)/(float)screenWidth - 1.0f; | ||||
|     float y = 1.0f - (2.0f*position.y)/(float)screenHeight; | ||||
|     float z = 1.0f; | ||||
| 
 | ||||
|     // Store values in a vector
 | ||||
|     Vector3 deviceCoords = { x, y, z }; | ||||
| 
 | ||||
|     // Calculate view matrix from camera look at
 | ||||
|     Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); | ||||
| 
 | ||||
|     Matrix matProj = MatrixIdentity(); | ||||
| 
 | ||||
|     if (camera.projection == CAMERA_PERSPECTIVE) | ||||
|     { | ||||
|         // Calculate projection matrix from perspective
 | ||||
|         matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)screenWidth/(double)screenHeight), 0.01f, zDistance); | ||||
|     } | ||||
|     else if (camera.projection == CAMERA_ORTHOGRAPHIC) | ||||
|     { | ||||
|         double aspect = (double)screenWidth/(double)screenHeight; | ||||
|         double top = camera.fovy/2.0; | ||||
|         double right = top*aspect; | ||||
| 
 | ||||
|         // Calculate projection matrix from orthographic
 | ||||
|         matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); | ||||
|     } | ||||
| 
 | ||||
|     // Unproject far/near points
 | ||||
|     Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); | ||||
|     Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); | ||||
| 
 | ||||
|     // Calculate normalized direction vector
 | ||||
|     Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); | ||||
| 
 | ||||
|     ray.position = farPoint; | ||||
| 
 | ||||
|     // Apply calculated vectors to ray
 | ||||
|     ray.direction = direction; | ||||
| 
 | ||||
|     return ray; | ||||
| } | ||||
| 
 | ||||
| uint32_t measureCalls = 0; | ||||
| 
 | ||||
| static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextElementConfig *config) { | ||||
|     measureCalls++; | ||||
|     // Measure string size for Font
 | ||||
|     Clay_Dimensions textSize = { 0 }; | ||||
| 
 | ||||
|     float maxTextWidth = 0.0f; | ||||
|     float lineTextWidth = 0; | ||||
| 
 | ||||
|     float textHeight = config->fontSize; | ||||
|     Font fontToUse = Raylib_fonts[config->fontId].font; | ||||
| 
 | ||||
|     for (int i = 0; i < text->length; ++i) | ||||
|     { | ||||
|         if (text->chars[i] == '\n') { | ||||
|             maxTextWidth = fmax(maxTextWidth, lineTextWidth); | ||||
|             lineTextWidth = 0; | ||||
|             continue; | ||||
|         } | ||||
|         int index = text->chars[i] - 32; | ||||
|         if (fontToUse.glyphs[index].advanceX != 0) lineTextWidth += fontToUse.glyphs[index].advanceX; | ||||
|         else lineTextWidth += (fontToUse.recs[index].width + fontToUse.glyphs[index].offsetX); | ||||
|     } | ||||
| 
 | ||||
|     maxTextWidth = fmax(maxTextWidth, lineTextWidth); | ||||
| 
 | ||||
|     textSize.width = maxTextWidth / 2; | ||||
|     textSize.height = textHeight; | ||||
| 
 | ||||
|     return textSize; | ||||
| } | ||||
| 
 | ||||
| void Clay_Raylib_Initialize(unsigned int flags) { | ||||
|     SetConfigFlags(flags); | ||||
|     InitWindow(1024, 768, "Clay - Raylib Renderer Example"); | ||||
| //    EnableEventWaiting();
 | ||||
| } | ||||
| 
 | ||||
| void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands) | ||||
| { | ||||
|     measureCalls = 0; | ||||
|     for (int j = 0; j < renderCommands.length; j++) | ||||
|     { | ||||
|         Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); | ||||
|         Clay_Rectangle boundingBox = renderCommand->boundingBox; | ||||
|         switch (renderCommand->commandType) | ||||
|         { | ||||
|             case CLAY_RENDER_COMMAND_TYPE_TEXT: { | ||||
|                 // Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
 | ||||
|                 Clay_String text = renderCommand->text; | ||||
|                 char *cloned = (char *)malloc(text.length + 1); | ||||
|                 memcpy(cloned, text.chars, text.length); | ||||
|                 cloned[text.length] = '\0'; | ||||
|                 Font fontToUse = Raylib_fonts[renderCommand->config.textElementConfig->fontId].font; | ||||
|                 DrawTextEx(fontToUse, cloned, (Vector2){boundingBox.x, boundingBox.y}, (float)renderCommand->config.textElementConfig->fontSize, (float)renderCommand->config.textElementConfig->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(renderCommand->config.textElementConfig->textColor)); | ||||
|                 free(cloned); | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_IMAGE: { | ||||
|                 Texture2D imageTexture = *(Texture2D *)renderCommand->config.imageElementConfig->imageData; | ||||
|                 DrawTextureEx( | ||||
|                 imageTexture, | ||||
|                 (Vector2){boundingBox.x, boundingBox.y}, | ||||
|                 0, | ||||
|                 boundingBox.width / (float)imageTexture.width, | ||||
|                 WHITE); | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { | ||||
|                 BeginScissorMode((int)roundf(boundingBox.x), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width), (int)roundf(boundingBox.height)); | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { | ||||
|                 EndScissorMode(); | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { | ||||
|                 DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width), (int)roundf(boundingBox.height), CLAY_COLOR_TO_RAYLIB_COLOR(renderCommand->config.rectangleElementConfig->color)); | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_BORDER: { | ||||
|                 Clay_BorderContainerElementConfig *config = renderCommand->config.borderElementConfig; | ||||
|                 // Left border
 | ||||
|                 if (config->left.width > 0) { | ||||
|                     DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y + config->cornerRadius.topLeft), (int)config->left.width, (int)roundf(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), CLAY_COLOR_TO_RAYLIB_COLOR(config->left.color)); | ||||
|                 } | ||||
|                 // Right border
 | ||||
|                 if (config->right.width > 0) { | ||||
|                     DrawRectangle((int)roundf(boundingBox.x + boundingBox.width - config->right.width), (int)roundf(boundingBox.y + config->cornerRadius.topRight), (int)config->right.width, (int)roundf(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), CLAY_COLOR_TO_RAYLIB_COLOR(config->right.color)); | ||||
|                 } | ||||
|                 // Top border
 | ||||
|                 if (config->top.width > 0) { | ||||
|                     DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.topLeft), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight), (int)config->top.width, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); | ||||
|                 } | ||||
|                 // Bottom border
 | ||||
|                 if (config->bottom.width > 0) { | ||||
|                     DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.bottomLeft), (int)roundf(boundingBox.y + boundingBox.height - config->bottom.width), (int)roundf(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), (int)config->bottom.width, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); | ||||
|                 } | ||||
|                 if (config->cornerRadius.topLeft > 0) { | ||||
|                     DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.topLeft), roundf(boundingBox.y + config->cornerRadius.topLeft) }, roundf(config->cornerRadius.topLeft - config->top.width), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); | ||||
|                 } | ||||
|                 if (config->cornerRadius.topRight > 0) { | ||||
|                     DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->top.width), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); | ||||
|                 } | ||||
|                 if (config->cornerRadius.bottomLeft > 0) { | ||||
|                     DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->top.width), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); | ||||
|                 } | ||||
|                 if (config->cornerRadius.bottomRight > 0) { | ||||
|                     DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->bottom.width), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { | ||||
|                 CustomLayoutElement *customElement = (CustomLayoutElement *)renderCommand->config.customElementConfig->customData; | ||||
|                 if (!customElement) continue; | ||||
|                 switch (customElement->type) { | ||||
|                     case CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL: { | ||||
|                         Clay_Rectangle rootBox = renderCommands.internalArray[0].boundingBox; | ||||
|                         float scaleValue = CLAY__MIN(CLAY__MIN(1, 768 / rootBox.height) * CLAY__MAX(1, rootBox.width / 1024), 1.5f); | ||||
|                         Ray positionRay = GetScreenToWorldPointWithZDistance((Vector2) { renderCommand->boundingBox.x + renderCommand->boundingBox.width / 2, renderCommand->boundingBox.y + (renderCommand->boundingBox.height / 2) + 20 }, Raylib_camera, (int)roundf(rootBox.width), (int)roundf(rootBox.height), 140); | ||||
|                         BeginMode3D(Raylib_camera); | ||||
|                             DrawModel(customElement->model.model, positionRay.position, customElement->model.scale * scaleValue, WHITE);        // Draw 3d model with texture
 | ||||
|                         EndMode3D(); | ||||
|                         break; | ||||
|                     } | ||||
|                     default: break; | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|             default: { | ||||
|                 printf("Error: unhandled render command."); | ||||
|                 raise(SIGTRAP); | ||||
|                 exit(1); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1689
									
								
								renderers/raylib/raylib.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2583
									
								
								renderers/raylib/raymath.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										13
									
								
								renderers/web/build-wasm.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						|  | @ -0,0 +1,13 @@ | |||
| cp ../../clay.h clay.c && 		\ | ||||
| clang 							\ | ||||
| -Os 							\ | ||||
| -DCLAY_WASM 					\ | ||||
| -mbulk-memory 					\ | ||||
| --target=wasm32 				\ | ||||
| -nostdlib 						\ | ||||
| -Wl,--strip-all 				\ | ||||
| -Wl,--export-dynamic 			\ | ||||
| -Wl,--no-entry 					\ | ||||
| -Wl,--export=__heap_base 		\ | ||||
| -Wl,--initial-memory=6553600 	\ | ||||
| -o clay.wasm clay.c; rm clay.c; | ||||
							
								
								
									
										480
									
								
								renderers/web/canvas2d/clay-canvas2d-renderer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,480 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| 
 | ||||
| <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <title>Clay - UI Layout Library</title> | ||||
|     <style> | ||||
|         html, | ||||
|         body { | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|             overflow: hidden; | ||||
|             padding: 0; | ||||
|             margin: 0; | ||||
|             pointer-events: none; | ||||
|         } | ||||
| 
 | ||||
|         @font-face { | ||||
|             font-family: 'Calistoga'; | ||||
|             font-style: normal; | ||||
|             font-weight: 400; | ||||
|             src: url('/fonts/Calistoga-Regular.ttf') format('truetype'); | ||||
|         } | ||||
| 
 | ||||
|         @font-face { | ||||
|             font-family: 'Quicksand'; | ||||
|             font-style: normal; | ||||
|             font-weight: 400; | ||||
|             src: url('/fonts/Quicksand-Semibold.ttf') format('truetype'); | ||||
|         } | ||||
| 
 | ||||
|         body>canvas { | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| <script type="module"> | ||||
|     const CLAY_RENDER_COMMAND_TYPE_NONE = 0; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_RECTANGLE = 1; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_BORDER = 2; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_TEXT = 3; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_IMAGE = 4; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_SCISSOR_START = 5; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_SCISSOR_END = 6; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_CUSTOM = 7; | ||||
|     const GLOBAL_FONT_SCALING_FACTOR = 0.8; | ||||
|     let renderCommandSize = 0; | ||||
|     let scratchSpaceAddress = 0; | ||||
|     let heapSpaceAddress = 0; | ||||
|     let memoryDataView; | ||||
|     let textDecoder = new TextDecoder("utf-8"); | ||||
|     let previousFrameTime; | ||||
|     let fontsById = [ | ||||
|         // YOUR FONTS HERE | ||||
|     ]; | ||||
|     let elementCache = {}; | ||||
|     let imageCache = {}; | ||||
|     let colorDefinition = { | ||||
|         type: 'struct', members: [ | ||||
|             { name: 'r', type: 'float' }, | ||||
|             { name: 'g', type: 'float' }, | ||||
|             { name: 'b', type: 'float' }, | ||||
|             { name: 'a', type: 'float' }, | ||||
|         ] | ||||
|     }; | ||||
|     let stringDefinition = { | ||||
|         type: 'struct', members: [ | ||||
|             { name: 'length', type: 'uint32_t' }, | ||||
|             { name: 'chars', type: 'uint32_t' }, | ||||
|         ] | ||||
|     }; | ||||
|     let borderDefinition = { | ||||
|         type: 'struct', members: [ | ||||
|             { name: 'width', type: 'uint32_t' }, | ||||
|             { name: 'color', ...colorDefinition }, | ||||
|         ] | ||||
|     }; | ||||
|     let cornerRadiusDefinition = { | ||||
|         type: 'struct', members: [ | ||||
|             { name: 'topLeft', type: 'float' }, | ||||
|             { name: 'topRight', type: 'float' }, | ||||
|             { name: 'bottomLeft', type: 'float' }, | ||||
|             { name: 'bottomRight', type: 'float' }, | ||||
|         ] | ||||
|     }; | ||||
|     let rectangleConfigDefinition = { | ||||
|         name: 'rectangle', type: 'struct', members: [ | ||||
|             { name: 'color', ...colorDefinition }, | ||||
|             { name: 'cornerRadius', ...cornerRadiusDefinition }, | ||||
|             { name: 'link', ...stringDefinition }, | ||||
|             { name: 'cursorPointer', type: 'uint8_t' }, | ||||
|         ] | ||||
|     }; | ||||
|     let borderConfigDefinition = { | ||||
|         name: 'text', type: 'struct', members: [ | ||||
|             { name: 'left', ...borderDefinition }, | ||||
|             { name: 'right', ...borderDefinition }, | ||||
|             { name: 'top', ...borderDefinition }, | ||||
|             { name: 'bottom', ...borderDefinition }, | ||||
|             { name: 'betweenChildren', ...borderDefinition }, | ||||
|             { name: 'cornerRadius', ...cornerRadiusDefinition } | ||||
|         ] | ||||
|     }; | ||||
|     let textConfigDefinition = { | ||||
|         name: 'text', type: 'struct', members: [ | ||||
|             { name: 'textColor', ...colorDefinition }, | ||||
|             { name: 'fontId', type: 'uint16_t' }, | ||||
|             { name: 'fontSize', type: 'uint16_t' }, | ||||
|             { name: 'letterSpacing', type: 'uint16_t' }, | ||||
|             { name: 'lineSpacing', type: 'uint16_t' }, | ||||
|             { name: 'disablePointerEvents', type: 'uint8_t' } | ||||
|         ] | ||||
|     }; | ||||
|     let imageConfigDefinition = { | ||||
|         name: 'image', type: 'struct', members: [ | ||||
|             { name: 'imageData', type: 'uint32_t' }, | ||||
|             { | ||||
|                 name: 'sourceDimensions', type: 'struct', members: [ | ||||
|                     { name: 'width', type: 'float' }, | ||||
|                     { name: 'height', type: 'float' }, | ||||
|                 ] | ||||
|             }, | ||||
|             { name: 'sourceURL', ...stringDefinition } | ||||
|         ] | ||||
|     }; | ||||
|     let customConfigDefinition = { | ||||
|         name: 'custom', type: 'struct', members: [ | ||||
|             { name: 'customData', type: 'uint32_t' }, | ||||
|         ] | ||||
|     } | ||||
|     let renderCommandDefinition = { | ||||
|         name: 'CLay_RenderCommand', | ||||
|         type: 'struct', | ||||
|         members: [ | ||||
|             { | ||||
|                 name: 'boundingBox', type: 'struct', members: [ | ||||
|                     { name: 'x', type: 'float' }, | ||||
|                     { name: 'y', type: 'float' }, | ||||
|                     { name: 'width', type: 'float' }, | ||||
|                     { name: 'height', type: 'float' }, | ||||
|                 ] | ||||
|             }, | ||||
|             { name: 'config', type: 'uint32_t' }, | ||||
|             { name: 'text', ...stringDefinition }, | ||||
|             { name: 'id', type: 'uint32_t' }, | ||||
|             { name: 'commandType', type: 'uint32_t', }, | ||||
|         ] | ||||
|     }; | ||||
| 
 | ||||
|     function getStructTotalSize(definition) { | ||||
|         switch (definition.type) { | ||||
|             case 'union': | ||||
|             case 'struct': { | ||||
|                 let totalSize = 0; | ||||
|                 for (const member of definition.members) { | ||||
|                     let result = getStructTotalSize(member); | ||||
|                     if (definition.type === 'struct') { | ||||
|                         totalSize += result; | ||||
|                     } else { | ||||
|                         totalSize = Math.max(totalSize, result); | ||||
|                     } | ||||
|                 } | ||||
|                 return totalSize; | ||||
|             } | ||||
|             case 'float': return 4; | ||||
|             case 'uint32_t': return 4; | ||||
|             case 'uint16_t': return 2; | ||||
|             case 'uint8_t': return 1; | ||||
|             default: { | ||||
|                 throw "Unimplemented C data type " + definition.type | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function readStructAtAddress(address, definition) { | ||||
|         switch (definition.type) { | ||||
|             case 'union': | ||||
|             case 'struct': { | ||||
|                 let struct = { __size: 0 }; | ||||
|                 for (const member of definition.members) { | ||||
|                     let result = readStructAtAddress(address, member); | ||||
|                     struct[member.name] = result; | ||||
|                     if (definition.type === 'struct') { | ||||
|                         struct.__size += result.__size; | ||||
|                         address += result.__size; | ||||
|                     } else { | ||||
|                         struct.__size = Math.max(struct.__size, result.__size); | ||||
|                     } | ||||
|                 } | ||||
|                 return struct; | ||||
|             } | ||||
|             case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 }; | ||||
|             case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 }; | ||||
|             case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 }; | ||||
|             case 'uint8_t': return { value: memoryDataView.getUint16(address, true), __size: 1 }; | ||||
|             default: { | ||||
|                 throw "Unimplemented C data type " + definition.type | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function getTextDimensions(text, font) { | ||||
|         // re-use canvas object for better performance | ||||
|         window.canvasContext.font = font; | ||||
|         let metrics = window.canvasContext.measureText(text); | ||||
|         return { width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent }; | ||||
|     } | ||||
| 
 | ||||
|     function createMainArena(arenaStructAddress, arenaMemoryAddress) { | ||||
|         let memorySize = instance.exports.Clay_MinMemorySize(); | ||||
|         // Last arg is address to store return value | ||||
|         instance.exports.Clay_CreateArenaWithCapacityAndMemory(arenaStructAddress, memorySize, arenaMemoryAddress); | ||||
|     } | ||||
|     async function init() { | ||||
|         window.htmlRoot = document.body.appendChild(document.createElement('div')); | ||||
|         window.canvasRoot = document.body.appendChild(document.createElement('canvas')); | ||||
|         window.canvasContext = window.canvasRoot.getContext("2d"); | ||||
|         window.mousePositionXThisFrame = 0; | ||||
|         window.mousePositionYThisFrame = 0; | ||||
|         window.mouseWheelXThisFrame = 0; | ||||
|         window.mouseWheelYThisFrame = 0; | ||||
|         window.touchDown = false; | ||||
|         let zeroTimeout = null; | ||||
|         addEventListener("wheel", (event) => { | ||||
|             window.mouseWheelXThisFrame = event.deltaX * -0.1; | ||||
|             window.mouseWheelYThisFrame = event.deltaY * -0.1; | ||||
|             clearTimeout(zeroTimeout); | ||||
|             zeroTimeout = setTimeout(() => { | ||||
|                 window.mouseWheelXThisFrame = 0; | ||||
|                 window.mouseWheelYThisFrame = 0; | ||||
|             }, 10); | ||||
|         }); | ||||
| 
 | ||||
|         function handleTouch(event) { | ||||
|             if (event.touches.length === 1) { | ||||
|                 window.touchDown = true; | ||||
|                 window.mousePositionXThisFrame = event.changedTouches[0].pageX; | ||||
|                 window.mousePositionYThisFrame = event.changedTouches[0].pageY; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         document.addEventListener("touchstart", handleTouch); | ||||
|         document.addEventListener("touchmove", handleTouch); | ||||
|         document.addEventListener("touchend", () => { | ||||
|             window.touchDown = false; | ||||
|             window.mousePositionXThisFrame = 0; | ||||
|             window.mousePositionYThisFrame = 0; | ||||
|         }) | ||||
| 
 | ||||
|         document.addEventListener("mousemove", (event) => { | ||||
|             window.mousePositionXThisFrame = event.x; | ||||
|             window.mousePositionYThisFrame = event.y; | ||||
|         }); | ||||
| 
 | ||||
|         document.addEventListener("mousedown", (event) => { | ||||
|             window.mouseDown = true; | ||||
|         }); | ||||
| 
 | ||||
|         const importObject = { | ||||
|             clay: { | ||||
|                 measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => { | ||||
|                     let stringLength = memoryDataView.getUint32(textToMeasure, true); | ||||
|                     let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true); | ||||
|                     let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition); | ||||
|                     let textDecoder = new TextDecoder("utf-8"); | ||||
|                     let text = textDecoder.decode(memoryDataView.buffer.slice(pointerToString, pointerToString + stringLength)); | ||||
|                     let sourceDimensions = getTextDimensions(text, `${Math.round(textConfig.fontSize.value * GLOBAL_FONT_SCALING_FACTOR)}px ${fontsById[textConfig.fontId.value]}`); | ||||
|                     memoryDataView.setFloat32(addressOfDimensions, sourceDimensions.width, true); | ||||
|                     memoryDataView.setFloat32(addressOfDimensions + 4, sourceDimensions.height, true); | ||||
|                 } | ||||
|             }, | ||||
|         }; | ||||
|         const { instance } = await WebAssembly.instantiateStreaming( | ||||
|             fetch("./index.wasm"), importObject | ||||
|         ); | ||||
|         memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer); | ||||
|         scratchSpaceAddress = instance.exports.__heap_base.value; | ||||
|         heapSpaceAddress = instance.exports.__heap_base.value + 1024; | ||||
|         let arenaAddress = scratchSpaceAddress; | ||||
|         window.instance = instance; | ||||
|         createMainArena(arenaAddress, heapSpaceAddress); | ||||
|         instance.exports.Clay_Initialize(arenaAddress); | ||||
|         renderCommandSize = getStructTotalSize(renderCommandDefinition); | ||||
|         renderLoop(); | ||||
|     } | ||||
| 
 | ||||
|     function renderLoopCanvas() { | ||||
|         // Note: Rendering to canvas needs to be scaled up by window.devicePixelRatio in both width and height. | ||||
|         // e.g. if we're working on a device where devicePixelRatio is 2, we need to render | ||||
|         // everything at width^2 x height^2 resolution, then scale back down with css to get the correct pixel density. | ||||
|         let capacity = memoryDataView.getUint32(scratchSpaceAddress, true); | ||||
|         let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true); | ||||
|         let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true); | ||||
|         if (window.previousWidth !== window.innerWidth) { | ||||
|             window.canvasRoot.width = window.innerWidth * window.devicePixelRatio; | ||||
|             window.previousWidth = window.innerWidth; | ||||
|         } | ||||
|         if (window.previousHeight !== window.innerHeight) { | ||||
|             window.canvasRoot.height = window.innerHeight * window.devicePixelRatio; | ||||
|             window.previousHeight = window.innerHeight; | ||||
|         } | ||||
|         let ctx = window.canvasContext; | ||||
|         let scale = window.devicePixelRatio; | ||||
|         for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) { | ||||
|             let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition); | ||||
|             let boundingBox = renderCommand.boundingBox; | ||||
|             switch (renderCommand.commandType.value) { | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_NONE): { | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition); | ||||
|                     let color = config.color; | ||||
|                     ctx.beginPath(); | ||||
|                     window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                     window.canvasContext.roundRect( | ||||
|                         boundingBox.x.value * scale, // x | ||||
|                         boundingBox.y.value * scale, // y | ||||
|                         boundingBox.width.value * scale, // width | ||||
|                         boundingBox.height.value * scale, | ||||
|                         [config.cornerRadius.topLeft.value * scale, config.cornerRadius.topRight.value * scale, config.cornerRadius.bottomRight.value * scale, config.cornerRadius.bottomLeft.value * scale]) // height; | ||||
|                     ctx.fill(); | ||||
|                     ctx.closePath(); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_BORDER): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition); | ||||
|                     ctx.beginPath(); | ||||
|                     ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale); | ||||
|                     // Top Left Corner | ||||
|                     if (config.cornerRadius.topLeft.value > 0) { | ||||
|                         let lineWidth = config.top.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale); | ||||
|                         let color = config.top.color; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, config.cornerRadius.topLeft.value * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Top border | ||||
|                     if (config.top.width.value > 0) { | ||||
|                         let lineWidth = config.top.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         let color = config.top.color; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.moveTo((boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale); | ||||
|                         ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Top Right Corner | ||||
|                     if (config.cornerRadius.topRight.value > 0) { | ||||
|                         let lineWidth = config.top.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale); | ||||
|                         let color = config.top.color; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale, config.cornerRadius.topRight.value * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Right border | ||||
|                     if (config.right.width.value > 0) { | ||||
|                         let color = config.right.color; | ||||
|                         let lineWidth = config.right.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale); | ||||
|                         ctx.lineTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.topRight.value - halfLineWidth) * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Bottom Right Corner | ||||
|                     if (config.cornerRadius.bottomRight.value > 0) { | ||||
|                         let color = config.top.color; | ||||
|                         let lineWidth = config.top.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale); | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, config.cornerRadius.bottomRight.value * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Bottom Border | ||||
|                     if (config.bottom.width.value > 0) { | ||||
|                         let color = config.bottom.color; | ||||
|                         let lineWidth = config.bottom.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale); | ||||
|                         ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Bottom Left Corner | ||||
|                     if (config.cornerRadius.bottomLeft.value > 0) { | ||||
|                         let color = config.bottom.color; | ||||
|                         let lineWidth = config.bottom.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale); | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale, config.cornerRadius.bottomLeft.value * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     // Left Border | ||||
|                     if (config.left.width.value > 0) { | ||||
|                         let color = config.left.color; | ||||
|                         let lineWidth = config.left.width.value; | ||||
|                         let halfLineWidth = lineWidth / 2; | ||||
|                         ctx.lineWidth = lineWidth * scale; | ||||
|                         ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                         ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale); | ||||
|                         ctx.lineTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.bottomRight.value + halfLineWidth) * scale); | ||||
|                         ctx.stroke(); | ||||
|                     } | ||||
|                     ctx.closePath(); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_TEXT): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition); | ||||
|                     let textContents = renderCommand.text; | ||||
|                     let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value)); | ||||
|                     let fontSize = config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR * scale; | ||||
|                     ctx.font = `${fontSize}px ${fontsById[config.fontId.value]}`; | ||||
|                     let color = config.textColor; | ||||
|                     ctx.textBaseline = 'middle'; | ||||
|                     ctx.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                     ctx.fillText(textDecoder.decode(stringContents), boundingBox.x.value * scale, (boundingBox.y.value + boundingBox.height.value / 2 + 1) * scale); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): { | ||||
|                     window.canvasContext.beginPath(); | ||||
|                     window.canvasContext.rect(boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale); | ||||
|                     window.canvasContext.clip(); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): { | ||||
|                     window.canvasContext.restore(); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_IMAGE): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition); | ||||
|                     let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value))); | ||||
|                     if (!imageCache[src]) { | ||||
|                         imageCache[src] = { | ||||
|                             image: new Image(), | ||||
|                             loaded: false, | ||||
|                         } | ||||
|                         imageCache[src].image.onload = () => imageCache[src].loaded = true; | ||||
|                         imageCache[src].image.src = src; | ||||
|                     } else if (imageCache[src].loaded) { | ||||
|                         ctx.drawImage(imageCache[src].image, boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale); | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function renderLoop(currentTime) { | ||||
|         const elapsed = currentTime - previousFrameTime; | ||||
|         previousFrameTime = currentTime; | ||||
|         instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, elapsed / 1000); | ||||
|         renderLoopCanvas(); | ||||
|         requestAnimationFrame(renderLoop); | ||||
|         window.mouseDown = false; | ||||
|     } | ||||
|     init(); | ||||
| </script> | ||||
| 
 | ||||
| <body> | ||||
| </body> | ||||
| 
 | ||||
| </html> | ||||
							
								
								
									
										
											BIN
										
									
								
								renderers/web/clay.wasm
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										522
									
								
								renderers/web/html/clay-html-renderer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,522 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| 
 | ||||
| <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|     <title>Clay - UI Layout Library</title> | ||||
|     <style> | ||||
|         html, | ||||
|         body { | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|             overflow: hidden; | ||||
|             padding: 0; | ||||
|             margin: 0; | ||||
|             pointer-events: none; | ||||
|         } | ||||
| 
 | ||||
|         @font-face { | ||||
|             font-family: 'Calistoga'; | ||||
|             font-style: normal; | ||||
|             font-weight: 400; | ||||
|             src: url('/fonts/Calistoga-Regular.ttf') format('truetype'); | ||||
|         } | ||||
| 
 | ||||
|         @font-face { | ||||
|             font-family: 'Quicksand'; | ||||
|             font-style: normal; | ||||
|             font-weight: 400; | ||||
|             src: url('/fonts/Quicksand-Semibold.ttf') format('truetype'); | ||||
|         } | ||||
| 
 | ||||
|         body>canvas { | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|         } | ||||
| 
 | ||||
|         div, | ||||
|         a, | ||||
|         img { | ||||
|             position: absolute; | ||||
|             box-sizing: border-box; | ||||
|             -webkit-backface-visibility: hidden; | ||||
|         } | ||||
| 
 | ||||
|         a { | ||||
|             cursor: pointer; | ||||
|             pointer-events: all; | ||||
|         } | ||||
| 
 | ||||
|         .text { | ||||
|             pointer-events: all; | ||||
|             white-space: nowrap; | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| <script type="module"> | ||||
|     const CLAY_RENDER_COMMAND_TYPE_NONE = 0; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_RECTANGLE = 1; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_BORDER = 2; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_TEXT = 3; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_IMAGE = 4; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_SCISSOR_START = 5; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_SCISSOR_END = 6; | ||||
|     const CLAY_RENDER_COMMAND_TYPE_CUSTOM = 7; | ||||
|     const GLOBAL_FONT_SCALING_FACTOR = 0.8; | ||||
|     let renderCommandSize = 0; | ||||
|     let scratchSpaceAddress = 0; | ||||
|     let heapSpaceAddress = 0; | ||||
|     let memoryDataView; | ||||
|     let textDecoder = new TextDecoder("utf-8"); | ||||
|     let previousFrameTime; | ||||
|     let fontsById = [ | ||||
|         // YOUR FONTS HERE | ||||
|     ]; | ||||
|     let elementCache = {}; | ||||
|     let imageCache = {}; | ||||
|     let colorDefinition = { | ||||
|         type: 'struct', members: [ | ||||
|             { name: 'r', type: 'float' }, | ||||
|             { name: 'g', type: 'float' }, | ||||
|             { name: 'b', type: 'float' }, | ||||
|             { name: 'a', type: 'float' }, | ||||
|         ] | ||||
|     }; | ||||
|     let stringDefinition = { | ||||
|         type: 'struct', members: [ | ||||
|             { name: 'length', type: 'uint32_t' }, | ||||
|             { name: 'chars', type: 'uint32_t' }, | ||||
|         ] | ||||
|     }; | ||||
|     let borderDefinition = { | ||||
|         type: 'struct', members: [ | ||||
|             { name: 'width', type: 'uint32_t' }, | ||||
|             { name: 'color', ...colorDefinition }, | ||||
|         ] | ||||
|     }; | ||||
|     let cornerRadiusDefinition = { | ||||
|         type: 'struct', members: [ | ||||
|             { name: 'topLeft', type: 'float' }, | ||||
|             { name: 'topRight', type: 'float' }, | ||||
|             { name: 'bottomLeft', type: 'float' }, | ||||
|             { name: 'bottomRight', type: 'float' }, | ||||
|         ] | ||||
|     }; | ||||
|     let rectangleConfigDefinition = { | ||||
|         name: 'rectangle', type: 'struct', members: [ | ||||
|             { name: 'color', ...colorDefinition }, | ||||
|             { name: 'cornerRadius', ...cornerRadiusDefinition }, | ||||
|             { name: 'link', ...stringDefinition }, | ||||
|             { name: 'cursorPointer', type: 'uint8_t' }, | ||||
|         ] | ||||
|     }; | ||||
|     let borderConfigDefinition = { | ||||
|         name: 'text', type: 'struct', members: [ | ||||
|             { name: 'left', ...borderDefinition }, | ||||
|             { name: 'right', ...borderDefinition }, | ||||
|             { name: 'top', ...borderDefinition }, | ||||
|             { name: 'bottom', ...borderDefinition }, | ||||
|             { name: 'betweenChildren', ...borderDefinition }, | ||||
|             { name: 'cornerRadius', ...cornerRadiusDefinition } | ||||
|         ] | ||||
|     }; | ||||
|     let textConfigDefinition = { | ||||
|         name: 'text', type: 'struct', members: [ | ||||
|             { name: 'textColor', ...colorDefinition }, | ||||
|             { name: 'fontId', type: 'uint16_t' }, | ||||
|             { name: 'fontSize', type: 'uint16_t' }, | ||||
|             { name: 'letterSpacing', type: 'uint16_t' }, | ||||
|             { name: 'lineSpacing', type: 'uint16_t' }, | ||||
|             { name: 'disablePointerEvents', type: 'uint8_t' } | ||||
|         ] | ||||
|     }; | ||||
|     let imageConfigDefinition = { | ||||
|         name: 'image', type: 'struct', members: [ | ||||
|             { name: 'imageData', type: 'uint32_t' }, | ||||
|             { | ||||
|                 name: 'sourceDimensions', type: 'struct', members: [ | ||||
|                     { name: 'width', type: 'float' }, | ||||
|                     { name: 'height', type: 'float' }, | ||||
|                 ] | ||||
|             }, | ||||
|             { name: 'sourceURL', ...stringDefinition } | ||||
|         ] | ||||
|     }; | ||||
|     let customConfigDefinition = { | ||||
|         name: 'custom', type: 'struct', members: [ | ||||
|             { name: 'customData', type: 'uint32_t' }, | ||||
|         ] | ||||
|     } | ||||
|     let renderCommandDefinition = { | ||||
|         name: 'CLay_RenderCommand', | ||||
|         type: 'struct', | ||||
|         members: [ | ||||
|             { | ||||
|                 name: 'boundingBox', type: 'struct', members: [ | ||||
|                     { name: 'x', type: 'float' }, | ||||
|                     { name: 'y', type: 'float' }, | ||||
|                     { name: 'width', type: 'float' }, | ||||
|                     { name: 'height', type: 'float' }, | ||||
|                 ] | ||||
|             }, | ||||
|             { name: 'config', type: 'uint32_t' }, | ||||
|             { name: 'text', ...stringDefinition }, | ||||
|             { name: 'id', type: 'uint32_t' }, | ||||
|             { name: 'commandType', type: 'uint32_t', }, | ||||
|         ] | ||||
|     }; | ||||
| 
 | ||||
|     function getStructTotalSize(definition) { | ||||
|         switch (definition.type) { | ||||
|             case 'union': | ||||
|             case 'struct': { | ||||
|                 let totalSize = 0; | ||||
|                 for (const member of definition.members) { | ||||
|                     let result = getStructTotalSize(member); | ||||
|                     if (definition.type === 'struct') { | ||||
|                         totalSize += result; | ||||
|                     } else { | ||||
|                         totalSize = Math.max(totalSize, result); | ||||
|                     } | ||||
|                 } | ||||
|                 return totalSize; | ||||
|             } | ||||
|             case 'float': return 4; | ||||
|             case 'uint32_t': return 4; | ||||
|             case 'uint16_t': return 2; | ||||
|             case 'uint8_t': return 1; | ||||
|             default: { | ||||
|                 throw "Unimplemented C data type " + definition.type | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function readStructAtAddress(address, definition) { | ||||
|         switch (definition.type) { | ||||
|             case 'union': | ||||
|             case 'struct': { | ||||
|                 let struct = { __size: 0 }; | ||||
|                 for (const member of definition.members) { | ||||
|                     let result = readStructAtAddress(address, member); | ||||
|                     struct[member.name] = result; | ||||
|                     if (definition.type === 'struct') { | ||||
|                         struct.__size += result.__size; | ||||
|                         address += result.__size; | ||||
|                     } else { | ||||
|                         struct.__size = Math.max(struct.__size, result.__size); | ||||
|                     } | ||||
|                 } | ||||
|                 return struct; | ||||
|             } | ||||
|             case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 }; | ||||
|             case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 }; | ||||
|             case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 }; | ||||
|             case 'uint8_t': return { value: memoryDataView.getUint16(address, true), __size: 1 }; | ||||
|             default: { | ||||
|                 throw "Unimplemented C data type " + definition.type | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function getTextDimensions(text, font) { | ||||
|         // re-use canvas object for better performance | ||||
|         window.canvasContext.font = font; | ||||
|         let metrics = window.canvasContext.measureText(text); | ||||
|         return { width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent }; | ||||
|     } | ||||
| 
 | ||||
|     function createMainArena(arenaStructAddress, arenaMemoryAddress) { | ||||
|         let memorySize = instance.exports.Clay_MinMemorySize(); | ||||
|         // Last arg is address to store return value | ||||
|         instance.exports.Clay_CreateArenaWithCapacityAndMemory(arenaStructAddress, memorySize, arenaMemoryAddress); | ||||
|     } | ||||
|     async function init() { | ||||
|         window.htmlRoot = document.body.appendChild(document.createElement('div')); | ||||
|         window.canvasRoot = document.body.appendChild(document.createElement('canvas')); | ||||
|         window.canvasContext = window.canvasRoot.getContext("2d"); | ||||
|         window.mousePositionXThisFrame = 0; | ||||
|         window.mousePositionYThisFrame = 0; | ||||
|         window.mouseWheelXThisFrame = 0; | ||||
|         window.mouseWheelYThisFrame = 0; | ||||
|         window.touchDown = false; | ||||
|         let zeroTimeout = null; | ||||
|         addEventListener("wheel", (event) => { | ||||
|             window.mouseWheelXThisFrame = event.deltaX * -0.1; | ||||
|             window.mouseWheelYThisFrame = event.deltaY * -0.1; | ||||
|             clearTimeout(zeroTimeout); | ||||
|             zeroTimeout = setTimeout(() => { | ||||
|                 window.mouseWheelXThisFrame = 0; | ||||
|                 window.mouseWheelYThisFrame = 0; | ||||
|             }, 10); | ||||
|         }); | ||||
| 
 | ||||
|         function handleTouch(event) { | ||||
|             if (event.touches.length === 1) { | ||||
|                 window.touchDown = true; | ||||
|                 window.mousePositionXThisFrame = event.changedTouches[0].pageX; | ||||
|                 window.mousePositionYThisFrame = event.changedTouches[0].pageY; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         document.addEventListener("touchstart", handleTouch); | ||||
|         document.addEventListener("touchmove", handleTouch); | ||||
|         document.addEventListener("touchend", () => { | ||||
|             window.touchDown = false; | ||||
|             window.mousePositionXThisFrame = 0; | ||||
|             window.mousePositionYThisFrame = 0; | ||||
|         }) | ||||
| 
 | ||||
|         document.addEventListener("mousemove", (event) => { | ||||
|             window.mousePositionXThisFrame = event.x; | ||||
|             window.mousePositionYThisFrame = event.y; | ||||
|         }); | ||||
| 
 | ||||
|         document.addEventListener("mousedown", (event) => { | ||||
|             window.mouseDown = true; | ||||
|         }); | ||||
| 
 | ||||
|         const importObject = { | ||||
|             clay: { | ||||
|                 measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => { | ||||
|                     let stringLength = memoryDataView.getUint32(textToMeasure, true); | ||||
|                     let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true); | ||||
|                     let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition); | ||||
|                     let textDecoder = new TextDecoder("utf-8"); | ||||
|                     let text = textDecoder.decode(memoryDataView.buffer.slice(pointerToString, pointerToString + stringLength)); | ||||
|                     let sourceDimensions = getTextDimensions(text, `${Math.round(textConfig.fontSize.value * GLOBAL_FONT_SCALING_FACTOR)}px ${fontsById[textConfig.fontId.value]}`); | ||||
|                     memoryDataView.setFloat32(addressOfDimensions, sourceDimensions.width, true); | ||||
|                     memoryDataView.setFloat32(addressOfDimensions + 4, sourceDimensions.height, true); | ||||
|                 } | ||||
|             }, | ||||
|         }; | ||||
|         const { instance } = await WebAssembly.instantiateStreaming( | ||||
|             fetch("./index.wasm"), importObject | ||||
|         ); | ||||
|         memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer); | ||||
|         scratchSpaceAddress = instance.exports.__heap_base.value; | ||||
|         heapSpaceAddress = instance.exports.__heap_base.value + 1024; | ||||
|         let arenaAddress = scratchSpaceAddress; | ||||
|         window.instance = instance; | ||||
|         createMainArena(arenaAddress, heapSpaceAddress); | ||||
|         instance.exports.Clay_Initialize(arenaAddress); | ||||
|         renderCommandSize = getStructTotalSize(renderCommandDefinition); | ||||
|         renderLoop(); | ||||
|     } | ||||
| 
 | ||||
|     function MemoryIsDifferent(one, two, length) { | ||||
|         for (let i = 0; i < length; i++) { | ||||
|             if (one[i] !== two[i]) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     function renderLoopHTML() { | ||||
|         let capacity = memoryDataView.getUint32(scratchSpaceAddress, true); | ||||
|         let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true); | ||||
|         let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true); | ||||
|         let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }]; | ||||
|         for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) { | ||||
|             let entireRenderCommandMemory = new Uint32Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize)); | ||||
|             let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition); | ||||
|             let parentElement = scissorStack[scissorStack.length - 1]; | ||||
|             let element = null; | ||||
|             if (!elementCache[renderCommand.id.value]) { | ||||
|                 let elementType = 'div'; | ||||
|                 switch (renderCommand.commandType.value) { | ||||
|                     case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { | ||||
|                         if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) { | ||||
|                             elementType = 'a'; | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                     case CLAY_RENDER_COMMAND_TYPE_IMAGE: { elementType = 'img'; break; } | ||||
|                     default: break; | ||||
|                 } | ||||
|                 element = document.createElement(elementType); | ||||
|                 element.id = renderCommand.id.value; | ||||
|                 elementCache[renderCommand.id.value] = { | ||||
|                     exists: true, | ||||
|                     element: element, | ||||
|                     previousMemoryCommand: new Uint8Array(0), | ||||
|                     previousMemoryConfig: new Uint8Array(0), | ||||
|                     previousMemoryText: new Uint8Array(0) | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             let dirty = false; | ||||
|             let elementData = elementCache[renderCommand.id.value]; | ||||
|             element = elementData.element; | ||||
|             if (Array.prototype.indexOf.call(parentElement.element.children, element) !== parentElement.nextElementIndex) { | ||||
|                 if (parentElement.nextElementIndex === 0) { | ||||
|                     parentElement.element.insertAdjacentElement('afterbegin', element); | ||||
|                 } else { | ||||
|                     parentElement.element.childNodes[parentElement.nextElementIndex - 1].insertAdjacentElement('afterend', element); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             elementData.exists = true; | ||||
|             // Don't get me started. Cheaper to compare the render command memory than to update HTML elements | ||||
|             if (renderCommand.commandType.value !== CLAY_RENDER_COMMAND_TYPE_SCISSOR_START && renderCommand.commandType.value !== CLAY_RENDER_COMMAND_TYPE_SCISSOR_END) { | ||||
|                 dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize); | ||||
|                 parentElement.nextElementIndex++; | ||||
|             } else { | ||||
|                 dirty = true; | ||||
|             } | ||||
| 
 | ||||
|             elementData.previousMemoryCommand = entireRenderCommandMemory; | ||||
|             let offsetX = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.x : 0; | ||||
|             let offsetY = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.y : 0; | ||||
|             if (dirty) { | ||||
|                 element.style.transform = `translate(${Math.round(renderCommand.boundingBox.x.value - offsetX)}px, ${Math.round(renderCommand.boundingBox.y.value - offsetY)}px)` | ||||
|                 element.style.width = Math.round(renderCommand.boundingBox.width.value) + 'px'; | ||||
|                 element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px'; | ||||
|             } | ||||
| 
 | ||||
|             switch (renderCommand.commandType.value) { | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_NONE): { | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition); | ||||
|                     let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); | ||||
|                     let linkContents = config.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.link.chars.value, config.link.chars.value + config.link.length.value))) : 0; | ||||
|                     if (linkContents.length > 0 && (window.mouseDown || window.touchDown) && instance.exports.Clay_PointerOver(renderCommand.id.value)) { | ||||
|                         window.location.href = linkContents; | ||||
|                     } | ||||
|                     if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) { | ||||
|                         break; | ||||
|                     } | ||||
|                     if (linkContents.length > 0) { | ||||
|                         element.href = linkContents; | ||||
|                     } | ||||
| 
 | ||||
|                     if (linkContents.length > 0 || config.cursorPointer.value) { | ||||
|                         element.style.pointerEvents = 'all'; | ||||
|                         element.style.cursor = 'pointer'; | ||||
|                     } | ||||
|                     elementData.previousMemoryConfig = configMemory; | ||||
|                     let color = config.color; | ||||
|                     element.style.backgroundColor = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`; | ||||
|                     if (config.cornerRadius.topLeft.value > 0) { | ||||
|                         element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.topRight.value > 0) { | ||||
|                         element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.bottomLeft.value > 0) { | ||||
|                         element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.bottomRight.value > 0) { | ||||
|                         element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px'; | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_BORDER): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition); | ||||
|                     let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); | ||||
|                     if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) { | ||||
|                         break; | ||||
|                     } | ||||
|                     elementData.previousMemoryConfig = configMemory; | ||||
|                     if (config.left.width.value > 0) { | ||||
|                         let color = config.left.color; | ||||
|                         element.style.borderLeft = `${config.left.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})` | ||||
|                     } | ||||
|                     if (config.right.width.value > 0) { | ||||
|                         let color = config.right.color; | ||||
|                         element.style.borderRight = `${config.right.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})` | ||||
|                     } | ||||
|                     if (config.top.width.value > 0) { | ||||
|                         let color = config.top.color; | ||||
|                         element.style.borderTop = `${config.top.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})` | ||||
|                     } | ||||
|                     if (config.bottom.width.value > 0) { | ||||
|                         let color = config.bottom.color; | ||||
|                         element.style.borderBottom = `${config.bottom.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})` | ||||
|                     } | ||||
|                     if (config.cornerRadius.topLeft.value > 0) { | ||||
|                         element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.topRight.value > 0) { | ||||
|                         element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.bottomLeft.value > 0) { | ||||
|                         element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px'; | ||||
|                     } | ||||
|                     if (config.cornerRadius.bottomRight.value > 0) { | ||||
|                         element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px'; | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_TEXT): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition); | ||||
|                     let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); | ||||
|                     let textContents = renderCommand.text; | ||||
|                     let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value)); | ||||
|                     if (MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) { | ||||
|                         element.className = 'text'; | ||||
|                         let textColor = config.textColor; | ||||
|                         let fontSize = Math.round(config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR); | ||||
|                         element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`; | ||||
|                         element.style.fontFamily = fontsById[config.fontId.value]; | ||||
|                         element.style.fontSize = fontSize + 'px'; | ||||
|                         element.style.pointerEvents = config.disablePointerEvents.value ? 'none' : 'all'; | ||||
|                         elementData.previousMemoryConfig = configMemory; | ||||
|                     } | ||||
|                     if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) { | ||||
|                         element.innerHTML = textDecoder.decode(stringContents); | ||||
|                     } | ||||
|                     elementData.previousMemoryText = stringContents; | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): { | ||||
|                     scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 }); | ||||
|                     element.style.overflow = 'hidden'; | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): { | ||||
|                     scissorStack.splice(scissorStack.length - 1, 1); | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_IMAGE): { | ||||
|                     let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition); | ||||
|                     let srcContents = new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value)); | ||||
|                     if (srcContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(srcContents, elementData.previousMemoryText, srcContents.length)) { | ||||
|                         element.src = textDecoder.decode(srcContents); | ||||
|                     } | ||||
|                     elementData.previousMemoryText = srcContents; | ||||
|                     break; | ||||
|                 } | ||||
|                 case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (const key of Object.keys(elementCache)) { | ||||
|             if (elementCache[key].exists) { | ||||
|                 elementCache[key].exists = false; | ||||
|             } else { | ||||
|                 elementCache[key].element.remove(); | ||||
|                 delete elementCache[key]; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function renderLoop(currentTime) { | ||||
|         const elapsed = currentTime - previousFrameTime; | ||||
|         previousFrameTime = currentTime; | ||||
|         instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, elapsed / 1000); | ||||
|         renderLoopHTML(); | ||||
|         requestAnimationFrame(renderLoop); | ||||
|         window.mouseDown = false; | ||||
|     } | ||||
|     init(); | ||||
| </script> | ||||
| 
 | ||||
| <body> | ||||
| </body> | ||||
| 
 | ||||
| </html> | ||||
 Nic Barker
						Nic Barker