diff --git a/ansi/ansi.go b/ansi/ansi.go index e5da6d7..59e9881 100755 --- a/ansi/ansi.go +++ b/ansi/ansi.go @@ -127,7 +127,7 @@ const ( AnsiCodeOptions_LineAbs AnsiCodeOptions_ScrollOffset - // This is at the bottom so iota starts at 0 + // This is at the bottom so the above iota starts at 0 AnsiCodeOptions_Unknown AnsiCodeOptions = 0 ) diff --git a/glyph_grid.go b/glyph_grid.go new file mode 100755 index 0000000..2e2483e --- /dev/null +++ b/glyph_grid.go @@ -0,0 +1,128 @@ +package main + +import ( + "fmt" + "unicode/utf8" + + "github.com/bloeys/gglm/gglm" +) + +type GridTile struct { + Glyph rune + FgColor gglm.Vec4 + BgColor gglm.Vec4 +} + +type GlyphGrid struct { + CursorX uint + CursorY uint + SizeX uint + SizeY uint + Tiles [][]GridTile +} + +func (gg *GlyphGrid) Write(rs []rune, fgColor *gglm.Vec4, bgColor *gglm.Vec4) { + + for i := 0; i < len(rs); i++ { + + r := rs[i] + gg.Tiles[gg.CursorY][gg.CursorX] = GridTile{ + Glyph: r, + FgColor: *fgColor, + BgColor: *bgColor, + } + + if !gg.TickCursor(r == '\n') { + break + } + } +} + +func (gg *GlyphGrid) Clear() { + + for y := 0; y < len(gg.Tiles); y++ { + row := gg.Tiles[y] + for x := 0; x < len(row); x++ { + row[x].Glyph = utf8.RuneError + } + } + + gg.SetCursor(0, 0) +} + +func (gg *GlyphGrid) SetCursor(x, y uint) { + + if x > gg.SizeX || y > gg.SizeY { + panic("cursor position can not be larger than grid size") + } + + gg.CursorX = x + gg.CursorY = y +} + +func (gg *GlyphGrid) TickCursor(forceDown bool) (success bool) { + + if gg.CursorX == gg.SizeX-1 && gg.CursorY == gg.SizeY-1 { + // fmt.Println("trying to advance cursor beyond grid which is not allowed. Keeping cursor at position") + return false + } + + if forceDown { + + if gg.CursorY == gg.SizeY-1 { + // fmt.Println("trying to move cursor to next line but cursor already at last line. Keeping cursor at position") + return false + } + + gg.CursorX = 0 + gg.CursorY++ + return true + } + + gg.CursorX++ + if gg.CursorX >= gg.SizeX { + gg.CursorX = 0 + gg.CursorY++ + } + + return true +} + +func (gg *GlyphGrid) Print() { + + fmt.Println("\n---") + for y := 0; y < len(gg.Tiles); y++ { + + row := gg.Tiles[y] + for x := 0; x < len(row); x++ { + + if row[x].Glyph == utf8.RuneError { + break + } + fmt.Print(string(row[x].Glyph)) + } + + fmt.Print("\n") + } + fmt.Println("---") +} + +func NewGlyphGrid(width, height uint) *GlyphGrid { + + if width == 0 || height == 0 { + panic("glyph grid width and height must be larger than zero") + } + + tiles := make([][]GridTile, height) + for i := 0; i < len(tiles); i++ { + tiles[i] = make([]GridTile, width) + } + + return &GlyphGrid{ + CursorX: 0, + CursorY: 0, + SizeX: width, + SizeY: height, + Tiles: tiles, + } +} diff --git a/main.go b/main.go index 9dd2c68..ed5a510 100755 --- a/main.go +++ b/main.go @@ -91,7 +91,7 @@ type nterm struct { scrollPosRel int64 scrollSpd int64 - gridTiles [][]GridTile + glyphGrid *GlyphGrid activeCmd *Cmd Settings *Settings @@ -116,11 +116,6 @@ const ( defaultScrollSpd = 1 ) -type GridTile struct { - glyph rune - color *gglm.Vec4 -} - var ( drawGrid bool drawManyLines = false @@ -155,7 +150,7 @@ func main() { win: win, rend: rend, imguiInfo: nmageimgui.NewImGUI(), - FontSize: 40, + FontSize: 24, Lines: ring.NewBuffer[Line](defaultLineBufSize), @@ -220,7 +215,8 @@ func (nt *nterm) Init() { w, h := nt.win.SDLWin.GetSize() // p.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/tajawal-regular-var.ttf", &truetype.Options{Size: float64(p.FontSize), DPI: p.Dpi, SubPixelsX: subPixelX, SubPixelsY: subPixelY, Hinting: hinting}, w, h) - nt.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/alm-fixed.ttf", &truetype.Options{Size: float64(nt.FontSize), DPI: nt.Dpi, SubPixelsX: subPixelX, SubPixelsY: subPixelY, Hinting: hinting}, w, h) + // nt.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/alm-fixed.ttf", &truetype.Options{Size: float64(nt.FontSize), DPI: nt.Dpi, SubPixelsX: subPixelX, SubPixelsY: subPixelY, Hinting: hinting}, w, h) + nt.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/CascadiaMono-Regular.ttf", &truetype.Options{Size: float64(nt.FontSize), DPI: nt.Dpi, SubPixelsX: subPixelX, SubPixelsY: subPixelY, Hinting: hinting}, w, h) if err != nil { panic("Failed to create atlas from font file. Err: " + err.Error()) } @@ -241,8 +237,12 @@ func (nt *nterm) Init() { nt.gridMat = materials.NewMaterial("grid", "./res/shaders/grid.glsl") nt.HandleWindowResize() - //Set initial cursor pos + // Set initial cursor pos nt.lastCmdCharPos.SetY(nt.GlyphRend.Atlas.LineHeight) + + // Init glyph grid + gridWidth, gridHeight := nt.GridSize() + nt.glyphGrid = NewGlyphGrid(uint(gridWidth), uint(gridHeight)) } func (nt *nterm) Update() { @@ -258,7 +258,7 @@ func (nt *nterm) Update() { } //Font sizing - oldFont := nt.FontSize + oldFontSize := nt.FontSize fontSizeChanged := false if input.KeyClicked(sdl.K_KP_PLUS) { nt.FontSize += 2 @@ -272,10 +272,12 @@ func (nt *nterm) Update() { err := nt.GlyphRend.SetFace(&truetype.Options{Size: float64(nt.FontSize), DPI: nt.Dpi, SubPixelsX: subPixelX, SubPixelsY: subPixelY, Hinting: hinting}) if err != nil { - nt.FontSize = oldFont + nt.FontSize = oldFontSize fmt.Println("Failed to update font face. Err: " + err.Error()) } else { glyphs.SaveImgToPNG(nt.GlyphRend.Atlas.Img, "./debug-atlas.png") + gridWidth, gridHeight := nt.GridSize() + nt.glyphGrid = NewGlyphGrid(uint(gridWidth), uint(gridHeight)) fmt.Println("New font size:", nt.FontSize, "; New texture size:", nt.GlyphRend.Atlas.Img.Rect.Max.X) } } @@ -322,17 +324,38 @@ func (nt *nterm) MainUpdate() { nt.SepLinePos.SetY(2 * nt.GlyphRend.Atlas.LineHeight) // Draw textBuf + nt.glyphGrid.Clear() gw, gh := nt.GridSize() v1, v2 := nt.textBuf.ViewsFromToRelIndex(uint64(nt.scrollPosRel), uint64(nt.scrollPosRel)+uint64(gw*gh)) nt.lastCmdCharPos.Data = gglm.NewVec3(0, float32(nt.GlyphRend.ScreenHeight)-nt.GlyphRend.Atlas.LineHeight, 0).Data - nt.lastCmdCharPos.Data = nt.DrawTextAnsiCodes(v1, *nt.lastCmdCharPos).Data - nt.lastCmdCharPos.Data = nt.DrawTextAnsiCodes(v2, *nt.lastCmdCharPos).Data + nt.DrawTextAnsiCodesOnGlyphGrid(v1) + nt.DrawTextAnsiCodesOnGlyphGrid(v2) + + for y := 0; y < len(nt.glyphGrid.Tiles); y++ { + + row := nt.glyphGrid.Tiles[y] + for x := 0; x < len(row); x++ { + + g := row[x] + if g.Glyph == utf8.RuneError { + continue + } + nt.GlyphRend.OptValues.BgColor.Data = g.BgColor.Data + nt.lastCmdCharPos.Data = nt.GlyphRend.DrawTextOpenGLAbsRectWithStartPos([]rune{g.Glyph}, nt.lastCmdCharPos, gglm.NewVec3(0, 0, 0), gglm.NewVec2(float32(nt.GlyphRend.ScreenWidth), 2*nt.GlyphRend.Atlas.LineHeight), &g.FgColor).Data + } + } + nt.GlyphRend.OptValues.BgColor.Data = nt.Settings.DefaultBgColor.Data // Draw cmd buf nt.lastCmdCharPos.SetX(0) nt.lastCmdCharPos.SetY(nt.SepLinePos.Y() - nt.GlyphRend.Atlas.LineHeight) nt.lastCmdCharPos.Data = nt.SyntaxHighlightAndDraw(nt.cmdBuf[:nt.cmdBufLen], *nt.lastCmdCharPos).Data + + if input.KeyClicked(sdl.K_F4) { + nt.glyphGrid.Print() + println(nt.glyphGrid.SizeX, nt.glyphGrid.SizeY) + } } func (nt *nterm) ReadInputs() { @@ -394,33 +417,13 @@ func (nt *nterm) ReadInputs() { } } -func (nt *nterm) DrawTextAnsiCodes(bs []byte, pos gglm.Vec3) gglm.Vec3 { +func (nt *nterm) DrawTextAnsiCodesOnGlyphGrid(bs []byte) { currFgColor := nt.Settings.DefaultFgColor currBgColor := nt.Settings.DefaultBgColor draw := func(rs []rune) { - - nt.GlyphRend.OptValues.BgColor.Data = currBgColor.Data - - startIndex := 0 - for i := 0; i < len(rs); i++ { - - r := rs[i] - - // @PERF We could probably use bytes.IndexByte here - if r == '\n' { - pos.Data = nt.GlyphRend.DrawTextOpenGLAbsRectWithStartPos(rs[startIndex:i], &pos, gglm.NewVec3(0, 0, 0), gglm.NewVec2(float32(nt.GlyphRend.ScreenWidth), 2*nt.GlyphRend.Atlas.LineHeight), &currFgColor).Data - pos.SetX(0) - pos.AddY(-nt.GlyphRend.Atlas.LineHeight) - startIndex = i + 1 - continue - } - } - - if startIndex < len(rs) { - pos.Data = nt.GlyphRend.DrawTextOpenGLAbsRectWithStartPos(rs[startIndex:], &pos, gglm.NewVec3(0, 0, 0), gglm.NewVec2(float32(nt.GlyphRend.ScreenWidth), 2*nt.GlyphRend.Atlas.LineHeight), &currFgColor).Data - } + nt.glyphGrid.Write(rs, &currFgColor, &currBgColor) } for { @@ -457,8 +460,6 @@ func (nt *nterm) DrawTextAnsiCodes(bs []byte, pos gglm.Vec3) gglm.Vec3 { // Advance beyond the code chars bs = bs[index+len(code):] } - - return pos } func (nt *nterm) SyntaxHighlightAndDraw(text []rune, pos gglm.Vec3) gglm.Vec3 { @@ -858,9 +859,6 @@ func (nt *nterm) HandleWindowResize() { projMtx := gglm.Ortho(0, float32(w), float32(h), 0, 0.1, 20) viewMtx := gglm.LookAt(gglm.NewVec3(0, 0, -10), gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0)) nt.gridMat.SetUnifMat4("projViewMat", &projMtx.Mul(viewMtx).Mat4) - - gridWidth, gridHeight := nt.GridSize() - nt.gridTiles = make([][]GridTile, gridWidth*gridHeight) } func (nt *nterm) WriteToTextBuf(text []byte) { diff --git a/res/fonts/CascadiaMono-Regular.ttf b/res/fonts/CascadiaMono-Regular.ttf new file mode 100755 index 0000000..d6cdcaa Binary files /dev/null and b/res/fonts/CascadiaMono-Regular.ttf differ