# HG changeset patch # User Sam # Date 1706377271 -25200 # Node ID b032768df6313f7d704ae319c065689aec3095e0 # Parent 73cca428e27adc8ca9fdffd15e2c01a87a0361fa add: alignment for text boxes diff -r 73cca428e27a -r b032768df631 semicongine/text.nim --- a/semicongine/text.nim Sat Jan 27 21:08:31 2024 +0700 +++ b/semicongine/text.nim Sun Jan 28 00:41:11 2024 +0700 @@ -1,4 +1,5 @@ import std/tables +# import std/sequtils import std/unicode import std/strformat @@ -11,20 +12,26 @@ var instanceCounter = 0 type - TextAlignment = enum + HorizontalAlignment = enum Left Center Right + VerticalAlignment = enum + Top + Center + Bottom Text* = object maxLen*: int text: seq[Rune] dirty: bool - alignment*: TextAlignment = Center + horizontalAlignment*: HorizontalAlignment = Center + verticalAlignment*: VerticalAlignment = Center font*: Font mesh*: Mesh color*: Vec4f const + NEWLINE = Rune('\n') POSITION_ATTRIB = SHADER_ATTRIB_PREFIX & "position" UV_ATTRIB = SHADER_ATTRIB_PREFIX & "uv" TEXT_MATERIAL_TYPE* = MaterialType( @@ -50,33 +57,47 @@ # pre-calculate text-width var width = 0'f32 - var maxWidth = 0'f32 - var height = 0'f32 # todo: finish implementation to handle newline, start here - const newline = Rune('\n') + var lineWidths: seq[float32] for i in 0 ..< min(textbox.text.len, textbox.maxLen): - if textbox.text[i] == newline: - maxWidth = max(width, maxWidth) + if textbox.text[i] == NEWLINE: + lineWidths.add width width = 0'f32 - height += textbox.font.lineAdvance else: 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])] - maxWidth = max(width, maxWidth) + lineWidths.add width + let + height = float32(lineWidths.len) * textbox.font.lineAdvance + + let anchorY = (case textbox.verticalAlignment + of Top: 0'f32 + of Center: height / 2 + of Bottom: height) - textbox.font.lineAdvance - let anchorX = maxWidth / 2 - # let anchorY = height / 2 # use this for vertical centering - let anchorY = 0'f32 - - var offsetX = 0'f32 - var offsetY = 0'f32 - + var + offsetX = 0'f32 + offsetY = 0'f32 + lineIndex = 0 + anchorX = case textbox.horizontalAlignment + of Left: 0'f32 + of Center: lineWidths[lineIndex] / 2 + of Right: lineWidths.max for i in 0 ..< textbox.maxLen: let vertexOffset = i * 4 if i < textbox.text.len: if textbox.text[i] == Rune('\n'): offsetX = 0 offsetY += textbox.font.lineAdvance + 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() + inc lineIndex + anchorX = case textbox.horizontalAlignment + of Left: 0'f32 + of Center: lineWidths[lineIndex] / 2 + of Right: lineWidths.max else: let glyph = textbox.font.glyphs[textbox.text[i]] @@ -117,6 +138,20 @@ proc `text=`*(textbox: var Text, text: string) = `text=`(textbox, text.toRunes) +proc horizontalAlignment*(textbox: Text): HorizontalAlignment = + textbox.horizontalAlignment +proc verticalAlignment*(textbox: Text): VerticalAlignment = + textbox.verticalAlignment +proc `horizontalAlignment=`*(textbox: var Text, value: HorizontalAlignment) = + if value != textbox.horizontalAlignment: + textbox.horizontalAlignment = value + textbox.updateMesh() +proc `verticalAlignment=`*(textbox: var Text, value: VerticalAlignment) = + if value != textbox.verticalAlignment : + textbox.verticalAlignment = value + textbox.updateMesh() + + proc initText*(maxLen: int, font: Font, text = "".toRunes, color = newVec4f(0, 0, 0, 1)): Text = var positions = newSeq[Vec3f](int(maxLen * 4)) diff -r 73cca428e27a -r b032768df631 tests/test_font.nim --- a/tests/test_font.nim Sat Jan 27 21:08:31 2024 +0700 +++ b/tests/test_font.nim Sun Jan 28 00:41:11 2024 +0700 @@ -22,18 +22,19 @@ if engine.windowWasResized(): var winSize = engine.getWindow().size textbox.mesh.transform = scale(fontscale * (winSize[1] / winSize[0]), fontscale) - for c in [Key.A, Key.B, Key.C, Key.D, Key.E, Key.F, Key.G, Key.H, Key.I, - Key.J, Key.K, Key.L, Key.M, Key.N, Key.O, Key.P, Key.Q, Key.R, Key.S, - Key.T, Key.U, Key.V, Key.W, Key.X, Key.Y, Key.Z]: - if engine.keyWasPressed(c): - if engine.keyIsDown(ShiftL) or engine.keyIsDown(ShiftR): - textbox.text = textbox.text[0 ..< ^1] & ($c).toRunes & cursor - else: - textbox.text = textbox.text[0 ..< ^1] & ($c).toRunes[0].toLower() & cursor - if engine.keyWasPressed(Enter): - textbox.text = textbox.text[0 ..< ^1] & Rune('\n') & cursor - if engine.keyWasPressed(Space): - textbox.text = textbox.text[0 ..< ^1] & Rune(' ') & cursor + if textbox.text.len < textbox.maxLen - 1: + for c in [Key.A, Key.B, Key.C, Key.D, Key.E, Key.F, Key.G, Key.H, Key.I, + Key.J, Key.K, Key.L, Key.M, Key.N, Key.O, Key.P, Key.Q, Key.R, Key.S, + Key.T, Key.U, Key.V, Key.W, Key.X, Key.Y, Key.Z]: + if engine.keyWasPressed(c): + if engine.keyIsDown(ShiftL) or engine.keyIsDown(ShiftR): + textbox.text = textbox.text[0 ..< ^1] & ($c).toRunes & cursor + else: + textbox.text = textbox.text[0 ..< ^1] & ($c).toRunes[0].toLower() & cursor + if engine.keyWasPressed(Enter): + textbox.text = textbox.text[0 ..< ^1] & Rune('\n') & cursor + if engine.keyWasPressed(Space): + textbox.text = textbox.text[0 ..< ^1] & Rune(' ') & cursor if engine.keyWasPressed(Backspace) and textbox.text.len > 1: textbox.text = textbox.text[0 ..< ^2] & cursor engine.renderScene(scene)