Mercurial > games > semicongine
view src/semicongine/text.nim @ 363:451b7ccfe722
improve 2D collision, add some vector functionality, allow shaders/pipelines to be ordered for deterministic rendering order
author | Sam <sam@basx.dev> |
---|---|
date | Sun, 01 Oct 2023 20:53:35 +0700 |
parents | 61c5d5fe9d93 |
children | eef6cc3e1104 |
line wrap: on
line source
import std/tables import std/unicode import std/strformat import ./core import ./mesh import ./vulkan/shader const SHADER_ATTRIB_PREFIX = "semicon_text_" type TextAlignment = enum Left Center Right Textbox* = object maxLen*: int text: seq[Rune] dirty: bool alignment*: TextAlignment font*: Font mesh*: Mesh const TRANSFORM_ATTRIB = "transform" POSITION_ATTRIB = SHADER_ATTRIB_PREFIX & "position" UV_ATTRIB = SHADER_ATTRIB_PREFIX & "uv" TEXT_MATERIAL* = "default-text" TEXT_SHADER* = createShaderConfiguration( inputs=[ attr[Mat4](TRANSFORM_ATTRIB, memoryPerformanceHint=PreferFastWrite, perInstance=true), attr[Vec3f](POSITION_ATTRIB, memoryPerformanceHint=PreferFastWrite), attr[Vec2f](UV_ATTRIB, memoryPerformanceHint=PreferFastWrite), ], intermediates=[attr[Vec2f]("uvFrag")], outputs=[attr[Vec4f]("color")], samplers=[attr[Texture]("fontAtlas")], vertexCode= &"""gl_Position = vec4({POSITION_ATTRIB}, 1.0) * {TRANSFORM_ATTRIB}; uvFrag = {UV_ATTRIB};""", fragmentCode= &"""color = texture(fontAtlas, uvFrag);""", ) proc updateMesh(textbox: var Textbox) = # pre-calculate text-width var width = 0'f32 for i in 0 ..< min(textbox.text.len, textbox.maxLen): width += textbox.font.glyphs[textbox.text[i]].advance if i < textbox.text.len - 1: width += textbox.font.kerning[(textbox.text[i], textbox.text[i + 1])] let centerX = width / 2 let centerY = textbox.font.maxHeight / 2 var offsetX = 0'f32 for i in 0 ..< textbox.maxLen: let vertexOffset = i * 4 if i < textbox.text.len: let glyph = textbox.font.glyphs[textbox.text[i]] left = offsetX + glyph.leftOffset right = offsetX + glyph.leftOffset + glyph.dimension.x top = glyph.topOffset bottom = glyph.topOffset + glyph.dimension.y textbox.mesh[POSITION_ATTRIB, vertexOffset + 0] = newVec3f(left - centerX, bottom + centerY) textbox.mesh[POSITION_ATTRIB, vertexOffset + 1] = newVec3f(left - centerX, top + centerY) textbox.mesh[POSITION_ATTRIB, vertexOffset + 2] = newVec3f(right - centerX, top + centerY) textbox.mesh[POSITION_ATTRIB, vertexOffset + 3] = newVec3f(right - centerX, bottom + centerY) textbox.mesh[UV_ATTRIB, vertexOffset + 0] = glyph.uvs[0] textbox.mesh[UV_ATTRIB, vertexOffset + 1] = glyph.uvs[1] textbox.mesh[UV_ATTRIB, vertexOffset + 2] = glyph.uvs[2] textbox.mesh[UV_ATTRIB, vertexOffset + 3] = glyph.uvs[3] offsetX += glyph.advance if i < textbox.text.len - 1: offsetX += textbox.font.kerning[(textbox.text[i], textbox.text[i + 1])] else: textbox.mesh[POSITION_ATTRIB, vertexOffset + 0] = newVec3f() textbox.mesh[POSITION_ATTRIB, vertexOffset + 1] = newVec3f() textbox.mesh[POSITION_ATTRIB, vertexOffset + 2] = newVec3f() textbox.mesh[POSITION_ATTRIB, vertexOffset + 3] = newVec3f() func text*(textbox: Textbox): seq[Rune] = textbox.text proc `text=`*(textbox: var Textbox, text: seq[Rune]) = textbox.text = text textbox.updateMesh() proc `text=`*(textbox: var Textbox, text: string) = `text=`(textbox, text.toRunes) proc initTextbox*(maxLen: int, font: Font, text="".toRunes): Textbox = var positions = newSeq[Vec3f](int(maxLen * 4)) indices: seq[array[3, uint16]] uvs = newSeq[Vec2f](int(maxLen * 4)) for i in 0 ..< maxLen: let offset = i * 4 indices.add [ [uint16(offset + 0), uint16(offset + 1), uint16(offset + 2)], [uint16(offset + 2), uint16(offset + 3), uint16(offset + 0)], ] result = Textbox(maxLen: maxLen, text: text, font: font, dirty: true) result.mesh = newMesh(positions = positions, indices = indices, uvs = uvs) result.mesh[].renameAttribute("position", POSITION_ATTRIB) result.mesh[].renameAttribute("uv", UV_ATTRIB) result.mesh.materials = @[Material( name: TEXT_MATERIAL, textures: {"fontAtlas": font.fontAtlas}.toTable, )] result.updateMesh() proc initTextbox*(maxLen: int, font: Font, text=""): Textbox = initTextbox(maxLen=maxLen, font=font, text=text.toRunes)