Field pool

This commit is contained in:
Synthasmagoria 2026-07-23 21:31:28 +02:00
commit ff32ac6227
15 changed files with 121 additions and 6001 deletions

3
.gitignore vendored
View file

@ -1 +1,4 @@
build
cscope.out
cscope.in.out
cscope.po.out

View file

@ -1,22 +0,0 @@
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.

View file

@ -1,203 +0,0 @@
### 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.
Special thanks to
- [laytan](https://github.com/laytan)
- [Dudejoe870](https://github.com/Dudejoe870)
- MrStevns from the Odin Discord server
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):
```C
// C form of element macros
// Define an element with 16px of x and y padding
CLAY({ .id = CLAY_ID("Outer"), .layout = { .padding = CLAY_PADDING_ALL(16) } }) {
// Child elements here
}
```
```Odin
// Odin form of element macros
if clay.UI(clay.ID("Outer"))({ layout = { padding = clay.PaddingAll(16) }}) {
// Child elements here
}
```
### Quick Start
1. Download the [clay-odin](https://github.com/nicbarker/clay/tree/main/bindings/odin/clay-odin) 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(clay.Arena, clay.Dimensions, clay.ErrorHandler)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize).
```Odin
error_handler :: proc "c" (errorData: clay.ErrorData) {
// Do something with the error data.
}
min_memory_size := clay.MinMemorySize()
memory := make([^]u8, min_memory_size)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(uint(min_memory_size), memory)
clay.Initialize(arena, {1080, 720}, { handler = error_handler })
```
3. Provide a `measure_text(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
measure_text :: proc "c" (
text: clay.StringSlice,
config: ^clay.TextElementConfig,
userData: rawptr,
) -> clay.Dimensions {
// clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing, etc..
// Note: clay.String->chars is not guaranteed to be null terminated
return {
width = f32(text.length * i32(config.fontSize)),
height = f32(config.fontSize),
}
}
// Tell clay how to measure text
clay.SetMeasureTextFunction(measure_text, nil)
```
4. **Optional** - Call [clay.SetPointerState(pointerPosition, isPointerDown)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerstate) if you want to use mouse interactions.
```Odin
// Update internal pointer position for handling mouseover / click / touch events
clay.SetPointerState(
{ mouse_pos_x, mouse_pos_y },
is_mouse_down,
)
```
5. Call [clay.BeginLayout()](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros.
```Odin
// Define some colors.
COLOR_LIGHT :: clay.Color{224, 215, 210, 255}
COLOR_RED :: clay.Color{168, 66, 28, 255}
COLOR_ORANGE :: clay.Color{225, 138, 50, 255}
COLOR_BLACK :: clay.Color{0, 0, 0, 255}
// Layout config is just a struct that can be declared statically, or inline
sidebar_item_layout := clay.LayoutConfig {
sizing = {
width = clay.SizingGrow({}),
height = clay.SizingFixed(50)
},
}
// Re-useable components are just normal procs.
sidebar_item_component :: proc(index: u32) {
if clay.UI()({
id = clay.ID("SidebarBlob", index),
layout = sidebar_item_layout,
backgroundColor = COLOR_ORANGE,
}) {}
}
// An example function to create your layout tree
create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) {
// Begin constructing the layout.
clay.BeginLayout()
// 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.UI()({
id = clay.ID("OuterContainer"),
layout = {
sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
},
backgroundColor = { 250, 250, 255, 255 },
}) {
if clay.UI()({
id = clay.ID("SideBar"),
layout = {
layoutDirection = .TopToBottom,
sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
},
backgroundColor = COLOR_LIGHT,
}) {
if clay.UI()({
id = clay.ID("ProfilePictureOuter"),
layout = {
sizing = { width = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
childAlignment = { y = .Center },
},
backgroundColor = COLOR_RED,
cornerRadius = { 6, 6, 6, 6 },
}) {
if clay.UI()({
id = clay.ID("ProfilePicture"),
layout = {
sizing = { width = clay.SizingFixed(60), height = clay.SizingFixed(60) },
},
image = {
// How you define `profile_picture` depends on your renderer.
imageData = &profile_picture,
sourceDimensions = {
width = 60,
height = 60,
},
},
}) {}
clay.Text(
"Clay - UI Library",
clay.TextConfig({ textColor = COLOR_BLACK, fontSize = 16 }),
)
}
// Standard Odin code like loops, etc. work inside components.
// Here we render 5 sidebar items.
for i in u32(0)..<5 {
sidebar_item_component(i)
}
}
if clay.UI()({
id = clay.ID("MainContent"),
layout = {
sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) },
},
backgroundColor = COLOR_LIGHT,
}) {}
}
// Returns a list of render commands
return clay.EndLayout()
}
```
6. Call your layout proc and process the resulting [clay.ClayArray(clay.RenderCommand)](https://github.com/nicbarker/clay/blob/main/README.md#clay_rendercommandarray) in your choice of renderer.
```Odin
render_commands := create_layout()
for i in 0..<i32(render_commands.length) {
render_command := clay.RenderCommandArray_Get(render_commands, i)
switch render_command.commandType {
case .Rectangle:
DrawRectangle(render_command.boundingBox, render_command.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_ID` (C) -> `clay.ID` (Odin).

View file

@ -1,19 +0,0 @@
# Intel Mac
rm -f clay-odin/macos/clay.a
clang -c -o clay.o -ffreestanding -static -target x86_64-apple-darwin clay-odin/clay.c -fPIC -O3
ar r clay-odin/macos/clay.a clay.o;
# ARM Mac
rm -f clay-odin/macos-arm64/clay.a
clang -c -g -o clay.o -static clay-odin/clay.c -fPIC -O3
ar r clay-odin/macos-arm64/clay.a clay.o;
# x64 Windows
rm -f clay-odin/windows/clay.lib
clang -c -o clay-odin/windows/clay.lib -ffreestanding -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib -static -O3 clay-odin/clay.c;
# Linux
rm -f clay-odin/linux/clay.a
clang -c -o clay.o -ffreestanding -static -target x86_64-unknown-linux-gnu clay-odin/clay.c -fPIC -O3
ar r clay-odin/linux/clay.a clay.o;
# WASM
rm -f clay-odin/wasm/clay.o
clang -c -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static -O3 clay-odin/clay.c;
rm clay.o;

View file

@ -1,2 +0,0 @@
#define CLAY_IMPLEMENTATION
#include "clay.h"

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -1,601 +0,0 @@
package clay
import "core:c"
when ODIN_OS == .Windows {
foreign import Clay "clay-odin/windows/clay.lib"
} else when ODIN_OS == .Linux {
foreign import Clay "clay-odin/linux/clay.a"
} else when ODIN_OS == .Darwin {
when ODIN_ARCH == .arm64 {
foreign import Clay "clay-odin/macos-arm64/clay.a"
} else {
foreign import Clay "clay-odin/macos/clay.a"
}
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
foreign import Clay "clay-odin/wasm/clay.o"
}
String :: struct {
isStaticallyAllocated: c.bool,
length: c.int32_t,
chars: [^]c.char,
}
StringSlice :: struct {
length: c.int32_t,
chars: [^]c.char,
baseChars: [^]c.char,
}
Vector2 :: [2]c.float
Dimensions :: struct {
width: c.float,
height: c.float,
}
Arena :: struct {
nextAllocation: uintptr,
capacity: c.size_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,
}
ElementId :: struct {
id: u32,
offset: u32,
baseId: u32,
stringId: String,
}
ElementIdArray :: struct {
capacity: i32,
length: i32,
internalArray: [^]ElementId,
}
when ODIN_OS == .Windows {
EnumBackingType :: u32
} else {
EnumBackingType :: u8
}
RenderCommandType :: enum EnumBackingType {
None,
Rectangle,
Border,
Text,
Image,
ScissorStart,
ScissorEnd,
OverlayColorStart,
OverlayColorEnd,
Custom,
}
RectangleElementConfig :: struct {
color: Color,
}
TextWrapMode :: enum EnumBackingType {
Words,
Newlines,
None,
}
TextAlignment :: enum EnumBackingType {
Left,
Center,
Right,
}
TextElementConfig :: struct {
userData: rawptr,
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineHeight: u16,
wrapMode: TextWrapMode,
textAlignment: TextAlignment,
}
AspectRatioElementConfig :: struct {
aspectRatio: f32,
}
ImageElementConfig :: struct {
imageData: rawptr,
}
CustomElementConfig :: struct {
customData: rawptr,
}
BorderWidth :: struct {
left: u16,
right: u16,
top: u16,
bottom: u16,
betweenChildren: u16,
}
BorderElementConfig :: struct {
color: Color,
width: BorderWidth,
}
TransitionData :: struct {
boundingBox: BoundingBox,
backgroundColor: Color,
overlayColor: Color,
borderColor: Color,
borderWidth: BorderWidth,
}
TransitionState :: enum c.int {
Idle,
Entering,
Transitioning,
Exiting,
}
TransitionProperty :: enum c.int {
X,
Y,
Width,
Height,
BackgroundColor,
OverlayColor,
CornerRadius,
BorderColor,
BorderWidth,
}
TransitionPropertyFlags :: bit_set[TransitionProperty;c.int]
TransitionPropertyPosition :: TransitionPropertyFlags{.X, .Y}
TransitionPropertyDimensions :: TransitionPropertyFlags{.Width, .Height}
TransitionPropertyBoundingBox :: TransitionPropertyPosition + TransitionPropertyDimensions
TransitionPropertyBorder :: TransitionPropertyFlags{.BorderColor, .BorderWidth}
TransitionCallbackArguments :: struct {
transitionState: TransitionState,
initial: TransitionData,
current: ^TransitionData,
target: TransitionData,
elapsedTime: f32,
duration: f32,
properties: TransitionPropertyFlags,
}
TransitionEnterTriggerType :: enum EnumBackingType {
SkipOnFirstParentFrame,
TriggerOnFirstParentFrame,
}
TransitionExitTriggerType :: enum EnumBackingType {
SkipWhenParentExits,
TriggerWhenParentExits,
}
TransitionInteractionHandlingType :: enum EnumBackingType {
DisableInteractionsWhileTransitioningPosition,
AllowInteractionsWhileTransitioningPosition,
}
ExitTransitionSiblingOrdering :: enum EnumBackingType {
UnderneathSiblings,
NaturalOrder,
AboveSiblings,
}
TransitionElementConfig :: struct {
handler: proc "c" (args: TransitionCallbackArguments) -> bool,
duration: f32,
properties: TransitionPropertyFlags,
interactionHandling: TransitionInteractionHandlingType,
enter: struct {
setInitialState: proc "c" (initialState: TransitionData, properties: TransitionPropertyFlags) -> TransitionData,
trigger: TransitionEnterTriggerType,
},
exit: struct {
setFinalState: proc "c" (finalState: TransitionData, properties: TransitionPropertyFlags) -> TransitionData,
trigger: TransitionExitTriggerType,
siblingOrdering: ExitTransitionSiblingOrdering,
},
}
ClipElementConfig :: struct {
horizontal: bool, // clip overflowing elements on the "X" axis
vertical: bool, // clip overflowing elements on the "Y" axis
childOffset: Vector2, // offsets the [X,Y] positions of all child elements, primarily for scrolling containers
}
FloatingAttachPointType :: enum EnumBackingType {
LeftTop,
LeftCenter,
LeftBottom,
CenterTop,
CenterCenter,
CenterBottom,
RightTop,
RightCenter,
RightBottom,
}
FloatingAttachPoints :: struct {
element: FloatingAttachPointType,
parent: FloatingAttachPointType,
}
PointerCaptureMode :: enum EnumBackingType {
Capture,
Passthrough,
}
FloatingAttachToElement :: enum EnumBackingType {
None,
Parent,
ElementWithId,
Root,
}
FloatingClipToElement :: enum EnumBackingType {
None,
AttachedParent,
}
FloatingElementConfig :: struct {
offset: Vector2,
expand: Dimensions,
parentId: u32,
zIndex: i16,
attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement,
clipTo: FloatingClipToElement,
}
TextRenderData :: struct {
stringContents: StringSlice,
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineHeight: u16,
}
RectangleRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
}
ImageRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
imageData: rawptr,
}
CustomRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
customData: rawptr,
}
ClipRenderData :: struct {
horizontal: bool,
vertical: bool,
}
OverlayColorRenderData :: struct {
color: Color,
}
BorderRenderData :: struct {
color: Color,
cornerRadius: CornerRadius,
width: BorderWidth,
}
RenderCommandData :: struct #raw_union {
rectangle: RectangleRenderData,
text: TextRenderData,
image: ImageRenderData,
custom: CustomRenderData,
border: BorderRenderData,
clip: ClipRenderData,
overlayColor: OverlayColorRenderData,
}
RenderCommand :: struct {
boundingBox: BoundingBox,
renderData: RenderCommandData,
userData: rawptr,
id: u32,
zIndex: i16,
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: ClipElementConfig,
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
found: bool,
}
ElementData :: struct {
boundingBox: BoundingBox,
found: bool,
}
PointerDataInteractionState :: enum EnumBackingType {
PressedThisFrame,
Pressed,
ReleasedThisFrame,
Released,
}
PointerData :: struct {
position: Vector2,
state: PointerDataInteractionState,
}
SizingType :: enum EnumBackingType {
Fit,
Grow,
Percent,
Fixed,
}
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 {
left: u16,
right: u16,
top: u16,
bottom: u16,
}
LayoutDirection :: enum EnumBackingType {
LeftToRight,
TopToBottom,
}
LayoutAlignmentX :: enum EnumBackingType {
Left,
Right,
Center,
}
LayoutAlignmentY :: enum EnumBackingType {
Top,
Bottom,
Center,
}
ChildAlignment :: struct {
x: LayoutAlignmentX,
y: LayoutAlignmentY,
}
LayoutConfig :: struct {
sizing: Sizing,
padding: Padding,
childGap: u16,
childAlignment: ChildAlignment,
layoutDirection: LayoutDirection,
}
ClayArray :: struct($type: typeid) {
capacity: i32,
length: i32,
internalArray: [^]type,
}
ElementDeclaration :: struct {
layout: LayoutConfig,
backgroundColor: Color,
overlayColor: Color,
cornerRadius: CornerRadius,
aspectRatio: AspectRatioElementConfig,
image: ImageElementConfig,
floating: FloatingElementConfig,
custom: CustomElementConfig,
clip: ClipElementConfig,
border: BorderElementConfig,
transition: TransitionElementConfig,
userData: rawptr,
}
ErrorType :: enum EnumBackingType {
TextMeasurementFunctionNotProvided,
ArenaCapacityExceeded,
ElementsCapacityExceeded,
TextMeasurementCapacityExceeded,
DuplicateId,
FloatingContainerParentNotFound,
PercentageOver1,
InternalError,
UnbalancedOpenClose,
}
ErrorData :: struct {
errorType: ErrorType,
errorText: String,
userData: rawptr,
}
ErrorHandler :: struct {
handler: proc "c" (errorData: ErrorData),
userData: rawptr,
}
Context :: struct {} // opaque structure, only use as a pointer
@(link_prefix = "Clay_", default_calling_convention = "c")
foreign Clay {
_OpenElement :: proc() ---
_OpenElementWithId :: proc(id: ElementId) ---
_CloseElement :: proc() ---
MinMemorySize :: proc() -> u32 ---
CreateArenaWithCapacityAndMemory :: proc(capacity: c.size_t, offset: [^]u8) -> Arena ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
GetPointerState :: proc() -> PointerData ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) -> ^Context ---
GetCurrentContext :: proc() -> ^Context ---
SetCurrentContext :: proc(ctx: ^Context) ---
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
BeginLayout :: proc() ---
EndLayout :: proc(deltaTime: c.float) -> ClayArray(RenderCommand) ---
GetOpenElementId :: proc() -> u32 ---
GetElementId :: proc(id: String) -> ElementId ---
GetElementIdWithIndex :: proc(id: String, index: u32) -> ElementId ---
GetElementData :: proc(id: ElementId) -> ElementData ---
Hovered :: proc() -> bool ---
OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) ---
PointerOver :: proc(id: ElementId) -> bool ---
GetPointerOverIds :: proc() -> ElementIdArray ---
GetScrollOffset :: proc() -> Vector2 ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: rawptr) -> Dimensions, userData: rawptr) ---
SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32, userData: rawptr) -> Vector2, userData: rawptr) ---
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
SetDebugModeEnabled :: proc(enabled: bool) ---
IsDebugModeEnabled :: proc() -> bool ---
SetCullingEnabled :: proc(enabled: bool) ---
GetMaxElementCount :: proc() -> i32 ---
SetMaxElementCount :: proc(maxElementCount: i32) ---
GetMaxMeasureTextCacheWordCount :: proc() -> i32 ---
SetMaxMeasureTextCacheWordCount :: proc(maxMeasureTextCacheWordCount: i32) ---
ResetMeasureTextCache :: proc() ---
EaseOut :: proc(arguments: TransitionCallbackArguments) -> bool ---
}
@(link_prefix = "Clay_", default_calling_convention = "c", private)
foreign Clay {
_ConfigureOpenElement :: proc(config: ElementDeclaration) ---
_HashString :: proc(key: String, seed: u32) -> ElementId ---
_HashStringWithOffset :: proc(key: String, index: u32, seed: u32) -> ElementId ---
_OpenTextElement :: proc(text: String, textConfig: TextElementConfig) ---
}
ConfigureOpenElement :: proc(config: ElementDeclaration) -> bool {
_ConfigureOpenElement(config)
return true
}
@(deferred_none = _CloseElement)
UI_WithId :: proc(id: ElementId) -> proc(config: ElementDeclaration) -> bool {
_OpenElementWithId(id)
return ConfigureOpenElement
}
@(deferred_none = _CloseElement)
UI_AutoId :: proc() -> proc(config: ElementDeclaration) -> bool {
_OpenElement()
return ConfigureOpenElement
}
UI :: proc {
UI_WithId,
UI_AutoId,
}
Text :: proc {
TextStatic,
TextDynamic,
}
TextStatic :: proc($text: string, config: TextElementConfig) {
wrapped := MakeString(text)
wrapped.isStaticallyAllocated = true
_OpenTextElement(wrapped, config)
}
TextDynamic :: proc(text: string, config: TextElementConfig) {
_OpenTextElement(MakeString(text), config)
}
PaddingAll :: proc(allPadding: u16) -> Padding {
return {left = allPadding, right = allPadding, top = allPadding, bottom = allPadding}
}
BorderOutside :: proc(width: u16) -> BorderWidth {
return {width, width, width, width, 0}
}
BorderAll :: proc(width: u16) -> BorderWidth {
return {width, width, width, width, width}
}
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.Fixed, 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, index: u32 = 0) -> ElementId {
return _HashString(MakeString(label), index)
}
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashStringWithOffset(MakeString(label), index, GetOpenElementId())
}

