Initial draw package
This commit is contained in:
140
draw/text.odin
Normal file
140
draw/text.odin
Normal file
@@ -0,0 +1,140 @@
|
||||
package draw
|
||||
|
||||
import "core:log"
|
||||
import sdl "vendor:sdl3"
|
||||
import sdl_ttf "vendor:sdl3/ttf"
|
||||
|
||||
Font_Id :: u16
|
||||
|
||||
Font_Key :: struct {
|
||||
id: Font_Id,
|
||||
size: u16,
|
||||
}
|
||||
|
||||
Text_Cache :: struct {
|
||||
engine: ^sdl_ttf.TextEngine,
|
||||
font_bytes: [dynamic][]u8,
|
||||
sdl_fonts: map[Font_Key]^sdl_ttf.Font,
|
||||
cache: map[u32]^sdl_ttf.Text,
|
||||
}
|
||||
|
||||
// Internal for fetching SDL TTF font pointer for rendering
|
||||
get_font :: proc(id: Font_Id, size: u16) -> ^sdl_ttf.Font {
|
||||
assert(int(id) < len(GLOB.text_cache.font_bytes), "Invalid font ID.")
|
||||
key := Font_Key{id, size}
|
||||
font := GLOB.text_cache.sdl_fonts[key]
|
||||
|
||||
if font == nil {
|
||||
log.debug("Font with id:", id, "and size:", size, "not found. Adding..")
|
||||
|
||||
font_bytes := GLOB.text_cache.font_bytes[id]
|
||||
if font_bytes == nil {
|
||||
log.panicf("Font must first be registered with register_font before using (id=%d)", id)
|
||||
}
|
||||
|
||||
font_io := sdl.IOFromConstMem(raw_data(font_bytes[:]), len(font_bytes))
|
||||
if font_io == nil {
|
||||
log.panicf("Failed to create IOStream for font id=%d: %s", id, sdl.GetError())
|
||||
}
|
||||
|
||||
sdl_font := sdl_ttf.OpenFontIO(font_io, true, f32(size))
|
||||
if sdl_font == nil {
|
||||
log.panicf("Failed to create SDL font for font id=%d size=%d: %s", id, size, sdl.GetError())
|
||||
}
|
||||
|
||||
if !sdl_ttf.SetFontSizeDPI(sdl_font, f32(size), 72 * i32(GLOB.dpi_scaling), 72 * i32(GLOB.dpi_scaling)) {
|
||||
log.panicf("Failed to set font DPI for font id=%d size=%d: %s", id, size, sdl.GetError())
|
||||
}
|
||||
|
||||
GLOB.text_cache.sdl_fonts[key] = sdl_font
|
||||
return sdl_font
|
||||
} else {
|
||||
return font
|
||||
}
|
||||
}
|
||||
|
||||
// Returns `false` if there are more than max(u16) fonts
|
||||
register_font :: proc(bytes: []u8) -> (id: Font_Id, ok: bool) #optional_ok {
|
||||
if GLOB.text_cache.engine == nil {
|
||||
log.panicf("Cannot register font: text system not initialized. Call init() first.")
|
||||
}
|
||||
if len(GLOB.text_cache.font_bytes) > int(max(Font_Id)) do return 0, false
|
||||
|
||||
log.debug("Registering font...")
|
||||
append(&GLOB.text_cache.font_bytes, bytes)
|
||||
return Font_Id(len(GLOB.text_cache.font_bytes) - 1), true
|
||||
}
|
||||
|
||||
Text :: struct {
|
||||
ref: ^sdl_ttf.Text,
|
||||
position: [2]f32,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
text :: proc(
|
||||
id: u32,
|
||||
txt: cstring,
|
||||
pos: [2]f32,
|
||||
font_id: Font_Id,
|
||||
font_size: u16 = 44,
|
||||
color: Color = {0, 0, 0, 255},
|
||||
) -> Text {
|
||||
sdl_text := GLOB.text_cache.cache[id]
|
||||
if sdl_text == nil {
|
||||
sdl_text = sdl_ttf.CreateText(GLOB.text_cache.engine, get_font(font_id, font_size), txt, 0)
|
||||
if sdl_text == nil {
|
||||
log.panicf("Failed to create SDL text: %s", sdl.GetError())
|
||||
}
|
||||
GLOB.text_cache.cache[id] = sdl_text
|
||||
} else {
|
||||
//TODO if IDs are always unique and never change the underlying text
|
||||
// can get rid of this
|
||||
if !sdl_ttf.SetTextString(sdl_text, txt, 0) {
|
||||
log.panicf("Failed to update SDL text string: %s", sdl.GetError())
|
||||
}
|
||||
}
|
||||
|
||||
return Text{sdl_text, pos, color}
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
init_text_cache :: proc(
|
||||
device: ^sdl.GPUDevice,
|
||||
allocator := context.allocator,
|
||||
) -> (
|
||||
text_cache: Text_Cache,
|
||||
ok: bool,
|
||||
) {
|
||||
log.debug("Initializing text state")
|
||||
if !sdl_ttf.Init() {
|
||||
log.errorf("Failed to initialize SDL_ttf: %s", sdl.GetError())
|
||||
return text_cache, false
|
||||
}
|
||||
|
||||
engine := sdl_ttf.CreateGPUTextEngine(device)
|
||||
if engine == nil {
|
||||
log.errorf("Failed to create GPU text engine: %s", sdl.GetError())
|
||||
sdl_ttf.Quit()
|
||||
return text_cache, false
|
||||
}
|
||||
sdl_ttf.SetGPUTextEngineWinding(engine, .COUNTER_CLOCKWISE)
|
||||
|
||||
text_cache = Text_Cache {
|
||||
engine = engine,
|
||||
cache = make(map[u32]^sdl_ttf.Text, allocator = allocator),
|
||||
}
|
||||
|
||||
log.debug("Done initializing text cache")
|
||||
return text_cache, true
|
||||
}
|
||||
|
||||
destroy_text_cache :: proc() {
|
||||
for _, font in GLOB.text_cache.sdl_fonts {
|
||||
sdl_ttf.CloseFont(font)
|
||||
}
|
||||
delete(GLOB.text_cache.sdl_fonts)
|
||||
delete(GLOB.text_cache.font_bytes)
|
||||
delete(GLOB.text_cache.cache)
|
||||
sdl_ttf.DestroyGPUTextEngine(GLOB.text_cache.engine)
|
||||
sdl_ttf.Quit()
|
||||
}
|
||||
Reference in New Issue
Block a user