Added draw package as renderer focused on mixed use layout / 2D / 3D scene applications #7
@@ -60,6 +60,11 @@
|
|||||||
"command": "odin run draw/examples -debug -out=out/debug/draw-examples -- hellope-text",
|
"command": "odin run draw/examples -debug -out=out/debug/draw-examples -- hellope-text",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT",
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "Run draw hellope-custom example",
|
||||||
|
"command": "odin run draw/examples -debug -out=out/debug/draw-examples -- hellope-custom",
|
||||||
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
|
},
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// ----- Other ------------------------
|
// ----- Other ------------------------
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -352,6 +352,11 @@ prepare_text :: proc(layer: ^Layer, text: Text) {
|
|||||||
|
|
||||||
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
||||||
|
|
||||||
|
// Snap base position to integer physical pixels to avoid atlas sub-pixel
|
||||||
|
// sampling blur (and the off-by-one bottom-row clip that comes with it).
|
||||||
|
base_x := math.round(text.position[0] * GLOB.dpi_scaling)
|
||||||
|
base_y := math.round(text.position[1] * GLOB.dpi_scaling)
|
||||||
|
|
||||||
for data != nil {
|
for data != nil {
|
||||||
vertex_start := u32(len(GLOB.tmp_text_verts))
|
vertex_start := u32(len(GLOB.tmp_text_verts))
|
||||||
index_start := u32(len(GLOB.tmp_text_indices))
|
index_start := u32(len(GLOB.tmp_text_indices))
|
||||||
@@ -363,7 +368,7 @@ prepare_text :: proc(layer: ^Layer, text: Text) {
|
|||||||
append(
|
append(
|
||||||
&GLOB.tmp_text_verts,
|
&GLOB.tmp_text_verts,
|
||||||
Vertex {
|
Vertex {
|
||||||
position = {pos.x + text.position[0] * GLOB.dpi_scaling, -pos.y + text.position[1] * GLOB.dpi_scaling},
|
position = {pos.x + base_x, -pos.y + base_y},
|
||||||
uv = {uv.x, uv.y},
|
uv = {uv.x, uv.y},
|
||||||
color = text.color,
|
color = text.color,
|
||||||
},
|
},
|
||||||
@@ -471,6 +476,19 @@ clay_error_handler :: proc "c" (errorData: clay.ErrorData) {
|
|||||||
log.error("Clay error:", errorData.errorType, errorData.errorText)
|
log.error("Clay error:", errorData.errorType, errorData.errorText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called for each Clay `RenderCommandType.Custom` render command that
|
||||||
|
// `prepare_clay_batch` encounters.
|
||||||
|
//
|
||||||
|
// - `layer` is the layer the command belongs to (post-z-index promotion).
|
||||||
|
// - `bounds` is already translated into the active layer's coordinate system
|
||||||
|
// and pre-DPI, matching what the built-in shape procs expect.
|
||||||
|
// - `render_data` is Clay's `CustomRenderData` for the element, exposing
|
||||||
|
// `backgroundColor`, `cornerRadius`, and the `customData` pointer the caller
|
||||||
|
// attached to `clay.CustomElementConfig.customData`.
|
||||||
|
//
|
||||||
|
// The callback must not call `new_layer` or `prepare_clay_batch`.
|
||||||
|
Custom_Draw :: #type proc(layer: ^Layer, bounds: Rectangle, render_data: clay.CustomRenderData)
|
||||||
|
|
||||||
ClayBatch :: struct {
|
ClayBatch :: struct {
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cmds: clay.ClayArray(clay.RenderCommand),
|
cmds: clay.ClayArray(clay.RenderCommand),
|
||||||
@@ -482,6 +500,7 @@ prepare_clay_batch :: proc(
|
|||||||
batch: ^ClayBatch,
|
batch: ^ClayBatch,
|
||||||
mouse_wheel_delta: [2]f32,
|
mouse_wheel_delta: [2]f32,
|
||||||
frame_time: f32 = 0,
|
frame_time: f32 = 0,
|
||||||
|
custom_draw: Custom_Draw = nil,
|
||||||
) {
|
) {
|
||||||
mouse_pos: [2]f32
|
mouse_pos: [2]f32
|
||||||
mouse_flags := sdl.GetMouseState(&mouse_pos.x, &mouse_pos.y)
|
mouse_flags := sdl.GetMouseState(&mouse_pos.x, &mouse_pos.y)
|
||||||
@@ -522,10 +541,10 @@ prepare_clay_batch :: proc(
|
|||||||
render_data := render_command.renderData.text
|
render_data := render_command.renderData.text
|
||||||
txt := string(render_data.stringContents.chars[:render_data.stringContents.length])
|
txt := string(render_data.stringContents.chars[:render_data.stringContents.length])
|
||||||
c_text := strings.clone_to_cstring(txt, context.temp_allocator)
|
c_text := strings.clone_to_cstring(txt, context.temp_allocator)
|
||||||
// Clay's render_command.id is already hashed with the same Jenkins algorithm
|
// Clay render-command IDs are derived via Clay's internal HashNumber (Jenkins-family)
|
||||||
// as text_cache_hash, so it shares the same keyspace.
|
// and namespaced with .Clay so they can never collide with user-provided custom text IDs.
|
||||||
sdl_text := cache_get_or_update(
|
sdl_text := cache_get_or_update(
|
||||||
render_command.id,
|
Cache_Key{render_command.id, .Clay},
|
||||||
c_text,
|
c_text,
|
||||||
get_font(render_data.fontId, render_data.fontSize),
|
get_font(render_data.fontId, render_data.fontSize),
|
||||||
)
|
)
|
||||||
@@ -581,7 +600,9 @@ prepare_clay_batch :: proc(
|
|||||||
} else {
|
} else {
|
||||||
rectangle_corners_lines(layer, bounds, radii, color, thickness)
|
rectangle_corners_lines(layer, bounds, radii, color, thickness)
|
||||||
}
|
}
|
||||||
case clay.RenderCommandType.Custom:
|
case clay.RenderCommandType.Custom: if custom_draw != nil {
|
||||||
|
custom_draw(layer, bounds, render_command.renderData.custom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package examples
|
|||||||
|
|
||||||
import "../../draw"
|
import "../../draw"
|
||||||
import "../../vendor/clay"
|
import "../../vendor/clay"
|
||||||
|
import "core:math"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import sdl "vendor:sdl3"
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
@@ -108,6 +109,11 @@ hellope_shapes :: proc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hellope_text :: proc() {
|
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)
|
if !sdl.Init({.VIDEO}) do os.exit(1)
|
||||||
window := sdl.CreateWindow("Hellope!", 600, 600, {.HIGH_PIXEL_DENSITY})
|
window := sdl.CreateWindow("Hellope!", 600, 600, {.HIGH_PIXEL_DENSITY})
|
||||||
gpu := sdl.CreateGPUDevice({.MSL}, true, nil)
|
gpu := sdl.CreateGPUDevice({.MSL}, true, nil)
|
||||||
@@ -141,7 +147,7 @@ hellope_text :: proc() {
|
|||||||
FONT_SIZE,
|
FONT_SIZE,
|
||||||
color = draw.WHITE,
|
color = draw.WHITE,
|
||||||
origin = draw.center_of("Hellope!", JETBRAINS_MONO_REGULAR, FONT_SIZE),
|
origin = draw.center_of("Hellope!", JETBRAINS_MONO_REGULAR, FONT_SIZE),
|
||||||
id = "hellope",
|
id = HELLOPE_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rotating sentence — verifies multi-word text rotation around center
|
// Rotating sentence — verifies multi-word text rotation around center
|
||||||
@@ -154,7 +160,7 @@ hellope_text :: proc() {
|
|||||||
color = {255, 200, 50, 255},
|
color = {255, 200, 50, 255},
|
||||||
origin = draw.center_of("Hellope World!", JETBRAINS_MONO_REGULAR, FONT_SIZE),
|
origin = draw.center_of("Hellope World!", JETBRAINS_MONO_REGULAR, FONT_SIZE),
|
||||||
rotation = spin_angle,
|
rotation = spin_angle,
|
||||||
id = "rotating_sentence",
|
id = ROTATING_SENTENCE_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Uncached text (no id) — created and destroyed each frame, simplest usage
|
// Uncached text (no id) — created and destroyed each frame, simplest usage
|
||||||
@@ -178,7 +184,7 @@ hellope_text :: proc() {
|
|||||||
FONT_SIZE,
|
FONT_SIZE,
|
||||||
color = draw.WHITE,
|
color = draw.WHITE,
|
||||||
origin = draw.top_of("Measured!", JETBRAINS_MONO_REGULAR, FONT_SIZE),
|
origin = draw.top_of("Measured!", JETBRAINS_MONO_REGULAR, FONT_SIZE),
|
||||||
id = "measured",
|
id = MEASURED_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rotating text anchored at top-left (no origin offset) — spins around top-left corner
|
// Rotating text anchored at top-left (no origin offset) — spins around top-left corner
|
||||||
@@ -190,7 +196,7 @@ hellope_text :: proc() {
|
|||||||
FONT_SIZE,
|
FONT_SIZE,
|
||||||
color = {100, 200, 255, 255},
|
color = {100, 200, 255, 255},
|
||||||
rotation = spin_angle,
|
rotation = spin_angle,
|
||||||
id = "corner_spin",
|
id = CORNER_SPIN_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
draw.end(gpu, window)
|
draw.end(gpu, window)
|
||||||
@@ -207,7 +213,7 @@ hellope_clay :: proc() {
|
|||||||
|
|
||||||
text_config := clay.TextElementConfig {
|
text_config := clay.TextElementConfig {
|
||||||
fontId = JETBRAINS_MONO_REGULAR,
|
fontId = JETBRAINS_MONO_REGULAR,
|
||||||
fontSize = 24,
|
fontSize = 36,
|
||||||
textColor = {255, 255, 255, 255},
|
textColor = {255, 255, 255, 255},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,3 +246,106 @@ hellope_clay :: proc() {
|
|||||||
draw.end(gpu, window)
|
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({.MSL}, true, nil)
|
||||||
|
if !sdl.ClaimWindowForGPUDevice(gpu, window) do os.exit(1)
|
||||||
|
if !draw.init(gpu, window) do os.exit(1)
|
||||||
|
JETBRAINS_MONO_REGULAR = draw.register_font(JETBRAINS_MONO_REGULAR_RAW)
|
||||||
|
|
||||||
|
text_config := clay.TextElementConfig {
|
||||||
|
fontId = JETBRAINS_MONO_REGULAR,
|
||||||
|
fontSize = 24,
|
||||||
|
textColor = {255, 255, 255, 255},
|
||||||
|
}
|
||||||
|
|
||||||
|
gauge := Gauge {
|
||||||
|
value = 0.73,
|
||||||
|
color = {50, 200, 100, 255},
|
||||||
|
}
|
||||||
|
gauge2 := Gauge {
|
||||||
|
value = 0.45,
|
||||||
|
color = {200, 100, 50, 255},
|
||||||
|
}
|
||||||
|
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()(
|
||||||
|
{
|
||||||
|
id = 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()({id = clay.ID("title"), layout = {sizing = {clay.SizingFit({}), clay.SizingFit({})}}}) {
|
||||||
|
clay.Text("Custom Draw Demo", &text_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if clay.UI()(
|
||||||
|
{
|
||||||
|
id = clay.ID("gauge"),
|
||||||
|
layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}},
|
||||||
|
custom = {customData = &gauge},
|
||||||
|
backgroundColor = {80, 80, 80, 255},
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
if clay.UI()(
|
||||||
|
{
|
||||||
|
id = clay.ID("gauge2"),
|
||||||
|
layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}},
|
||||||
|
custom = {customData = &gauge2},
|
||||||
|
backgroundColor = {80, 80, 80, 255},
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
clay_batch := draw.ClayBatch {
|
||||||
|
bounds = base_layer.bounds,
|
||||||
|
cmds = clay.EndLayout(),
|
||||||
|
}
|
||||||
|
draw.prepare_clay_batch(base_layer, &clay_batch, {0, 0}, custom_draw = draw_custom)
|
||||||
|
draw.end(gpu, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
Gauge :: struct {
|
||||||
|
value: f32,
|
||||||
|
color: draw.Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_custom :: proc(layer: ^draw.Layer, bounds: draw.Rectangle, render_data: clay.CustomRenderData) {
|
||||||
|
gauge := cast(^Gauge)render_data.customData
|
||||||
|
|
||||||
|
// Background from clay's backgroundColor
|
||||||
|
draw.rectangle(layer, bounds, draw.color_from_clay(render_data.backgroundColor), roundness = 0.25)
|
||||||
|
|
||||||
|
// Fill bar
|
||||||
|
fill := bounds
|
||||||
|
fill.width *= gauge.value
|
||||||
|
draw.rectangle(layer, fill, gauge.color, roundness = 0.25)
|
||||||
|
|
||||||
|
// Border
|
||||||
|
draw.rectangle_lines(layer, bounds, draw.WHITE, thickness = 2, roundness = 0.25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,17 +57,18 @@ main :: proc() {
|
|||||||
args := os.args
|
args := os.args
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
fmt.eprintln("Usage: examples <example_name>")
|
fmt.eprintln("Usage: examples <example_name>")
|
||||||
fmt.eprintln("Available examples: hellope-shapes, hellope-text, hellope-clay")
|
fmt.eprintln("Available examples: hellope-shapes, hellope-text, hellope-clay, hellope-custom")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "hellope-clay": hellope_clay()
|
case "hellope-clay": hellope_clay()
|
||||||
|
case "hellope-custom": hellope_custom()
|
||||||
case "hellope-shapes": hellope_shapes()
|
case "hellope-shapes": hellope_shapes()
|
||||||
case "hellope-text": hellope_text()
|
case "hellope-text": hellope_text()
|
||||||
case:
|
case:
|
||||||
fmt.eprintf("Unknown example: %v\n", args[1])
|
fmt.eprintf("Unknown example: %v\n", args[1])
|
||||||
fmt.eprintln("Available examples: hellope-shapes, hellope-text, hellope-clay")
|
fmt.eprintln("Available examples: hellope-shapes, hellope-text, hellope-clay, hellope-custom")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package draw
|
package draw
|
||||||
|
|
||||||
import "core:c"
|
import "core:c"
|
||||||
import "core:hash"
|
|
||||||
import "core:log"
|
import "core:log"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import sdl "vendor:sdl3"
|
import sdl "vendor:sdl3"
|
||||||
@@ -14,11 +13,21 @@ Font_Key :: struct {
|
|||||||
size: u16,
|
size: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cache_Source :: enum u8 {
|
||||||
|
Custom,
|
||||||
|
Clay,
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache_Key :: struct {
|
||||||
|
id: u32,
|
||||||
|
source: Cache_Source,
|
||||||
|
}
|
||||||
|
|
||||||
Text_Cache :: struct {
|
Text_Cache :: struct {
|
||||||
engine: ^sdl_ttf.TextEngine,
|
engine: ^sdl_ttf.TextEngine,
|
||||||
font_bytes: [dynamic][]u8,
|
font_bytes: [dynamic][]u8,
|
||||||
sdl_fonts: map[Font_Key]^sdl_ttf.Font,
|
sdl_fonts: map[Font_Key]^sdl_ttf.Font,
|
||||||
cache: map[u32]^sdl_ttf.Text,
|
cache: map[Cache_Key]^sdl_ttf.Text,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for fetching SDL TTF font pointer for rendering
|
// Internal for fetching SDL TTF font pointer for rendering
|
||||||
@@ -75,27 +84,20 @@ Text :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// ----- Text cache hashing ------------
|
// ----- Text cache lookup -------------
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Hash a string to a u32 cache key using the same Jenkins one-at-a-time algorithm as Clay.
|
|
||||||
// This means Clay element IDs and user-chosen string IDs share the same keyspace — same
|
|
||||||
// string produces the same cache key regardless of whether it came from Clay or user code.
|
|
||||||
text_cache_hash :: #force_inline proc(text_string: string) -> u32 {
|
|
||||||
return hash.jenkins(transmute([]u8)text_string) + 1 // +1 reserves 0 as "no entry" (matches Clay convention)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shared cache lookup/create/update logic used by both the `text` proc and the Clay render path.
|
// Shared cache lookup/create/update logic used by both the `text` proc and the Clay render path.
|
||||||
// Returns the cached (or newly created) TTF_Text pointer.
|
// Returns the cached (or newly created) TTF_Text pointer.
|
||||||
@(private)
|
@(private)
|
||||||
cache_get_or_update :: proc(cache_id: u32, c_str: cstring, font: ^sdl_ttf.Font) -> ^sdl_ttf.Text {
|
cache_get_or_update :: proc(key: Cache_Key, c_str: cstring, font: ^sdl_ttf.Font) -> ^sdl_ttf.Text {
|
||||||
existing, found := GLOB.text_cache.cache[cache_id]
|
existing, found := GLOB.text_cache.cache[key]
|
||||||
if !found {
|
if !found {
|
||||||
sdl_text := sdl_ttf.CreateText(GLOB.text_cache.engine, font, c_str, 0)
|
sdl_text := sdl_ttf.CreateText(GLOB.text_cache.engine, font, c_str, 0)
|
||||||
if sdl_text == nil {
|
if sdl_text == nil {
|
||||||
log.panicf("Failed to create SDL text: %s", sdl.GetError())
|
log.panicf("Failed to create SDL text: %s", sdl.GetError())
|
||||||
}
|
}
|
||||||
GLOB.text_cache.cache[cache_id] = sdl_text
|
GLOB.text_cache.cache[key] = sdl_text
|
||||||
return sdl_text
|
return sdl_text
|
||||||
} else {
|
} else {
|
||||||
if !sdl_ttf.SetTextString(existing, c_str, 0) {
|
if !sdl_ttf.SetTextString(existing, c_str, 0) {
|
||||||
@@ -114,11 +116,12 @@ cache_get_or_update :: proc(cache_id: u32, c_str: cstring, font: ^sdl_ttf.Font)
|
|||||||
// When `id` is nil (the default), the text is created and destroyed each frame — simple and
|
// When `id` is nil (the default), the text is created and destroyed each frame — simple and
|
||||||
// leak-free, appropriate for HUDs and moderate UI (up to ~50 text elements per frame).
|
// leak-free, appropriate for HUDs and moderate UI (up to ~50 text elements per frame).
|
||||||
//
|
//
|
||||||
// When `id` is set, the TTF_Text object is cached across frames using a hash of the provided
|
// When `id` is set, the TTF_Text object is cached across frames keyed by the provided u32.
|
||||||
// string (same algorithm as Clay's element IDs). This avoids per-frame HarfBuzz shaping and
|
// This avoids per-frame HarfBuzz shaping and allocation, which matters for text-heavy apps
|
||||||
// allocation, which matters for text-heavy apps (editors, terminals, chat). The user is
|
// (editors, terminals, chat). The user is responsible for choosing unique IDs per logical text
|
||||||
// responsible for choosing unique ID strings per logical text element and calling
|
// element and calling `clear_text_cache` or `clear_text_cache_entry` when cached entries are
|
||||||
// `clear_text_cache` or `clear_text_cache_entry` when cached entries are no longer needed.
|
// no longer needed. Custom text IDs occupy a separate namespace from Clay text IDs, so
|
||||||
|
// collisions between the two are impossible.
|
||||||
//
|
//
|
||||||
// `origin` is in pixels from the text block's top-left corner (raylib convention).
|
// `origin` is in pixels from the text block's top-left corner (raylib convention).
|
||||||
// The point whose local coords equal `origin` lands at `pos` in world space.
|
// The point whose local coords equal `origin` lands at `pos` in world space.
|
||||||
@@ -132,7 +135,7 @@ text :: proc(
|
|||||||
color: Color = BLACK,
|
color: Color = BLACK,
|
||||||
origin: [2]f32 = {0, 0},
|
origin: [2]f32 = {0, 0},
|
||||||
rotation: f32 = 0,
|
rotation: f32 = 0,
|
||||||
id: Maybe(string) = nil,
|
id: Maybe(u32) = nil,
|
||||||
temp_allocator := context.temp_allocator,
|
temp_allocator := context.temp_allocator,
|
||||||
) {
|
) {
|
||||||
c_str := strings.clone_to_cstring(text_string, temp_allocator)
|
c_str := strings.clone_to_cstring(text_string, temp_allocator)
|
||||||
@@ -140,9 +143,9 @@ text :: proc(
|
|||||||
sdl_text: ^sdl_ttf.Text
|
sdl_text: ^sdl_ttf.Text
|
||||||
cached := false
|
cached := false
|
||||||
|
|
||||||
if id_string, ok := id.?; ok {
|
if cache_id, ok := id.?; ok {
|
||||||
cached = true
|
cached = true
|
||||||
sdl_text = cache_get_or_update(text_cache_hash(id_string), c_str, get_font(font_id, font_size))
|
sdl_text = cache_get_or_update(Cache_Key{cache_id, .Custom}, c_str, get_font(font_id, font_size))
|
||||||
} else {
|
} else {
|
||||||
sdl_text = sdl_ttf.CreateText(GLOB.text_cache.engine, get_font(font_id, font_size), c_str, 0)
|
sdl_text = sdl_ttf.CreateText(GLOB.text_cache.engine, get_font(font_id, font_size), c_str, 0)
|
||||||
if sdl_text == nil {
|
if sdl_text == nil {
|
||||||
@@ -236,8 +239,8 @@ bottom_right_of_text :: proc(text_string: string, font_id: Font_Id, font_size: u
|
|||||||
// ----- Cache management --------------
|
// ----- Cache management --------------
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Destroy all cached text objects. Call on scene transitions, view changes, or periodically
|
// Destroy all cached text objects (both custom and Clay entries). Call on scene transitions,
|
||||||
// in apps that produce many distinct cached text entries over time.
|
// view changes, or periodically in apps that produce many distinct cached text entries over time.
|
||||||
// After calling this, subsequent text draws with an `id` will re-create their cache entries.
|
// After calling this, subsequent text draws with an `id` will re-create their cache entries.
|
||||||
clear_text_cache :: proc() {
|
clear_text_cache :: proc() {
|
||||||
for _, sdl_text in GLOB.text_cache.cache {
|
for _, sdl_text in GLOB.text_cache.cache {
|
||||||
@@ -246,12 +249,12 @@ clear_text_cache :: proc() {
|
|||||||
clear(&GLOB.text_cache.cache)
|
clear(&GLOB.text_cache.cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy a specific cached text entry by its string id (same string used when drawing).
|
// Destroy a specific cached custom text entry by its u32 id (the same value passed to the
|
||||||
// Uses the same hash as Clay's element IDs, so this also works for clearing Clay text entries
|
// `text` proc's `id` parameter). This only affects custom text entries — Clay text entries
|
||||||
// by passing the same string used in clay.ID("...").
|
// are managed internally and are not addressable by the user.
|
||||||
// No-op if the id is not in the cache.
|
// No-op if the id is not in the cache.
|
||||||
clear_text_cache_entry :: proc(id: string) {
|
clear_text_cache_entry :: proc(id: u32) {
|
||||||
key := text_cache_hash(id)
|
key := Cache_Key{id, .Custom}
|
||||||
sdl_text, ok := GLOB.text_cache.cache[key]
|
sdl_text, ok := GLOB.text_cache.cache[key]
|
||||||
if ok {
|
if ok {
|
||||||
sdl_ttf.DestroyText(sdl_text)
|
sdl_ttf.DestroyText(sdl_text)
|
||||||
@@ -287,7 +290,7 @@ init_text_cache :: proc(
|
|||||||
|
|
||||||
text_cache = Text_Cache {
|
text_cache = Text_Cache {
|
||||||
engine = engine,
|
engine = engine,
|
||||||
cache = make(map[u32]^sdl_ttf.Text, allocator = allocator),
|
cache = make(map[Cache_Key]^sdl_ttf.Text, allocator = allocator),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Done initializing text cache")
|
log.debug("Done initializing text cache")
|
||||||
|
|||||||
Reference in New Issue
Block a user