diff --git a/bindings/odin/.gitignore b/bindings/odin/.gitignore new file mode 100644 index 0000000..38fdfe7 --- /dev/null +++ b/bindings/odin/.gitignore @@ -0,0 +1,4 @@ +odin +odin.dSYM +.vscode +examples/clay-official-website/clay-official-website \ No newline at end of file diff --git a/bindings/odin/README.md b/bindings/odin/README.md new file mode 100644 index 0000000..1507a2c --- /dev/null +++ b/bindings/odin/README.md @@ -0,0 +1,145 @@ +### Odin Language Bindings + +This directory contains bindings for the [Odin](odin-lang.org) programming language, as well as an example implementation of the [clay website](https://nicbarker.com/clay) in Odin. + +If you haven't taken a look at the [full documentation for clay](https://github.com/nicbarker/clay/blob/main/README.md), it's recommended that you take a look there first to familiarise yourself with the general concepts. This README is abbreviated and applies to using clay in Odin specifically. + +The **most notable difference** between the C API and the Odin bindings is the use of if statements to create the scope for declaring child elements. When using the equivalent of the [Element Macros](https://github.com/nicbarker/clay/blob/main/README.md#element-macros): + +Special thanks to + +- [laytan](https://github.com/laytan) +- [Dudejoe870](https://github.com/Dudejoe870) +- MrStevns from the Odin Discord server + +```C +// C form of element macros +CLAY_RECTANGLE(CLAY_ID("SideBar"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { + // Child elements here +}); +``` + +```Odin +// Odin form of element macros +if clay.Rectangle(clay.ID("SideBar"), clay.Layout({ layoutDirection = .TOP_TO_BOTTOM, sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) }, padding = {16, 16} }), clay.RectangleConfig({ color = COLOR_LIGHT })) { + // Child elements here +} +``` + +### Quick Start + +1. Download the [clay-odin](link todo) directory and copy it into your project. + +```Odin +import clay "clay-odin" +``` + +2. Ask clay for how much static memory it needs using [clay.MinMemorySize()](https://github.com/nicbarker/clay/blob/main/README.md#clay_minmemorysize), create an Arena for it to use with [clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)](https://github.com/nicbarker/clay/blob/main/README.md#clay_createarenawithcapacityandmemory), and initialize it with [clay.Initialize(arena)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize). + +```Odin +minMemorySize: c.uint32_t = clay.MinMemorySize() +memory := make([^]u8, minMemorySize) +arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory) +``` + +3. Provide a `measureText(text, config)` proc "c" with [clay.SetMeasureTextFunction(function)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setmeasuretextfunction) so that clay can measure and wrap text. + +```Odin +// Example measure text function +measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions { + // clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing etc + // Note: clay.String->chars is not guaranteed to be null terminated +} + +// Tell clay how to measure text +clay.SetMeasureTextFunction(measureText) +``` + +4. **Optional** - Call [clay.SetPointerPosition(pointerPosition)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerposition) if you want to use mouse interactions. + +```Odin +// Update internal pointer position for handling mouseover / click / touch events +clay.SetPointerPosition(clay.Vector2{ mousePositionX, mousePositionY }) +``` + +5. Call [clay.BeginLayout(screenWidth, screenHeight)](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros. + +```Odin +COLOR_LIGHT :: clay.Color{224, 215, 210, 255} +COLOR_RED :: clay.Color{168, 66, 28, 255} +COLOR_ORANGE :: clay.Color{225, 138, 50, 255} + +// Layout config is just a struct that can be declared statically, or inline +sidebarItemLayout := clay.LayoutConfig { + sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(50)}, +} + +// Re-useable components are just normal functions +SidebarItemComponent :: proc(index: u32) { + if clay.Rectangle(clay.IDI("SidebarBlob", index), &sidebarItemLayout, clay.RectangleConfig({color = COLOR_ORANGE})) {} +} + +// An example function to begin the "root" of your layout tree +CreateLayout :: proc() -> clay.ClayArray(clay.RenderCommand) { + clay.BeginLayout(windowWidth, windowHeight) + + // An example of laying out a UI with a fixed width sidebar and flexible width main content + // NOTE: To create a scope for child components, the Odin api uses `if` with components that have children + if clay.Rectangle( + clay.ID("OuterContainer"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}), + clay.RectangleConfig({color = {250, 250, 255, 255}}), + ) { + if clay.Rectangle( + clay.ID("SideBar"), + clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingFixed(300), height = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}), + clay.RectangleConfig({color = COLOR_LIGHT}), + ) { + if clay.Rectangle( + clay.ID("ProfilePictureOuter"), + clay.Layout({sizing = {width = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16, childAlignment = {y = .CENTER}}), + clay.RectangleConfig({color = COLOR_RED}), + ) { + if clay.Image( + clay.ID("ProfilePicture"), + clay.Layout({sizing = {width = clay.SizingFixed(60), height = clay.SizingFixed(60)}}), + clay.ImageConfig({imageData = &profilePicture, sourceDimensions = {height = 60, width = 60}}), + ) {} + clay.Text(clay.ID("ProfileTitle"), clay.MakeString("Clay - UI Library"), clay.TextConfig({fontSize = 24, textColor = {255, 255, 255, 255}})) + } + + // Standard Odin code like loops etc work inside components + for i: u32 = 0; i < 10; i += 1 { + SidebarItemComponent(i) + } + } + + if clay.Rectangle( + clay.ID("MainContent"), + clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingGrow({})}}), + clay.RectangleConfig({color = COLOR_LIGHT}), + ) {} + } + // ... +} +``` + +6. Call [clay.EndLayout(screenWidth, screenHeight)](https://github.com/nicbarker/clay/blob/main/README.md#clay_endlayout) and process the resulting [clay.RenderCommandArray](https://github.com/nicbarker/clay/blob/main/README.md#clay_rendercommandarray) in your choice of renderer. + +```Odin +renderCommands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout(windowWidth, windowHeight) + +for i: u32 = 0; i < renderCommands.length; i += 1 { + renderCommand := clay.RenderCommandArray_Get(&renderCommands, cast(i32)i) + + switch renderCommand.commandType { + case .Rectangle: + { + DrawRectangle(renderCommand.boundingBox, renderCommand.config.rectangleElementConfig.color) + } + // ... Implement handling of other command types + } +} +``` + +Please see the [full C documentation for clay](https://github.com/nicbarker/clay/blob/main/README.md) for API details. All public C functions and Macros have Odin binding equivalents, generally of the form `CLAY_RECTANGLE` (C) -> `clay.Rectangle` (Odin) \ No newline at end of file diff --git a/bindings/odin/build-clay-lib.sh b/bindings/odin/build-clay-lib.sh new file mode 100755 index 0000000..b4ce6e0 --- /dev/null +++ b/bindings/odin/build-clay-lib.sh @@ -0,0 +1,6 @@ +cp ../../clay.h clay.c; +clang -c -o clay.o -static -target x86_64-apple-darwin clay.c -fPIC && ar r clay-odin/macos/clay.a clay.o; +clang -c -o clay.o -static clay.c -fPIC && ar r clay-odin/macos-arm64/clay.a clay.o; +clang -c -o clay-odin/windows/clay.lib -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib -static clay.c; +rm clay.o; +rm clay.c; \ No newline at end of file diff --git a/bindings/odin/clay-odin/clay.odin b/bindings/odin/clay-odin/clay.odin new file mode 100644 index 0000000..721d4b9 --- /dev/null +++ b/bindings/odin/clay-odin/clay.odin @@ -0,0 +1,434 @@ +package clay + +import "core:c" +import "core:strings" + +when ODIN_OS == .Windows { + foreign import Clay "windows/clay.lib" +} else when ODIN_OS == .Linux { + foreign import Clay "linux/libclay.a" +} else when ODIN_OS == .Darwin { + when ODIN_ARCH == .arm64 { + foreign import Clay "macos-arm64/clay.a" + } else { + foreign import Clay "macos/clay.a" + } +} + +String :: struct { + length: c.int, + chars: [^]c.char, +} + +Vector2 :: [2]c.float + +Dimensions :: struct { + width: c.float, + height: c.float, +} + +Arena :: struct { + label: String, + nextAllocation: c.uint64_t, + capacity: c.uint64_t, + memory: [^]c.char, +} + +BoundingBox :: struct { + x: c.float, + y: c.float, + width: c.float, + height: c.float, +} + +Color :: [4]c.float + +CornerRadius :: struct { + topLeft: c.float, + topRight: c.float, + bottomLeft: c.float, + bottomRight: c.float, +} + +BorderData :: struct { + width: c.uint32_t, + color: Color, +} + +RenderCommandType :: enum u8 { + None, + Rectangle, + Border, + Text, + Image, + ScissorStart, + ScissorEnd, + Custom, +} + +RectangleElementConfig :: struct { + color: Color, + cornerRadius: CornerRadius, +} + +TextElementConfig :: struct { + textColor: Color, + fontId: c.uint16_t, + fontSize: c.uint16_t, + letterSpacing: c.uint16_t, + lineSpacing: c.uint16_t, +} + +ImageElementConfig :: struct { + imageData: rawptr, + sourceDimensions: Dimensions, +} + +CustomElementConfig :: struct { + customData: rawptr, +} + +BorderElementConfig :: struct { + left: BorderData, + right: BorderData, + top: BorderData, + bottom: BorderData, + betweenChildren: BorderData, + cornerRadius: CornerRadius, +} + +ScrollElementConfig :: struct { + horizontal: c.bool, + vertical: c.bool, +} + +FloatingAttachPointType :: enum u8 { + LEFT_TOP, + LEFT_CENTER, + LEFT_BOTTOM, + CENTER_TOP, + CENTER_CENTER, + CENTER_BOTTOM, + RIGHT_TOP, + RIGHT_CENTER, + RIGHT_BOTTOM, +} + +FloatingAttachPoints :: struct { + element: FloatingAttachPointType, + parent: FloatingAttachPointType, +} + +FloatingElementConfig :: struct { + offset: Vector2, + expand: Dimensions, + zIndex: c.uint16_t, + parentId: c.uint32_t, + attachment: FloatingAttachPoints, +} + +ElementConfigUnion :: struct #raw_union { + rectangleElementConfig: ^RectangleElementConfig, + textElementConfig: ^TextElementConfig, + imageElementConfig: ^ImageElementConfig, + customElementConfig: ^CustomElementConfig, + borderElementConfig: ^BorderElementConfig, +} + +RenderCommand :: struct { + boundingBox: BoundingBox, + config: ElementConfigUnion, + text: String, + id: c.uint32_t, + commandType: RenderCommandType, +} + +ScrollContainerData :: struct { + // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. + // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. + scrollPosition: ^Vector2, + scrollContainerDimensions: Dimensions, + contentDimensions: Dimensions, + config: ScrollElementConfig, + // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. + found: c.bool, +} + +SizingType :: enum u8 { + FIT, + GROW, + PERCENT, +} + +SizingConstraintsMinMax :: struct { + min: c.float, + max: c.float, +} + +SizingConstraints :: struct #raw_union { + sizeMinMax: SizingConstraintsMinMax, + sizePercent: c.float, +} + +SizingAxis :: struct { + // Note: `min` is used for CLAY_SIZING_PERCENT, slightly different to clay.h due to lack of C anonymous unions + constraints: SizingConstraints, + type: SizingType, +} + +Sizing :: struct { + width: SizingAxis, + height: SizingAxis, +} + +Padding :: struct { + x: c.uint16_t, + y: c.uint16_t, +} + +LayoutDirection :: enum u8 { + LEFT_TO_RIGHT, + TOP_TO_BOTTOM, +} + +LayoutAlignmentX :: enum u8 { + LEFT, + RIGHT, + CENTER, +} + +LayoutAlignmentY :: enum u8 { + TOP, + BOTTOM, + CENTER, +} + +ChildAlignment :: struct { + x: LayoutAlignmentX, + y: LayoutAlignmentY, +} + +LayoutConfig :: struct { + sizing: Sizing, + padding: Padding, + childGap: c.uint16_t, + layoutDirection: LayoutDirection, + childAlignment: ChildAlignment, +} + +ClayArray :: struct($type: typeid) { + capacity: c.uint32_t, + length: c.uint32_t, + internalArray: [^]type, +} + +@(link_prefix = "Clay_") +foreign Clay { + MinMemorySize :: proc() -> c.uint32_t --- + CreateArenaWithCapacityAndMemory :: proc(capacity: c.uint32_t, offset: [^]u8) -> Arena --- + SetPointerPosition :: proc(position: Vector2) --- + Initialize :: proc(arena: Arena) --- + UpdateScrollContainers :: proc(isPointerActive: c.bool, scrollDelta: Vector2, deltaTime: c.float) --- + BeginLayout :: proc(screenWidth: c.int, screenHeight: c.int) --- + EndLayout :: proc(screenWidth: c.int, screenHeight: c.int) -> ClayArray(RenderCommand) --- + PointerOver :: proc(id: c.uint32_t) -> c.bool --- + GetScrollContainerData :: proc(id: c.uint32_t) -> ScrollContainerData --- + SetMeasureTextFunction :: proc(measureTextFunction: proc(text: ^String, config: ^TextElementConfig) -> Dimensions) --- + RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: c.int32_t) -> ^RenderCommand --- + @(private) + _OpenContainerElement :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig) --- + @(private) + _OpenRectangleElement :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, rectangleConfig: ^RectangleElementConfig) --- + @(private) + _OpenTextElement :: proc(id: c.uint32_t, text: String, textConfig: ^TextElementConfig) --- + @(private) + _OpenImageElement :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, imageConfig: ^ImageElementConfig) --- + @(private) + _OpenScrollElement :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, imageConfig: ^ScrollElementConfig) --- + @(private) + _OpenFloatingElement :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, imageConfig: ^FloatingElementConfig) --- + @(private) + _OpenBorderElement :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, imageConfig: ^BorderElementConfig) --- + @(private) + _OpenCustomElement :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, imageConfig: ^CustomElementConfig) --- + @(private) + _CloseElementWithChildren :: proc() --- + @(private) + _CloseScrollElement :: proc() --- + @(private) + _CloseFloatingElement :: proc() --- + @(private) + _layoutConfigs: ClayArray(LayoutConfig) + @(private) + _LayoutConfigArray_Add :: proc(array: ^ClayArray(LayoutConfig), config: LayoutConfig) -> ^LayoutConfig --- + @(private) + _rectangleElementConfigs: ClayArray(RectangleElementConfig) + @(private) + _RectangleElementConfigArray_Add :: proc(array: ^ClayArray(RectangleElementConfig), config: RectangleElementConfig) -> ^RectangleElementConfig --- + @(private) + _textElementConfigs: ClayArray(TextElementConfig) + @(private) + _TextElementConfigArray_Add :: proc(array: ^ClayArray(TextElementConfig), config: TextElementConfig) -> ^TextElementConfig --- + @(private) + _imageElementConfigs: ClayArray(ImageElementConfig) + @(private) + _ImageElementConfigArray_Add :: proc(array: ^ClayArray(ImageElementConfig), config: ImageElementConfig) -> ^ImageElementConfig --- + @(private) + _floatingElementConfigs: ClayArray(FloatingElementConfig) + @(private) + _FloatingElementConfigArray_Add :: proc(array: ^ClayArray(FloatingElementConfig), config: FloatingElementConfig) -> ^FloatingElementConfig --- + @(private) + _customElementConfigs: ClayArray(CustomElementConfig) + @(private) + _CustomElementConfigArray_Add :: proc(array: ^ClayArray(CustomElementConfig), config: CustomElementConfig) -> ^CustomElementConfig --- + @(private) + _scrollElementConfigs: ClayArray(ScrollElementConfig) + @(private) + _ScrollElementConfigArray_Add :: proc(array: ^ClayArray(ScrollElementConfig), config: ScrollElementConfig) -> ^ScrollElementConfig --- + @(private) + _borderElementConfigs: ClayArray(BorderElementConfig) + @(private) + _BorderElementConfigArray_Add :: proc(array: ^ClayArray(BorderElementConfig), config: BorderElementConfig) -> ^BorderElementConfig --- + @(private) + _HashString :: proc(toHash: String, index: c.uint32_t) -> c.uint32_t --- +} + + +@(require_results, deferred_none = _CloseElementWithChildren) +Container :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig) -> bool { + _OpenContainerElement(id, layoutConfig) + return true +} + +@(require_results, deferred_none = _CloseElementWithChildren) +Rectangle :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, rectangleConfig: ^RectangleElementConfig) -> bool { + _OpenRectangleElement(id, layoutConfig, rectangleConfig) + return true +} + +Text :: proc(id: c.uint32_t, text: String, textConfig: ^TextElementConfig) -> bool { + _OpenTextElement(id, text, textConfig) + return true +} + +@(require_results, deferred_none = _CloseElementWithChildren) +Image :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, imageConfig: ^ImageElementConfig) -> bool { + _OpenImageElement(id, layoutConfig, imageConfig) + return true +} + +@(require_results, deferred_none = _CloseScrollElement) +Scroll :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, scrollConfig: ^ScrollElementConfig) -> bool { + _OpenScrollElement(id, layoutConfig, scrollConfig) + return true +} + +@(require_results, deferred_none = _CloseFloatingElement) +Floating :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, floatingConfig: ^FloatingElementConfig) -> bool { + _OpenFloatingElement(id, layoutConfig, floatingConfig) + return true +} + +@(require_results, deferred_none = _CloseElementWithChildren) +Border :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, borderConfig: ^BorderElementConfig) -> bool { + _OpenBorderElement(id, layoutConfig, borderConfig) + return true +} + +@(require_results, deferred_none = _CloseElementWithChildren) +Custom :: proc(id: c.uint32_t, layoutConfig: ^LayoutConfig, customConfig: ^CustomElementConfig) -> bool { + _OpenCustomElement(id, layoutConfig, customConfig) + return true +} + +Layout :: proc(config: LayoutConfig) -> ^LayoutConfig { + return _LayoutConfigArray_Add(&_layoutConfigs, config) +} + +RectangleConfig :: proc(config: RectangleElementConfig) -> ^RectangleElementConfig { + return _RectangleElementConfigArray_Add(&_rectangleElementConfigs, config) +} + +TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig { + return _TextElementConfigArray_Add(&_textElementConfigs, config) +} + +ImageConfig :: proc(config: ImageElementConfig) -> ^ImageElementConfig { + return _ImageElementConfigArray_Add(&_imageElementConfigs, config) +} + +FloatingConfig :: proc(config: FloatingElementConfig) -> ^FloatingElementConfig { + return _FloatingElementConfigArray_Add(&_floatingElementConfigs, config) +} + +Custom_elementConfig :: proc(config: CustomElementConfig) -> ^CustomElementConfig { + return _CustomElementConfigArray_Add(&_customElementConfigs, config) +} + +ScrollConfig :: proc(config: ScrollElementConfig) -> ^ScrollElementConfig { + return _ScrollElementConfigArray_Add(&_scrollElementConfigs, config) +} + +BorderConfig :: proc(config: BorderElementConfig) -> ^BorderElementConfig { + return _BorderElementConfigArray_Add(&_borderElementConfigs, config) +} + +BorderConfigOutside :: proc(outsideBorders: BorderData) -> ^BorderElementConfig { + return _BorderElementConfigArray_Add( + &_borderElementConfigs, + (BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders}, + ) +} + +BorderConfigOutsideRadius :: proc(outsideBorders: BorderData, radius: f32) -> ^BorderElementConfig { + return _BorderElementConfigArray_Add( + &_borderElementConfigs, + (BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders, cornerRadius = {radius, radius, radius, radius}}, + ) +} + +BorderConfigAll :: proc(allBorders: BorderData) -> ^BorderElementConfig { + return _BorderElementConfigArray_Add( + &_borderElementConfigs, + (BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, betweenChildren = allBorders}, + ) +} + +BorderConfigAllRadius :: proc(allBorders: BorderData, radius: f32) -> ^BorderElementConfig { + return _BorderElementConfigArray_Add( + &_borderElementConfigs, + (BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, cornerRadius = {radius, radius, radius, radius}}, + ) +} + +CornerRadiusAll :: proc(radius: f32) -> CornerRadius { + return CornerRadius{radius, radius, radius, radius} +} + +SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis { + return SizingAxis{type = SizingType.FIT, constraints = {sizeMinMax = sizeMinMax}} +} + +SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis { + return SizingAxis{type = SizingType.GROW, constraints = {sizeMinMax = sizeMinMax}} +} + +SizingFixed :: proc(size: c.float) -> SizingAxis { + return SizingAxis{type = SizingType.FIT, constraints = {sizeMinMax = {size, size}}} +} + +SizingPercent :: proc(sizePercent: c.float) -> SizingAxis { + return SizingAxis{type = SizingType.PERCENT, constraints = {sizePercent = sizePercent}} +} + +MakeString :: proc(label: string) -> String { + return String{chars = raw_data(label), length = cast(c.int)len(label)} +} + +ID :: proc(label: string) -> c.uint32_t { + return _HashString(MakeString(label), 0) +} + +IDI :: proc(label: string, index: u32) -> c.uint32_t { + return _HashString(MakeString(label), index) +} diff --git a/bindings/odin/clay-odin/macos-arm64/clay.a b/bindings/odin/clay-odin/macos-arm64/clay.a new file mode 100644 index 0000000..3f8d1f3 Binary files /dev/null and b/bindings/odin/clay-odin/macos-arm64/clay.a differ diff --git a/bindings/odin/clay-odin/macos/clay.a b/bindings/odin/clay-odin/macos/clay.a new file mode 100644 index 0000000..de77393 Binary files /dev/null and b/bindings/odin/clay-odin/macos/clay.a differ diff --git a/bindings/odin/clay-odin/windows/clay.lib b/bindings/odin/clay-odin/windows/clay.lib new file mode 100644 index 0000000..5acb144 Binary files /dev/null and b/bindings/odin/clay-odin/windows/clay.lib differ diff --git a/bindings/odin/examples/clay-official-website/clay-official-website.odin b/bindings/odin/examples/clay-official-website/clay-official-website.odin new file mode 100644 index 0000000..f7be9c3 --- /dev/null +++ b/bindings/odin/examples/clay-official-website/clay-official-website.odin @@ -0,0 +1,554 @@ +package main + +import clay "../../clay-odin" +import "core:c" +import "core:fmt" +import "vendor:raylib" + +windowWidth: i32 = 1024 +windowHeight: i32 = 768 + +syntaxImage: raylib.Texture2D = {} +checkImage1: raylib.Texture2D = {} +checkImage2: raylib.Texture2D = {} +checkImage3: raylib.Texture2D = {} +checkImage4: raylib.Texture2D = {} +checkImage5: raylib.Texture2D = {} + +FONT_ID_TITLE_56 :: 0 +FONT_ID_TITLE_52 :: 1 +FONT_ID_TITLE_48 :: 2 +FONT_ID_TITLE_36 :: 3 +FONT_ID_TITLE_32 :: 4 +FONT_ID_BODY_36 :: 5 +FONT_ID_BODY_30 :: 6 +FONT_ID_BODY_28 :: 7 +FONT_ID_BODY_24 :: 8 +FONT_ID_BODY_16 :: 9 + +COLOR_LIGHT :: clay.Color{244, 235, 230, 255} +COLOR_LIGHT_HOVER :: clay.Color{224, 215, 210, 255} +COLOR_BUTTON_HOVER :: clay.Color{238, 227, 225, 255} +COLOR_BROWN :: clay.Color{61, 26, 5, 255} +//COLOR_RED :: clay.Color {252, 67, 27, 255} +COLOR_RED :: clay.Color{168, 66, 28, 255} +COLOR_RED_HOVER :: clay.Color{148, 46, 8, 255} +COLOR_ORANGE :: clay.Color{225, 138, 50, 255} +COLOR_BLUE :: clay.Color{111, 173, 162, 255} +COLOR_TEAL :: clay.Color{111, 173, 162, 255} +COLOR_BLUE_DARK :: clay.Color{2, 32, 82, 255} + +// Colors for top stripe +COLOR_TOP_BORDER_1 :: clay.Color{168, 66, 28, 255} +COLOR_TOP_BORDER_2 :: clay.Color{223, 110, 44, 255} +COLOR_TOP_BORDER_3 :: clay.Color{225, 138, 50, 255} +COLOR_TOP_BORDER_4 :: clay.Color{236, 189, 80, 255} +COLOR_TOP_BORDER_5 :: clay.Color{240, 213, 137, 255} + +COLOR_BLOB_BORDER_1 :: clay.Color{168, 66, 28, 255} +COLOR_BLOB_BORDER_2 :: clay.Color{203, 100, 44, 255} +COLOR_BLOB_BORDER_3 :: clay.Color{225, 138, 50, 255} +COLOR_BLOB_BORDER_4 :: clay.Color{236, 159, 70, 255} +COLOR_BLOB_BORDER_5 :: clay.Color{240, 189, 100, 255} + +headerTextConfig := clay.TextElementConfig { + fontId = FONT_ID_BODY_24, + fontSize = 24, + textColor = {61, 26, 5, 255}, +} + +LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, text: clay.String, image: ^raylib.Texture2D) { + if clay.Border( + clay.IDI("HeroBlob", index), + clay.Layout({sizing = {width = clay.SizingGrow({max = 480})}, padding = clay.Padding{16, 16}, childGap = 16, childAlignment = clay.ChildAlignment{y = .CENTER}}), + clay.BorderConfigOutsideRadius({2, color}, 10), + ) { + if clay.Image( + clay.IDI("CheckImage", index), + clay.Layout({sizing = {width = clay.SizingFixed(32)}}), + clay.ImageConfig({imageData = image, sourceDimensions = {128, 128}}), + ) {} + clay.Text(clay.IDI("HeroBlobText", index), text, clay.TextConfig({fontSize = fontSize, fontId = fontId, textColor = color})) + } +} + +LandingPageDesktop :: proc() { + if clay.Container( + clay.ID("LandingPage1Desktop"), + clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFit({min = cast(f32)windowHeight - 70})}, childAlignment = {y = .CENTER}, padding = {x = 50}}), + ) { + if clay.Border( + clay.ID("LandingPage1"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {32, 32}, childGap = 32}), + clay.BorderConfig({left = {2, COLOR_RED}, right = {2, COLOR_RED}}), + ) { + if clay.Container(clay.ID("LeftText"), clay.Layout({sizing = {width = clay.SizingPercent(0.55)}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) { + clay.Text( + clay.ID("LeftTextTitle"), + clay.MakeString("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), + clay.TextConfig({fontSize = 56, fontId = FONT_ID_TITLE_56, textColor = COLOR_RED}), + ) + if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {} + clay.Text( + clay.ID("LeftTextTagline"), + clay.MakeString("Clay is laying out this webpage right now!"), + clay.TextConfig({fontSize = 36, fontId = FONT_ID_TITLE_36, textColor = COLOR_ORANGE}), + ) + } + if clay.Container( + clay.ID("HeroImageOuter"), + clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingPercent(0.45)}, childAlignment = {x = .CENTER}, childGap = 16}), + ) { + LandingPageBlob(1, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_5, clay.MakeString("High performance"), &checkImage5) + LandingPageBlob(2, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_4, clay.MakeString("Flexbox-style responsive layout"), &checkImage4) + LandingPageBlob(3, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_3, clay.MakeString("Declarative syntax"), &checkImage3) + LandingPageBlob(4, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_2, clay.MakeString("Single .h file for C/C++"), &checkImage2) + LandingPageBlob(5, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_1, clay.MakeString("Compile to 15kb .wasm"), &checkImage1) + } + } + } +} + +LandingPageMobile :: proc() { + if clay.Container( + clay.ID("LandingPage1Mobile"), + clay.Layout( + { + layoutDirection = .TOP_TO_BOTTOM, + sizing = {width = clay.SizingGrow({}), height = clay.SizingFit({min = cast(f32)windowHeight - 70})}, + childAlignment = {x = .CENTER, y = .CENTER}, + padding = {16, 32}, + childGap = 32, + }, + ), + ) { + if clay.Container(clay.ID("LeftText"), clay.Layout({sizing = {width = clay.SizingGrow({})}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) { + clay.Text( + clay.ID("LeftTextTitle"), + clay.MakeString("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), + clay.TextConfig({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}), + ) + if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {} + clay.Text( + clay.ID("LeftTextTagline"), + clay.MakeString("Clay is laying out this webpage right now!"), + clay.TextConfig({fontSize = 32, fontId = FONT_ID_TITLE_32, textColor = COLOR_ORANGE}), + ) + } + if clay.Container( + clay.ID("HeroImageOuter"), + clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingGrow({})}, childAlignment = {x = .CENTER}, childGap = 16}), + ) { + LandingPageBlob(1, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_5, clay.MakeString("High performance"), &checkImage5) + LandingPageBlob(2, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_4, clay.MakeString("Flexbox-style responsive layout"), &checkImage4) + LandingPageBlob(3, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_3, clay.MakeString("Declarative syntax"), &checkImage3) + LandingPageBlob(4, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_2, clay.MakeString("Single .h file for C/C++"), &checkImage2) + LandingPageBlob(5, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_1, clay.MakeString("Compile to 15kb .wasm"), &checkImage1) + } + } +} + +FeatureBlocks :: proc(widthSizing: clay.SizingAxis, outerPadding: u16) { + textConfig := clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_RED}) + if clay.Container( + clay.ID("HFileBoxOuter"), + clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = widthSizing}, childAlignment = {y = .CENTER}, padding = {outerPadding, 32}, childGap = 8}), + ) { + if clay.Rectangle(clay.ID("HFileIncludeOuter"), clay.Layout({padding = {8, 4}}), clay.RectangleConfig({color = COLOR_RED, cornerRadius = clay.CornerRadiusAll(8)})) { + clay.Text(clay.IDI("HFileBoxText", 2), clay.MakeString("#include clay.h"), clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_LIGHT})) + } + clay.Text(clay.ID("HFileSecondLine"), clay.MakeString("~2000 lines of C99."), textConfig) + clay.Text(clay.IDI("HFileBoxText", 5), clay.MakeString("Zero dependencies, including no C standard library."), textConfig) + } + if clay.Container( + clay.ID("BringYourOwnRendererOuter"), + clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = widthSizing}, childAlignment = {y = .CENTER}, padding = {x = outerPadding, y = 32}, childGap = 8}), + ) { + clay.Text(clay.IDI("ZeroDependenciesText", 1), clay.MakeString("Renderer agnostic."), clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = COLOR_ORANGE})) + clay.Text(clay.IDI("ZeroDependenciesText", 2), clay.MakeString("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig) + clay.Text(clay.IDI("ZeroDependenciesText", 3), clay.MakeString("Flexible output for easy compositing in your custom engine or environment."), textConfig) + } +} + +FeatureBlocksDesktop :: proc() { + if clay.Container(clay.ID("FeatureBlocksOuter"), clay.Layout({sizing = {width = clay.SizingGrow({})}})) { + if clay.Border( + clay.ID("FeatureBlocksInner"), + clay.Layout({sizing = {width = clay.SizingGrow({})}, childAlignment = {y = .CENTER}}), + clay.BorderConfig({betweenChildren = {width = 2, color = COLOR_RED}}), + ) { + FeatureBlocks(clay.SizingPercent(0.5), 50) + } + } +} + +FeatureBlocksMobile :: proc() { + if clay.Border( + clay.ID("FeatureBlocksInner"), + clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingGrow({})}}), + clay.BorderConfig({betweenChildren = {width = 2, color = COLOR_RED}}), + ) { + FeatureBlocks(clay.SizingGrow({}), 16) + } +} + +DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) { + if clay.Container(clay.ID("SyntaxPageLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) { + clay.Text(clay.ID("SyntaxPageTextTitle"), clay.MakeString("Declarative Syntax"), clay.TextConfig(titleTextConfig)) + if clay.Container(clay.ID("SyntaxSpacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {} + clay.Text( + clay.ID("SyntaxPageTextSubTitle1"), + clay.MakeString("Flexible and readable declarative syntax with nested UI element hierarchies."), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}), + ) + clay.Text( + clay.ID("SyntaxPageTextSubTitle2"), + clay.MakeString("Mix elements with standard C code like loops, conditionals and functions."), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}), + ) + clay.Text( + clay.ID("SyntaxPageTextSubTitle3"), + clay.MakeString("Create your own library of re-usable components from UI primitives like text, images and rectangles."), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}), + ) + } + if clay.Container(clay.ID("SyntaxPageRightImage"), clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}})) { + if clay.Image( + clay.ID("SyntaxPageRightImage"), + clay.Layout({sizing = {width = clay.SizingGrow({max = 568})}}), + clay.ImageConfig({imageData = &syntaxImage, sourceDimensions = {1136, 1194}}), + ) {} + } +} + +DeclarativeSyntaxPageDesktop :: proc() { + if clay.Container( + clay.ID("SyntaxPageDesktop"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, childAlignment = {y = .CENTER}, padding = {x = 50}}), + ) { + if clay.Border( + clay.ID("SyntaxPage"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {32, 32}, childGap = 32}), + clay.BorderConfig({left = {2, COLOR_RED}, right = {2, COLOR_RED}}), + ) { + DeclarativeSyntaxPage({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_LIGHT}, clay.SizingGrow({})) + } + } +} + +DeclarativeSyntaxPageMobile :: proc() { + if clay.Container( + clay.ID("SyntaxPageDesktop"), + clay.Layout( + { + layoutDirection = .TOP_TO_BOTTOM, + sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, + childAlignment = {x = .CENTER, y = .CENTER}, + padding = {16, 32}, + childGap = 16, + }, + ), + ) { + DeclarativeSyntaxPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_LIGHT}, clay.SizingGrow({})) + } +} + +ColorLerp :: proc(a: clay.Color, b: clay.Color, amount: f32) -> clay.Color { + return clay.Color{a.r + (b.r - a.r) * amount, a.g + (b.g - a.g) * amount, a.b + (b.b - a.b) * amount, a.a + (b.a - a.a) * amount} +} + +LOREM_IPSUM_TEXT: clay.String = clay.MakeString("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") + +HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) { + if clay.Container(clay.ID("PerformanceLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) { + clay.Text(clay.ID("PerformanceTextTitle"), clay.MakeString("High Performance"), clay.TextConfig(titleTextConfig)) + if clay.Container(clay.ID("SyntaxSpacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {} + clay.Text( + clay.IDI("PerformanceTextSubTitle", 1), + clay.MakeString("Fast enough to recompute your entire UI every frame."), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}), + ) + clay.Text( + clay.IDI("PerformanceTextSubTitle", 2), + clay.MakeString("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}), + ) + clay.Text( + clay.IDI("PerformanceTextSubTitle", 3), + clay.MakeString("Simplify animations and reactive UI design by avoiding the standard performance hacks."), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}), + ) + } + if clay.Container(clay.ID("PerformanceRightImageOuter"), clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}})) { + if clay.Border( + clay.ID("PerformanceRightBorder"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(400)}}), + clay.BorderConfigAll({width = 2, color = COLOR_LIGHT}), + ) { + if clay.Rectangle( + clay.ID("AnimationDemoContainerLeft"), + clay.Layout({sizing = {clay.SizingPercent(0.35 + 0.3 * lerpValue), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {16, 16}}), + clay.RectangleConfig({color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)}), + ) { + clay.Text(clay.ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT})) + } + if clay.Rectangle( + clay.ID("AnimationDemoContainerRight"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {16, 16}}), + clay.RectangleConfig({color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)}), + ) { + clay.Text(clay.ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT})) + } + } + } +} + +HighPerformancePageDesktop :: proc(lerpValue: f32) { + if clay.Rectangle( + clay.ID("PerformanceDesktop"), + clay.Layout( + {sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, childAlignment = {y = .CENTER}, padding = {x = 82, y = 32}, childGap = 64}, + ), + clay.RectangleConfig({color = COLOR_RED}), + ) { + HighPerformancePage(lerpValue, {fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_LIGHT}, clay.SizingPercent(0.5)) + } +} + +HighPerformancePageMobile :: proc(lerpValue: f32) { + if clay.Rectangle( + clay.ID("PerformanceMobile"), + clay.Layout( + { + layoutDirection = .TOP_TO_BOTTOM, + sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, + childAlignment = {x = .CENTER, y = .CENTER}, + padding = {x = 16, y = 32}, + childGap = 32, + }, + ), + clay.RectangleConfig({color = COLOR_RED}), + ) { + HighPerformancePage(lerpValue, {fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_LIGHT}, clay.SizingGrow({})) + } +} + +RendererButtonActive :: proc(id: u32, index: i32, text: clay.String) { + if clay.Rectangle( + id, + clay.Layout({sizing = {width = clay.SizingFixed(300)}, padding = {16, 16}}), + clay.RectangleConfig({color = clay.PointerOver(id) ? COLOR_RED_HOVER : COLOR_RED, cornerRadius = clay.CornerRadiusAll(10)}), + ) { + clay.Text(clay.ID("RendererButtonActiveText"), text, clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_LIGHT})) + } +} + +RendererButtonInactive :: proc(id: u32, index: u32, text: clay.String) { + if clay.Border(id, clay.Layout({}), clay.BorderConfigOutsideRadius({2, COLOR_RED}, 10)) { + if clay.Rectangle( + clay.IDI("RendererButtonInactiveInner", index), + clay.Layout({sizing = {width = clay.SizingFixed(300)}, padding = {16, 16}}), + clay.RectangleConfig({color = clay.PointerOver(id) ? COLOR_LIGHT_HOVER : COLOR_LIGHT, cornerRadius = clay.CornerRadiusAll(10)}), + ) { + clay.Text(clay.IDI("RendererButtonInactiveText", index), text, clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED})) + } + } +} + +RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) { + if clay.Container(clay.ID("RendererLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) { + clay.Text(clay.ID("RendererTextTitle"), clay.MakeString("Renderer & Platform Agnostic"), clay.TextConfig(titleTextConfig)) + if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {} + clay.Text( + clay.IDI("RendererTextSubTitle", 1), + clay.MakeString("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}), + ) + clay.Text( + clay.IDI("RendererTextSubTitle", 2), + clay.MakeString("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}), + ) + clay.Text( + clay.IDI("RendererTextSubTitle", 3), + clay.MakeString("There's even an HTML renderer - you're looking at it right now!"), + clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}), + ) + } + if clay.Container( + clay.ID("RendererRightText"), + clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}, layoutDirection = .TOP_TO_BOTTOM, childGap = 16}), + ) { + clay.Text( + clay.ID("RendererTextRightTitle"), + clay.MakeString("Try changing renderer!"), + clay.TextConfig({fontSize = 36, fontId = FONT_ID_BODY_36, textColor = COLOR_ORANGE}), + ) + if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 32})}})) {} + RendererButtonActive(clay.IDI("RendererSelectButtonActive", 0), 0, clay.MakeString("Raylib Renderer")) + } +} + +RendererPageDesktop :: proc() { + if clay.Container( + clay.ID("RendererPageDesktop"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, childAlignment = {y = .CENTER}, padding = {x = 50}}), + ) { + if clay.Border( + clay.ID("RendererPage"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {32, 32}, childGap = 32}), + clay.BorderConfig({left = {2, COLOR_RED}, right = {2, COLOR_RED}}), + ) { + RendererPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5)) + } + } +} + +RendererPageMobile :: proc() { + if clay.Rectangle( + clay.ID("RendererMobile"), + clay.Layout( + { + layoutDirection = .TOP_TO_BOTTOM, + sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, + childAlignment = {x = .CENTER, y = .CENTER}, + padding = {x = 16, y = 32}, + childGap = 32, + }, + ), + clay.RectangleConfig({color = COLOR_LIGHT}), + ) { + RendererPage({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}, clay.SizingGrow({})) + } +} + +ScrollbarData :: struct { + clickOrigin: clay.Vector2, + positionOrigin: clay.Vector2, + mouseDown: bool, +} + +scrollbarData: ScrollbarData = ScrollbarData{} +animationLerpValue: f32 = -1.0 + +createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) { + mobileScreen := windowWidth < 750 + clay.BeginLayout(windowWidth, windowHeight) + if clay.Rectangle( + clay.ID("OuterContainer"), + clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {clay.SizingGrow({}), clay.SizingGrow({})}}), + clay.RectangleConfig({color = COLOR_LIGHT}), + ) { + if clay.Container( + clay.ID("Header"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(50)}, childAlignment = {y = .CENTER}, childGap = 24, padding = {x = 32}}), + ) { + clay.Text(clay.ID("Logo"), clay.MakeString("Clay"), &headerTextConfig) + if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({})}})) {} + + if (!mobileScreen) { + if clay.Rectangle(clay.ID("LinkExamplesOuter"), clay.Layout({}), clay.RectangleConfig({color = {0, 0, 0, 0}})) { + clay.Text(clay.ID("LinkExamplesText"), clay.MakeString("Examples"), clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}})) + } + if clay.Rectangle(clay.ID("LinkDocsOuter"), clay.Layout({}), clay.RectangleConfig({color = {0, 0, 0, 0}})) { + clay.Text(clay.ID("LinkDocsText"), clay.MakeString("Docs"), clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}})) + } + } + githubButtonId: u32 = clay.ID("HeaderButtonGithub") + if clay.Border(clay.ID("LinkGithubOuter"), clay.Layout({}), clay.BorderConfigOutsideRadius({2, COLOR_RED}, 10)) { + if clay.Rectangle( + githubButtonId, + clay.Layout({padding = {16, 6}}), + clay.RectangleConfig({cornerRadius = clay.CornerRadiusAll(10), color = clay.PointerOver(githubButtonId) ? COLOR_LIGHT_HOVER : COLOR_LIGHT}), + ) { + clay.Text(clay.ID("LinkGithubText"), clay.MakeString("Github"), clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}})) + } + } + } + if clay.Rectangle(clay.ID("TopBorder1"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_5})) {} + if clay.Rectangle(clay.ID("TopBorder2"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_4})) {} + if clay.Rectangle(clay.ID("TopBorder3"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_3})) {} + if clay.Rectangle(clay.ID("TopBorder4"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_2})) {} + if clay.Rectangle(clay.ID("TopBorder5"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_1})) {} + if clay.Rectangle( + clay.ID("ScrollContainerBackgroundRectangle"), + clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}}), + clay.RectangleConfig({color = COLOR_LIGHT}), + ) { + if clay.Scroll(clay.ID("OuterScrollContainer"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}}), clay.ScrollConfig({vertical = true})) { + if clay.Border( + clay.ID("ScrollContainerInner"), + clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingGrow({})}}), + clay.BorderConfig({betweenChildren = {2, COLOR_RED}}), + ) { + if (!mobileScreen) { + LandingPageDesktop() + FeatureBlocksDesktop() + DeclarativeSyntaxPageDesktop() + HighPerformancePageDesktop(lerpValue) + RendererPageDesktop() + } else { + LandingPageMobile() + FeatureBlocksMobile() + DeclarativeSyntaxPageMobile() + HighPerformancePageMobile(lerpValue) + RendererPageMobile() + } + } + } + } + } + return clay.EndLayout(windowWidth, windowHeight) +} + +loadFont :: proc(fontId: u16, fontSize: u16, path: cstring) { + raylibFonts[fontId] = RaylibFont { + font = raylib.LoadFontEx(path, cast(i32)fontSize * 2, nil, 0), + fontId = cast(u16)fontId, + } + raylib.SetTextureFilter(raylibFonts[fontId].font.texture, raylib.TextureFilter.TRILINEAR) +} + +main :: proc() { + minMemorySize: c.uint32_t = clay.MinMemorySize() + memory := make([^]u8, minMemorySize) + arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory) + clay.SetMeasureTextFunction(measureText) + clay.Initialize(arena) + + raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT}) + raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example") + raylib.SetTargetFPS(raylib.GetMonitorRefreshRate(0)) + loadFont(FONT_ID_TITLE_56, 56, "resources/Calistoga-Regular.ttf") + loadFont(FONT_ID_TITLE_52, 52, "resources/Calistoga-Regular.ttf") + loadFont(FONT_ID_TITLE_48, 48, "resources/Calistoga-Regular.ttf") + loadFont(FONT_ID_TITLE_36, 36, "resources/Calistoga-Regular.ttf") + loadFont(FONT_ID_TITLE_32, 32, "resources/Calistoga-Regular.ttf") + loadFont(FONT_ID_BODY_36, 36, "resources/Quicksand-Semibold.ttf") + loadFont(FONT_ID_BODY_30, 30, "resources/Quicksand-Semibold.ttf") + loadFont(FONT_ID_BODY_28, 28, "resources/Quicksand-Semibold.ttf") + loadFont(FONT_ID_BODY_24, 24, "resources/Quicksand-Semibold.ttf") + loadFont(FONT_ID_BODY_16, 16, "resources/Quicksand-Semibold.ttf") + + syntaxImage = raylib.LoadTextureFromImage(raylib.LoadImage("resources/declarative.png")) + checkImage1 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_1.png")) + checkImage2 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_2.png")) + checkImage3 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_3.png")) + checkImage4 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_4.png")) + checkImage5 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_5.png")) + + for !raylib.WindowShouldClose() { + animationLerpValue += raylib.GetFrameTime() + if animationLerpValue > 1 { + animationLerpValue = animationLerpValue - 2 + } + windowWidth = raylib.GetScreenWidth() + windowHeight = raylib.GetScreenHeight() + clay.SetPointerPosition(transmute(clay.Vector2)raylib.GetMousePosition()) + clay.UpdateScrollContainers(false, transmute(clay.Vector2)raylib.GetMouseWheelMoveV(), raylib.GetFrameTime()) + renderCommands: clay.ClayArray(clay.RenderCommand) = createLayout(animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue)) + raylib.BeginDrawing() + clayRaylibRender(&renderCommands) + raylib.EndDrawing() + } +} diff --git a/bindings/odin/examples/clay-official-website/clay_renderer_raylib.odin b/bindings/odin/examples/clay-official-website/clay_renderer_raylib.odin new file mode 100644 index 0000000..257b8ca --- /dev/null +++ b/bindings/odin/examples/clay-official-website/clay_renderer_raylib.odin @@ -0,0 +1,215 @@ +package main + +import clay "../../clay-odin" +import "core:math" +import "vendor:raylib" + +RaylibFont :: struct { + fontId: u16, + font: raylib.Font, +} + +clayColorToRaylibColor :: proc(color: clay.Color) -> raylib.Color { + return raylib.Color{cast(u8)color.r, cast(u8)color.g, cast(u8)color.b, cast(u8)color.a} +} + +raylibFonts := [10]RaylibFont{} + +measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions { + // Measure string size for Font + textSize: clay.Dimensions = {0, 0} + + maxTextWidth: f32 = 0 + lineTextWidth: f32 = 0 + + textHeight: f32 = cast(f32)config.fontSize + fontToUse := raylibFonts[config.fontId].font + + for i := 0; i < cast(int)text.length; i += 1 { + if (text.chars[i] == '\n') { + maxTextWidth = math.max(maxTextWidth, lineTextWidth) + lineTextWidth = 0 + continue + } + index: i32 = cast(i32)text.chars[i] - 32 + if (fontToUse.glyphs[index].advanceX != 0) { + lineTextWidth += cast(f32)fontToUse.glyphs[index].advanceX + } else { + lineTextWidth += (fontToUse.recs[index].width + cast(f32)fontToUse.glyphs[index].offsetX) + } + } + + maxTextWidth = math.max(maxTextWidth, lineTextWidth) + + textSize.width = maxTextWidth / 2 + textSize.height = textHeight + + return textSize +} + +clayRaylibRender :: proc(renderCommands: ^clay.ClayArray(clay.RenderCommand)) { + for i := 0; i < cast(int)renderCommands.length; i += 1 { + renderCommand := clay.RenderCommandArray_Get(renderCommands, cast(i32)i) + boundingBox := renderCommand.boundingBox + switch (renderCommand.commandType) + { + case clay.RenderCommandType.None: + {} + case clay.RenderCommandType.Text: + { + // Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator + text := renderCommand.text + cloned := make([]u8, text.length + 1, context.temp_allocator) + copy(cloned[0:text.length], text.chars[0:text.length]) + cloned[text.length] = 0 + fontToUse: raylib.Font = raylibFonts[renderCommand.config.textElementConfig.fontId].font + raylib.DrawTextEx( + fontToUse, + cstring(raw_data(cloned)), + raylib.Vector2{boundingBox.x, boundingBox.y}, + cast(f32)renderCommand.config.textElementConfig.fontSize, + cast(f32)renderCommand.config.textElementConfig.letterSpacing, + clayColorToRaylibColor(renderCommand.config.textElementConfig.textColor), + ) + } + case clay.RenderCommandType.Image: + { + // TODO image handling + imageTexture := cast(^raylib.Texture2D)renderCommand.config.imageElementConfig.imageData + raylib.DrawTextureEx(imageTexture^, raylib.Vector2{boundingBox.x, boundingBox.y}, 0, boundingBox.width / cast(f32)imageTexture.width, raylib.WHITE) + } + case clay.RenderCommandType.ScissorStart: + { + raylib.BeginScissorMode( + cast(i32)math.round(boundingBox.x), + cast(i32)math.round(boundingBox.y), + cast(i32)math.round(boundingBox.width), + cast(i32)math.round(boundingBox.height), + ) + } + case clay.RenderCommandType.ScissorEnd: + { + raylib.EndScissorMode() + } + case clay.RenderCommandType.Rectangle: + { + config: ^clay.RectangleElementConfig = renderCommand.config.rectangleElementConfig + if (config.cornerRadius.topLeft > 0) { + radius: f32 = (config.cornerRadius.topLeft * 2) / (boundingBox.width > boundingBox.height ? boundingBox.height : boundingBox.width) + raylib.DrawRectangleRounded( + raylib.Rectangle{boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height}, + radius, + 8, + clayColorToRaylibColor(config.color), + ) + } else { + raylib.DrawRectangle( + cast(i32)boundingBox.x, + cast(i32)boundingBox.y, + cast(i32)boundingBox.width, + cast(i32)boundingBox.height, + clayColorToRaylibColor(config.color), + ) + } + } + case clay.RenderCommandType.Border: + { + config := renderCommand.config.borderElementConfig + // Left border + if (config.left.width > 0) { + raylib.DrawRectangle( + cast(i32)math.round(boundingBox.x), + cast(i32)math.round(boundingBox.y + config.cornerRadius.topLeft), + cast(i32)config.left.width, + cast(i32)math.round(boundingBox.height - config.cornerRadius.topLeft - config.cornerRadius.bottomLeft), + clayColorToRaylibColor(config.left.color), + ) + } + // Right border + if (config.right.width > 0) { + raylib.DrawRectangle( + cast(i32)math.round(boundingBox.x + boundingBox.width - cast(f32)config.right.width), + cast(i32)math.round(boundingBox.y + config.cornerRadius.topRight), + cast(i32)config.right.width, + cast(i32)math.round(boundingBox.height - config.cornerRadius.topRight - config.cornerRadius.bottomRight), + clayColorToRaylibColor(config.right.color), + ) + } + // Top border + if (config.top.width > 0) { + raylib.DrawRectangle( + cast(i32)math.round(boundingBox.x + config.cornerRadius.topLeft), + cast(i32)math.round(boundingBox.y), + cast(i32)math.round(boundingBox.width - config.cornerRadius.topLeft - config.cornerRadius.topRight), + cast(i32)config.top.width, + clayColorToRaylibColor(config.top.color), + ) + } + // Bottom border + if (config.bottom.width > 0) { + raylib.DrawRectangle( + cast(i32)math.round(boundingBox.x + config.cornerRadius.bottomLeft), + cast(i32)math.round(boundingBox.y + boundingBox.height - cast(f32)config.bottom.width), + cast(i32)math.round(boundingBox.width - config.cornerRadius.bottomLeft - config.cornerRadius.bottomRight), + cast(i32)config.bottom.width, + clayColorToRaylibColor(config.bottom.color), + ) + } + if (config.cornerRadius.topLeft > 0) { + raylib.DrawRing( + raylib.Vector2{math.round(boundingBox.x + config.cornerRadius.topLeft), math.round(boundingBox.y + config.cornerRadius.topLeft)}, + math.round(config.cornerRadius.topLeft - cast(f32)config.top.width), + config.cornerRadius.topLeft, + 180, + 270, + 10, + clayColorToRaylibColor(config.top.color), + ) + } + if (config.cornerRadius.topRight > 0) { + raylib.DrawRing( + raylib.Vector2{math.round(boundingBox.x + boundingBox.width - config.cornerRadius.topRight), math.round(boundingBox.y + config.cornerRadius.topRight)}, + math.round(config.cornerRadius.topRight - cast(f32)config.top.width), + config.cornerRadius.topRight, + 270, + 360, + 10, + clayColorToRaylibColor(config.top.color), + ) + } + if (config.cornerRadius.bottomLeft > 0) { + raylib.DrawRing( + raylib.Vector2 { + math.round(boundingBox.x + config.cornerRadius.bottomLeft), + math.round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomLeft), + }, + math.round(config.cornerRadius.bottomLeft - cast(f32)config.top.width), + config.cornerRadius.bottomLeft, + 90, + 180, + 10, + clayColorToRaylibColor(config.bottom.color), + ) + } + if (config.cornerRadius.bottomRight > 0) { + raylib.DrawRing( + raylib.Vector2 { + math.round(boundingBox.x + boundingBox.width - config.cornerRadius.bottomRight), + math.round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomRight), + }, + math.round(config.cornerRadius.bottomRight - cast(f32)config.bottom.width), + config.cornerRadius.bottomRight, + 0.1, + 90, + 10, + clayColorToRaylibColor(config.bottom.color), + ) + } + } + case clay.RenderCommandType.Custom: + { + // Implement custom element rendering here + } + } + } +} diff --git a/bindings/odin/examples/clay-official-website/resources/Calistoga-Regular.ttf b/bindings/odin/examples/clay-official-website/resources/Calistoga-Regular.ttf new file mode 100644 index 0000000..3fc1c1e Binary files /dev/null and b/bindings/odin/examples/clay-official-website/resources/Calistoga-Regular.ttf differ diff --git a/bindings/odin/examples/clay-official-website/resources/Quicksand-Semibold.ttf b/bindings/odin/examples/clay-official-website/resources/Quicksand-Semibold.ttf new file mode 100644 index 0000000..27106d0 Binary files /dev/null and b/bindings/odin/examples/clay-official-website/resources/Quicksand-Semibold.ttf differ diff --git a/bindings/odin/examples/clay-official-website/resources/check_1.png b/bindings/odin/examples/clay-official-website/resources/check_1.png new file mode 100644 index 0000000..280fd23 Binary files /dev/null and b/bindings/odin/examples/clay-official-website/resources/check_1.png differ diff --git a/bindings/odin/examples/clay-official-website/resources/check_2.png b/bindings/odin/examples/clay-official-website/resources/check_2.png new file mode 100644 index 0000000..7972581 Binary files /dev/null and b/bindings/odin/examples/clay-official-website/resources/check_2.png differ diff --git a/bindings/odin/examples/clay-official-website/resources/check_3.png b/bindings/odin/examples/clay-official-website/resources/check_3.png new file mode 100644 index 0000000..fb60187 Binary files /dev/null and b/bindings/odin/examples/clay-official-website/resources/check_3.png differ diff --git a/bindings/odin/examples/clay-official-website/resources/check_4.png b/bindings/odin/examples/clay-official-website/resources/check_4.png new file mode 100644 index 0000000..a938f81 Binary files /dev/null and b/bindings/odin/examples/clay-official-website/resources/check_4.png differ diff --git a/bindings/odin/examples/clay-official-website/resources/check_5.png b/bindings/odin/examples/clay-official-website/resources/check_5.png new file mode 100644 index 0000000..ea6cfca Binary files /dev/null and b/bindings/odin/examples/clay-official-website/resources/check_5.png differ diff --git a/bindings/odin/examples/clay-official-website/resources/declarative.png b/bindings/odin/examples/clay-official-website/resources/declarative.png new file mode 100644 index 0000000..5cd4abb Binary files /dev/null and b/bindings/odin/examples/clay-official-website/resources/declarative.png differ diff --git a/bindings/odin/odinfmt.json b/bindings/odin/odinfmt.json new file mode 100644 index 0000000..ee3d563 --- /dev/null +++ b/bindings/odin/odinfmt.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/odinfmt.schema.json", + "character_width": 180, + "sort_imports": true, + "tabs": false +} \ No newline at end of file diff --git a/bindings/odin/ols.json b/bindings/odin/ols.json new file mode 100644 index 0000000..90e2150 --- /dev/null +++ b/bindings/odin/ols.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json", + "enable_document_symbols": true, + "enable_hover": true, + "enable_snippets": true +} \ No newline at end of file