package examples import "core:math" import "core:os" import sdl "vendor:sdl3" import "../../draw" import "../../draw/tess" import "../../vendor/clay" import cyber "../cybersteel" PLEX_SANS_REGULAR: draw.Font_Id = max(draw.Font_Id) // Max so we crash if registration is forgotten hellope_shapes :: proc() { if !sdl.Init({.VIDEO}) do os.exit(1) window := sdl.CreateWindow("Hellope!", 500, 500, {.HIGH_PIXEL_DENSITY}) gpu := sdl.CreateGPUDevice(draw.PLATFORM_SHADER_FORMAT, true, nil) if !sdl.ClaimWindowForGPUDevice(gpu, window) do os.exit(1) if !draw.init(gpu, window) do os.exit(1) spin_angle: f32 = 0 for { defer free_all(context.temp_allocator) ev: sdl.Event for sdl.PollEvent(&ev) { if ev.type == .QUIT do return } spin_angle += 1 base_layer := draw.begin({width = 500, height = 500}) // Background draw.rectangle(base_layer, {0, 0, 500, 500}, draw.Color{40, 40, 40, 255}) // ----- Shapes without rotation (existing demo) ----- draw.rectangle( base_layer, {20, 20, 200, 120}, draw.Color{80, 120, 200, 255}, outline_color = draw.WHITE, outline_width = 2, radii = {top_right = 15, top_left = 5}, ) red_rect_raddi := draw.uniform_radii({240, 20, 240, 120}, 0.3) red_rect_raddi.bottom_left = 0 draw.rectangle(base_layer, {240, 20, 240, 120}, draw.Color{200, 80, 80, 255}, radii = red_rect_raddi) draw.rectangle( base_layer, {20, 160, 460, 60}, draw.Linear_Gradient{start_color = {255, 0, 0, 255}, end_color = {0, 0, 255, 255}, angle = 0}, ) // ----- Rotation demos ----- // Rectangle rotating around its center rect := draw.Rectangle{100, 320, 80, 50} draw.rectangle( base_layer, rect, draw.Color{100, 200, 100, 255}, outline_color = draw.WHITE, outline_width = 2, origin = draw.center_of(rect), rotation = spin_angle, feather_ppx = 1, ) // Rounded rectangle rotating around its center rrect := draw.Rectangle{230, 300, 100, 80} draw.rectangle( base_layer, rrect, draw.Color{200, 100, 200, 255}, radii = draw.uniform_radii(rrect, 0.4), origin = draw.center_of(rrect), rotation = spin_angle, ) // Ellipse rotating around its center (tilted ellipse) draw.ellipse(base_layer, {410, 340}, 50, 30, draw.Color{255, 200, 50, 255}, rotation = spin_angle) // Circle orbiting a point (moon orbiting planet) // Convention B: center = pivot point (planet), origin = offset from moon center to pivot. // Moon's visual center at rotation=0: planet_pos - origin = (100, 450) - (0, 40) = (100, 410). planet_pos := draw.Vec2{100, 450} draw.circle(base_layer, planet_pos, 8, draw.Color{200, 200, 200, 255}) // planet (stationary) draw.circle( base_layer, planet_pos, 5, draw.Color{100, 150, 255, 255}, origin = draw.Vec2{0, 40}, rotation = spin_angle, ) // moon orbiting // Sector (pie slice) rotating in place draw.ring( base_layer, draw.Vec2{250, 450}, 0, 30, draw.Color{100, 100, 220, 255}, start_angle = 0, end_angle = 270, rotation = spin_angle, ) // Triangle rotating around its center tv1 := draw.Vec2{350, 420} tv2 := draw.Vec2{420, 480} tv3 := draw.Vec2{340, 480} tess.triangle_aa( base_layer, tv1, tv2, tv3, {220, 180, 60, 255}, origin = draw.center_of(tv1, tv2, tv3), rotation = spin_angle, ) // Polygon rotating around its center (already had rotation; now with origin for orbit) draw.polygon( base_layer, {460, 450}, 6, 30, draw.Color{180, 100, 220, 255}, outline_color = draw.WHITE, outline_width = 2, rotation = spin_angle, ) draw.end(gpu, window) } } hellope_text :: proc() { HELLOPE_ID :: 1 ROTATING_SENTENCE_ID :: 2 MEASURED_ID :: 3 CORNER_SPIN_ID :: 4 if !sdl.Init({.VIDEO}) do os.exit(1) window := sdl.CreateWindow("Hellope!", 600, 600, {.HIGH_PIXEL_DENSITY}) gpu := sdl.CreateGPUDevice(draw.PLATFORM_SHADER_FORMAT, true, nil) if !sdl.ClaimWindowForGPUDevice(gpu, window) do os.exit(1) if !draw.init(gpu, window) do os.exit(1) PLEX_SANS_REGULAR = draw.register_font(cyber.SANS_REGULAR_RAW) FONT_SIZE :: u16(24) spin_angle: f32 = 0 for { defer free_all(context.temp_allocator) ev: sdl.Event for sdl.PollEvent(&ev) { if ev.type == .QUIT do return } spin_angle += 0.5 base_layer := draw.begin({width = 600, height = 600}) // ----- Text API demos ----- // Cached text with id — TTF_Text reused across frames (good for text-heavy apps) draw.text( base_layer, "Hellope!", {300, 80}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.WHITE, origin = draw.center_of("Hellope!", PLEX_SANS_REGULAR, FONT_SIZE), id = HELLOPE_ID, ) // Rotating sentence — verifies multi-word text rotation around center draw.text( base_layer, "Hellope World!", {300, 250}, PLEX_SANS_REGULAR, FONT_SIZE, color = {255, 200, 50, 255}, origin = draw.center_of("Hellope World!", PLEX_SANS_REGULAR, FONT_SIZE), rotation = spin_angle, id = ROTATING_SENTENCE_ID, ) // Uncached text (no id) — created and destroyed each frame, simplest usage draw.text(base_layer, "Top-left anchored", {20, 450}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.WHITE) // Measure text for manual layout size := draw.measure_text("Measured!", PLEX_SANS_REGULAR, FONT_SIZE) draw.rectangle(base_layer, {300 - size.x / 2, 380, size.x, size.y}, draw.Color{60, 60, 60, 200}) draw.text( base_layer, "Measured!", {300, 380}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.WHITE, origin = draw.top_of("Measured!", PLEX_SANS_REGULAR, FONT_SIZE), id = MEASURED_ID, ) // Rotating text anchored at top-left (no origin offset) — spins around top-left corner draw.text( base_layer, "Corner spin", {150, 530}, PLEX_SANS_REGULAR, FONT_SIZE, color = {100, 200, 255, 255}, rotation = spin_angle, id = CORNER_SPIN_ID, ) draw.end(gpu, window, draw.Color{127, 127, 127, 255}) } } hellope_clay :: proc() { if !sdl.Init({.VIDEO}) do os.exit(1) window := sdl.CreateWindow("Hellope!", 500, 500, {.HIGH_PIXEL_DENSITY}) gpu := sdl.CreateGPUDevice(draw.PLATFORM_SHADER_FORMAT, true, nil) if !sdl.ClaimWindowForGPUDevice(gpu, window) do os.exit(1) if !draw.init(gpu, window) do os.exit(1) PLEX_SANS_REGULAR = draw.register_font(cyber.SANS_REGULAR_RAW) text_config := clay.TextElementConfig { fontId = PLEX_SANS_REGULAR, fontSize = 36, textColor = {255, 255, 255, 255}, } for { defer free_all(context.temp_allocator) ev: sdl.Event for sdl.PollEvent(&ev) { if ev.type == .QUIT do return } base_layer := draw.begin({width = 500, height = 500}) clay.SetLayoutDimensions({width = base_layer.bounds.width, height = base_layer.bounds.height}) clay.BeginLayout() if clay.UI(clay.ID("outer"))( { layout = { sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {x = .Center, y = .Center}, }, backgroundColor = {127, 127, 127, 255}, }, ) { clay.Text("Hellope!", text_config) } clay_batch := draw.ClayBatch { bounds = base_layer.bounds, cmds = clay.EndLayout(0), } draw.prepare_clay_batch(base_layer, &clay_batch) draw.end(gpu, window) } } hellope_custom :: proc() { if !sdl.Init({.VIDEO}) do os.exit(1) window := sdl.CreateWindow("Hellope Custom!", 600, 400, {.HIGH_PIXEL_DENSITY}) gpu := sdl.CreateGPUDevice(draw.PLATFORM_SHADER_FORMAT, true, nil) if !sdl.ClaimWindowForGPUDevice(gpu, window) do os.exit(1) if !draw.init(gpu, window) do os.exit(1) PLEX_SANS_REGULAR = draw.register_font(cyber.SANS_REGULAR_RAW) text_config := clay.TextElementConfig { fontId = PLEX_SANS_REGULAR, fontSize = 24, textColor = {255, 255, 255, 255}, } gauge := Gauge { value = 0.73, color = {50, 200, 100, 255}, bg_color = {80, 80, 80, 255}, } gauge2 := Gauge { value = 0.45, color = {200, 100, 50, 255}, bg_color = {80, 80, 80, 255}, } // `clay.CustomElementConfig.customData` is a rawptr; the Clay integration in `draw` // requires it to point at a `Clay_Custom` value. The explicit `rawptr(...)` cast is // necessary because Odin does not chain `^Gauge -> rawptr -> Clay_Custom` implicitly // (variant-to-union and ^T-to-rawptr are each implicit on their own, but not stacked). gauge_custom: draw.Clay_Custom = rawptr(&gauge) gauge2_custom: draw.Clay_Custom = rawptr(&gauge2) // Backdrop variant: variant-to-union conversion is implicit, so no cast needed. // `tint = draw.WHITE` is the no-op tint per the backdrop module's convention // (matches `examples/backdrop.odin`'s "pure blur, no color" usage). backdrop_custom: draw.Clay_Custom = draw.Backdrop_Marker { sigma = 8, tint = draw.WHITE, } spin_angle: f32 = 0 for { defer free_all(context.temp_allocator) ev: sdl.Event for sdl.PollEvent(&ev) { if ev.type == .QUIT do return } spin_angle += 1 gauge.value = (math.sin(spin_angle * 0.02) + 1) * 0.5 gauge2.value = (math.cos(spin_angle * 0.03) + 1) * 0.5 base_layer := draw.begin({width = 600, height = 400}) clay.SetLayoutDimensions({width = base_layer.bounds.width, height = base_layer.bounds.height}) clay.BeginLayout() if clay.UI(clay.ID("outer"))( { layout = { sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {x = .Center, y = .Center}, layoutDirection = .TopToBottom, childGap = 20, }, backgroundColor = {50, 50, 50, 255}, }, ) { if clay.UI(clay.ID("title"))({layout = {sizing = {clay.SizingFit({}), clay.SizingFit({})}}}) { clay.Text("Custom Draw Demo", text_config) } // gauge1 is BEHIND the backdrop — the backdrop is declared as a floating CHILD // of gauge1, pinned to gauge1's LeftTop and sized 300x30 so it covers exactly // gauge1's footprint. Clay emits a floating child's render command after the // parent's, so the stream order is gauge1 → backdrop → gauge2: gauge1's pixels // land in `source_texture` before the bracket samples (visible as a blurred // reflection inside the strip), and gauge2 is deferred-replayed by // `prepare_clay_batch` after the bracket closes (renders crisp on top of the // bracket output — unrelated to the strip since they don't overlap). // `backgroundColor` is omitted on the gauges; bg lives on `Gauge.bg_color`. See `draw_custom`. if clay.UI(clay.ID("gauge"))( { layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}}, custom = {customData = &gauge_custom}, }, ) { if clay.UI(clay.ID("backdrop"))( { floating = {attachTo = .Parent, attachment = {parent = .LeftTop, element = .LeftTop}}, layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}}, custom = {customData = &backdrop_custom}, }, ) {} } if clay.UI(clay.ID("gauge2"))( { layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}}, custom = {customData = &gauge2_custom}, }, ) {} } clay_batch := draw.ClayBatch { bounds = base_layer.bounds, cmds = clay.EndLayout(0), } draw.prepare_clay_batch(base_layer, &clay_batch, custom_draw = draw_custom) draw.end(gpu, window) } Gauge :: struct { value: f32, color: draw.Color, bg_color: draw.Color, } draw_custom :: proc(layer: ^draw.Layer, bounds: draw.Rectangle, render_data: clay.CustomRenderData) { // `render_data.customData` has been unwrapped from the `Clay_Custom` envelope by // `prepare_clay_batch` — it points at the Gauge directly, the same as it would have // before the union refactor. gauge := cast(^Gauge)render_data.customData // `gauge.bg_color` instead of `render_data.backgroundColor`: under Clay master, an // element with both `custom.customData` and `backgroundColor` emits a Custom AND a // Rectangle for the same bounds, in that order — the Rectangle paints over the // callback's output. Carrying bg on user data sidesteps it. border_width: f32 = 2 draw.rectangle(layer, bounds, gauge.bg_color, outline_color = draw.WHITE, outline_width = border_width) fill := draw.Rectangle { x = bounds.x, y = bounds.y, width = bounds.width * gauge.value, height = bounds.height, } draw.rectangle(layer, fill, gauge.color) } }