41
libraries/pool/pool.odin Normal file
View file

@ -0,0 +1,41 @@
package adb_pool
Pool :: struct($T: typeid) {
pool: []T,
usage, capacity: int,
}
init :: proc(pool: ^Pool($T), capacity: int, allocator := context.allocator) {
pool.pool = make([]T, capacity, allocator)
pool.usage = 0
pool.capacity = capacity
}
elems_available :: proc(pool: ^Pool($T), capacity: int) -> bool {
return pool.usage + count <= pool.capacity
}
get :: proc(pool: ^Pool($T), count: int) -> []T {
elements := pool.pool[pool.usage:pool.usage + count]
pool.usage += count
return elements
}
usage :: proc(pool: Pool($T)) -> int {
return pool.usage
}
get_allocated_elems :: proc(pool: Pool($T)) -> []T {
return pool.pool[:pool.usage]
}
clear :: proc(pool: ^Pool($T)) {
pool.usage = 0
}
free_range :: proc(pool: ^Pool($T), from, count: int) {
assert(count > 0, "Cannot free zero or negative count")
to := from + count
assert(to < pool.usage, "Cannot free beyond usage")
mem.copy(&pool.pool[from], &pool.pool[to], (pool.usage - to) * size_of(T))
}

View file

@ -1,6 +1,7 @@
package main
import rl "libraries/raylib"
import fixed_pool "libraries/pool"
import "base:runtime"
import "core:math"
import "core:math/linalg"
@ -23,84 +24,47 @@ FontStyle :: struct {
Program :: struct {
ui_state: UiState,
matrices: [dynamic; MATRIX_FIELD_COUNT]f32,
matrix_fields: [dynamic; MATRIX_FIELD_COUNT][MATRIX_FIELD_BYTES]byte,
matrix_type: MatrixType,
matrix_field_pool: FieldPool(f32),
grid: Grid,
font_style: FontStyle,
}
Grid :: struct {
area: rect2,
inner_area: rect2,
zoom: f32,
area: rect2,
inner_area: rect2,
zoom: f32,
}
grid_matrix_to_world :: proc(area: rect2, inner_area: rect2) -> mat3 {
return (
matrix3_translate2(rect_get_tl(area)) *
linalg.matrix3_scale(v3{**(rect_get_size(inner_area) / rect_get_size(area)), 1.0}) *
matrix3_translate2(-rect_get_tl(inner_area)))
FIELD_BYTE_COUNT :: 8
FieldInput :: [FIELD_BYTE_COUNT]byte
FieldPool :: struct($T: typeid) {
values: fixed_pool.Pool(T),
inputs: fixed_pool.Pool(FieldInput),
}
grid_get_cell_size :: proc(area: v2, lines_min: f32) -> f32 {
max_area := math.max(area.x, area.y)
check := math.pow10(math.floor(math.log10(max_area)))
for {
if max_area / check >= lines_min {
return check
}
check_halved := check / 2.0
if max_area / check_halved >= lines_min {
return check_halved
}
check_quartered := check / 4.0
if max_area / check_quartered >= lines_min {
return check_quartered
}
check /= 10.0
}
field_pool_get :: proc(pool: ^FieldPool($T), count: int) -> (values: []T, inputs: []FieldInput) {
return fixed_pool.get(&pool.values, count), fixed_pool.get(&pool.inputs, count)
}
grid_get_inner_size_from_zoom :: proc(zoom_amount: f32) -> f32 {
return math.pow(2.0, zoom_amount)
field_pool_init :: proc(pool: ^FieldPool($T), capacity: int, allocator := context.allocator) {
fixed_pool.init(&pool.values, capacity, allocator)
fixed_pool.init(&pool.inputs, capacity, allocator)
}
draw_grid :: proc(grid: Grid, font_style: FontStyle) {
grid_to_world_matrix := grid_matrix_to_world(grid.area, grid.inner_area)
grid_cell_size := grid_get_cell_size(rect_get_size(grid.area), 5.0)
grid_cell := v2{grid_cell_size, grid_cell_size}
grid_subgrid_cell := grid_cell / 5.0
draw_grid_transformed(grid.area, {0.0, 0.0}, grid_cell / 5.0, grid_to_world_matrix, rl.DARKGRAY)
draw_grid_transformed_annotated(grid.area, {0.0, 0.0}, grid_cell, grid_to_world_matrix, rl.GRAY, font_style)
field_pool_free_range :: proc(pool: ^FieldPool($T), from: int, count: int) {
fixed_pool.free_range(&pool.values, from, count)
fixed_pool.free_range(&pool.inputs, from, count)
}
draw_grid_transformed :: proc(area: rect2, align: v2, step: v2, transform: mat3, color: rl.Color) {
grid_start := linalg.floor((v2{area.x, area.y} + align) / step) * step + step
grid_stop := rect_get_br(area)
for x := grid_start.x; x < grid_stop.x; x += step.x {
rl.DrawLineV(matrix3_transform_v2(transform, {x, area.y}), matrix3_transform_v2(transform, {x, grid_stop.y}), color)
}
for y := grid_start.y; y < grid_stop.y; y += step.y {
rl.DrawLineV(matrix3_transform_v2(transform, {area.x, y}), matrix3_transform_v2(transform, {grid_stop.x, y}), color)
}
Field :: struct($T: typeid) {
value: f32,
input: FieldInput,
}
draw_grid_transformed_annotated :: proc(area: rect2, align: v2, step: v2, transform: mat3, color: rl.Color, style: FontStyle, temp_allocator := context.temp_allocator) {
grid_outside := matrix3_transform_v2(transform, {area.x, area.y}) - 4.0
grid_start := linalg.floor((v2{area.x, area.y} + align) / step) * step + step
grid_stop := rect_get_br(area)
for x := grid_start.x; x < grid_stop.x; x += step.x {
p1 := matrix3_transform_v2(transform, {x, area.y})
p2 := matrix3_transform_v2(transform, {x, grid_stop.y})
rl.DrawLineV(p1, p2, color)
draw_text_aligned(fmt.ctprintf("%.2f", x), {p1.x, grid_outside.y}, style, .Middle, .Bottom, color)
}
for y := grid_start.y; y < grid_stop.y; y += step.y {
p1 := matrix3_transform_v2(transform, {area.x, y})
p2 := matrix3_transform_v2(transform, {grid_stop.x, y})
rl.DrawLineV(p1, p2, color)
draw_text_aligned(fmt.ctprintf("%.2f", y), {grid_outside.x, p1.y}, style, .Right, .Center, color)
}
field_pool_get_allocated_elems :: proc(pool: ^FieldPool($T)) -> #soa[]Field(T) {
values := fixed_pool.get_allocated_elems(pool.values)
fields := fixed_pool.get_allocated_elems(pool.inputs)
return soa_zip(value = values, input = fields)
}
draw_text :: proc(text: cstring, position: v2, style: FontStyle, color: rl.Color) {
@ -135,6 +99,23 @@ draw_text_aligned :: proc(text: cstring, position: v2, style: FontStyle, halign:
draw_text(text, position, style, color)
}
field_pool_add_mat2 :: proc(pool: ^FieldPool(f32), m: mat2) {
values, _ := field_pool_get(pool, 4)
m := m
floats := cast([^]f32)&m[0, 0]
#unroll for i in 0..<4 {values[i] = floats[i]}
}
program_add_mat2 :: proc(data: rawptr) {
data := cast(^UiButton_AddMat2CallbackData)data
field_pool_add_mat2(&data.program.matrix_field_pool, data.mat)
}
UiButton_AddMat2CallbackData :: struct {
program: ^Program,
mat: mat2,
}
measure_text :: proc(text: cstring, style: FontStyle) -> v2 {
return rl.MeasureTextEx(style.font, text, style.size, style.spacing)
}
@ -157,61 +138,62 @@ init :: proc() {
rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION)
rl.SetTargetFPS(WINDOW_FRAMERATE)
field_pool_init(&program.matrix_field_pool, 4096, context.allocator)
program.font_style = {
font = rl.GetFontDefault(),
size = 16.0,
spacing = 1.0
}
program.grid = {
area = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT},
inner_area = {-1, -1, 2, 2},
zoom = 1.0,
}
ui_style := UiStyle{
margin = 4.0,
padding = 4.0,
font_style = {
font = rl.GetFontDefault(),
size = 16.0,
spacing = 1.0
}
font_style = program.font_style,
}
ui_init(&program.ui_state, ui_style, {0, 0}, context.temp_allocator)
}
matrix_add :: proc() {
matrices := &program.matrices
append(matrices, 1)
append(matrices, 0)
append(matrices, 1)
append(matrices, 0)
fields := &program.matrix_fields
append(fields, [MATRIX_FIELD_BYTES]byte{})
append(fields, [MATRIX_FIELD_BYTES]byte{})
append(fields, [MATRIX_FIELD_BYTES]byte{})
append(fields, [MATRIX_FIELD_BYTES]byte{})
}
update :: proc() {
if rl.IsMouseButtonPressed(.LEFT) {
ui_element_focus_reset(&program.ui_state)
}
matrix_add_button_callback_data := UiButton_AddMat2CallbackData {
program = &program,
mat = mat2{0, 1, 2, 3},
}
ui_button_size_top_bar := v2{32.0, 32.0}
ui_start(&program.ui_state)
ui_container_begin({label = ""})
ui_button(ui_button_size_top_bar, {label = "#10#", callback = matrix_add})
ui_button(ui_button_size_top_bar, {label = "#10#", callback = program_add_mat2, callback_data = cast(rawptr)&matrix_add_button_callback_data})
ui_same_line()
ui_button(ui_button_size_top_bar, {label = "#11#"})
ui_same_line()
ui_button(ui_button_size_top_bar, {label = "#12#"})
ui_container_end()
ui_container_begin({label = "Matrices"})
if len(program.matrices) == 0 {
field_pool := &program.matrix_field_pool
if fixed_pool.usage(field_pool.values) == 0 {
ui_text({text = "Add matrices", color = rl.WHITE})
} else {
ui_input_size_matrix := v2{48.0, 32.0}
switch program.matrix_type {
case .Mat2:
mat := cast(^mat2)&program.matrices[0]
fields := &program.matrix_fields
ui_input(ui_input_size_matrix, {label = "", min = 0, max = 1000, input = fields[0][:]})
fields := field_pool_get_allocated_elems(field_pool)
matrix_count := len(fields) / 4
matrix_field_input_size := v2{48.0, 32.0}
for i in 0..<matrix_count {
ui_input(matrix_field_input_size, {label = "", min = 0, max = 1000, input = fields[i * 4 + 0].input[:]})
ui_same_line()
ui_input(ui_input_size_matrix, {label = "", min = 0, max = 1000, input = fields[1][:]})
ui_input(ui_input_size_matrix, {label = "", min = 0, max = 1000, input = fields[2][:]})
ui_input(matrix_field_input_size, {label = "", min = 0, max = 1000, input = fields[i * 4 + 1].input[:]})
ui_input(matrix_field_input_size, {label = "", min = 0, max = 1000, input = fields[i * 4 + 2].input[:]})
ui_same_line()
ui_input(ui_input_size_matrix, {label = "", min = 0, max = 1000, input = fields[3][:]})
case .Mat3, .Mat4:
ui_input(matrix_field_input_size, {label = "", min = 0, max = 1000, input = fields[i * 4 + 3].input[:]})
}
}
ui_container_end()
@ -221,14 +203,12 @@ update :: proc() {
defer rl.EndDrawing()
rl.ClearBackground(rl.BLACK)
for element in ui_elements {
switch element.type {
case .Button:
data := element.data.(UiElementData_Button)
if rl.GuiButton(element.area, data.label) && data.callback != nil {
data.callback()
data.callback(data.callback_data)
}
case .Input:
data := element.data.(UiElementData_Input)

View file

@ -73,11 +73,12 @@ ui_end :: proc() -> []UiElement {
return elements
}
UiElementCallback_Button :: proc()
UiElementCallback_Button :: proc(data: rawptr)
UiElementData_Button :: struct {
label: cstring,
callback: UiElementCallback_Button,
callback_data: rawptr,
}
ui_button :: proc(size: v2, data: UiElementData_Button) {