commit 20540c69b12ab52988fbd9615f6067109088eceb Author: Synthasmagoria Date: Mon Jul 27 11:17:23 2026 +0200 Initial commit diff --git a/example/main.odin b/example/main.odin new file mode 100644 index 0000000..66f888c --- /dev/null +++ b/example/main.odin @@ -0,0 +1,51 @@ +package main + +import rl "vendor:raylib" +import ui ".." +import "core:fmt" + +WINDOW_WIDTH :: 1280 +WINDOW_HEIGHT :: 720 +WINDOW_CAPTION :: "Immediate mode GUI" +WINDOW_FRAMERATE :: 60 + +main :: proc() { + rl.SetTraceLogLevel(.WARNING) + rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION) + defer rl.CloseWindow() + rl.SetTargetFPS(WINDOW_FRAMERATE) + + ctx: ui.Context + ui.init(&ctx, 4000) + + for !rl.WindowShouldClose() { + ui.start(&ctx, {0, 0}) + ui.container_begin( + dimensions = {ui.Size_Pixels(WINDOW_WIDTH), ui.Size_Pixels(WINDOW_HEIGHT)}, + margin = {4.0, 8.0}, + padding = {12.0, 24.0}, + color = cast([4]u8)rl.RED, + label = "", + ) + ui.container_end() + elements := ui.end() + + rl.BeginDrawing() + defer rl.EndDrawing() + rl.ClearBackground(rl.BLACK) + + for element in elements { + switch data in element.data { + case ui.ElementData_Container: + area := rl.Rectangle(element.area) + fmt.println(area) + if len(element.label) == 0 { + rl.DrawRectangleRoundedLinesEx(area, 0.1, 4.0, 1.0, rl.Color(element.color)) + } else { + rl.GuiLabel(area, element.label) + } + case ui.ElementData_Button: + } + } + } +} diff --git a/ui.odin b/ui.odin new file mode 100644 index 0000000..1dc42b2 --- /dev/null +++ b/ui.odin @@ -0,0 +1,137 @@ +package synthas_ui + +v2 :: [2]f32 +rect2 :: struct {x, y, width, height: f32} + +@private rect_get_bl :: proc(rect: rect2) -> v2 { + return {rect.x, rect.y + rect.height} +} + +@private ctx: ^Context + +Context :: struct { + container_position, element_position: v2, + container_stack: [dynamic]^Element, + elements: [dynamic]Element, + same_line: bool, +} + +init :: proc(state: ^Context, element_capacity: int, allocator := context.allocator) { + state.container_stack = make(type_of(state.container_stack), 0, 8, allocator) + state.elements = make(type_of(state.elements), 0, element_capacity, allocator) +} + +Element :: struct { + area: rect2, + label: cstring, + color: [4]u8, + data: ElementData, +} + +ElementData :: union { + ElementData_Container, + ElementData_Button, +} + +Size_Pixels :: distinct f32 +Size_Percentage :: distinct f32 +Size_Flex :: distinct bool + +Size :: union { + Size_Pixels, + Size_Percentage, + Size_Flex, +} + +Dimensions :: struct { + width, height: Size, +} + +Axis :: enum {X, Y} + +start :: proc(ui_context: ^Context, position: v2) { + ctx = ui_context + ctx.container_position = position + ctx.element_position = position + clear(&ctx.container_stack) + clear(&ctx.elements) +} + +end :: proc() -> []Element { + elements := ctx.elements[:] + ctx = nil + return elements +} + +same_line :: proc() { + ctx.same_line = true +} + +ElementData_Container :: struct { + margin, padding: v2, +} + +container_begin :: proc(dimensions: Dimensions, margin: v2, padding: v2, label: cstring, color: [4]u8) { + ctx.container_position += margin + area := rect2{**ctx.container_position, **_resolve_dimensions(dimensions)} + ctx.element_position = ctx.container_position + padding + data := ElementData_Container {margin = margin, padding = padding} + element := Element{area = area, color = color, label = label, data = data} + append(&ctx.elements, element) + container := _element_last() + append(&ctx.container_stack, container) +} + +container_end :: proc() { + container := pop(&ctx.container_stack) + // TODO: Set flex + data := container.data.(ElementData_Container) + ctx.container_position = rect_get_bl(container.area) + data.margin.y + ctx.element_position = ctx.container_position +} + +ElementData_Button :: struct { +} + +button :: proc(dimensions: Dimensions) { + area := _context_advance_area(dimensions) +} + +_context_advance_area :: proc(dimensions: Dimensions) -> rect2 { + return {} +} + +_resolve_dimensions :: proc(dimensions: Dimensions) -> v2 { + return {_resolve_size(dimensions.width, .X), _resolve_size(dimensions.height, .Y)} +} + +_resolve_size :: proc(size: Size, axis: Axis) -> f32 { + switch value in size { + case Size_Pixels: + return cast(f32)value + case Size_Percentage: + if container := _container_last(); container != nil { + container_size := container.area.width if axis == .X else container.area.height + return container_size * cast(f32)value + } else { + panic("Cannot use dimension Size_Percentage without an outer container") + } + case Size_Flex: + return 0.0 + } + unreachable() +} + +_container_last :: proc() -> ^Element { + if len(ctx.container_stack) == 0 { + return nil + } + return ctx.container_stack[len(ctx.container_stack) - 1] +} + +_element_last :: proc() -> ^Element { + if len(ctx.elements) == 0 { + return nil + } + return &ctx.elements[len(ctx.elements) - 1] +}