diff --git a/editor.go b/editor.go new file mode 100755 index 0000000..983115d --- /dev/null +++ b/editor.go @@ -0,0 +1,189 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/bloeys/gopad/settings" + "github.com/inkyblackness/imgui-go/v4" +) + +const ( + linesPerNode int = 100 +) + +type Line struct { + chars []rune +} + +type LinesNode struct { + Lines [linesPerNode]Line + Next *LinesNode +} + +type Editor struct { + FileName string + FilePath string + FileContents string + + CursorX int + CursorY int + + SelectionStart int + SelectionLength int + + IsModified bool + + LinesHead *LinesNode + LineCount int +} + +func (e *Editor) SetCursorPos(x, y int) { + e.CursorX = x + e.CursorY = y +} + +func (e *Editor) Render(drawStartPos, winSize *imgui.Vec2) { + + e.CursorY = clampInt(e.CursorY, 0, e.LineCount) + e.CursorX = clampInt(e.CursorX, 0, e.GetLineCharCount(e.CursorY)) + + //Draw window + imgui.SetNextWindowPos(*drawStartPos) + imgui.SetNextWindowSize(*winSize) + imgui.BeginV("editorText", nil, imgui.WindowFlagsNoCollapse|imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoMove) + + imgui.PushStyleColor(imgui.StyleColorFrameBg, settings.EditorBgColor) + imgui.PushStyleColor(imgui.StyleColorTextSelectedBg, settings.TextSelectionColor) + + //Add padding to text + drawStartPos.X += 10 + drawStartPos.Y += 10 + + //Draw lines + lineHeight := imgui.TextLineHeightWithSpacing() + linesToDraw := int(winSize.Y / lineHeight) + println("Lines to draw:", linesToDraw) + + dl := imgui.WindowDrawList() + for i := 0; i < linesToDraw; i++ { + dl.AddText(*drawStartPos, imgui.PackedColorFromVec4(imgui.Vec4{X: 1, Y: 1, Z: 1, W: 1}), string(e.GetLine(e.CursorY+i).chars)) + drawStartPos.Y += lineHeight + } +} + +func (e *Editor) GetLine(lineNum int) *Line { + + if lineNum > e.LineCount { + return &Line{ + chars: []rune{}, + } + } + + curr := e.LinesHead + + //Advance to correct node + nodeNum := lineNum / linesPerNode + lineNum -= nodeNum * linesPerNode + for nodeNum > 0 { + curr = curr.Next + nodeNum-- + } + + return &curr.Lines[lineNum] +} + +func (e *Editor) GetLineCharCount(lineNum int) int { + + curr := e.LinesHead + + //Advance to correct node + nodeNum := lineNum / linesPerNode + lineNum -= nodeNum * linesPerNode + for nodeNum > 0 { + curr = curr.Next + nodeNum-- + } + + return len(curr.Lines[lineNum].chars) +} + +func ParseLines(fileContents string) (*LinesNode, int) { + + head := NewLineNode() + + lineCount := 0 + start := 0 + end := 0 + currLine := 0 + currNode := head + for i := 0; i < len(fileContents); i++ { + + if fileContents[i] != '\n' { + end++ + continue + } + + lineCount++ + currNode.Lines[currLine].chars = []rune(fileContents[start:end]) + + end++ + start = end + currLine++ + if currLine == linesPerNode { + currLine = 0 + currNode.Next = NewLineNode() + currNode = currNode.Next + } + } + + return head, lineCount +} + +func NewLineNode() *LinesNode { + + n := LinesNode{} + for i := 0; i < len(n.Lines); i++ { + n.Lines[i].chars = []rune{} + } + + return &n +} + +func clampInt(x, min, max int) int { + + if x > max { + return max + } + + if x < min { + return min + } + + return x +} + +func NewScratchEditor() *Editor { + + e := &Editor{ + FileName: "**scratch**", + LinesHead: NewLineNode(), + } + return e +} + +func NewEditor(fPath string) *Editor { + + b, err := os.ReadFile(fPath) + if err != nil { + panic(err) + } + + e := &Editor{ + FileName: filepath.Base(fPath), + FilePath: fPath, + FileContents: string(b), + } + e.LinesHead, e.LineCount = ParseLines(e.FileContents) + return e +} diff --git a/gopad-icon.ico b/gopad-icon.ico index f4b8d88..e98097e 100755 Binary files a/gopad-icon.ico and b/gopad-icon.ico differ diff --git a/gopad-logo.png b/gopad-logo.png index 2784b17..61850e7 100755 Binary files a/gopad-logo.png and b/gopad-logo.png differ diff --git a/main.go b/main.go old mode 100644 new mode 100755 index 4ea335d..d7dc075 --- a/main.go +++ b/main.go @@ -16,13 +16,6 @@ import ( //TODO: Cache os.ReadDir so we don't have to use lots of disk -type Editor struct { - fileName string - filePath string - fileContents string - isModified bool -} - type Gopad struct { Win *engine.Window mainFont imgui.Font @@ -45,10 +38,6 @@ type Gopad struct { haveErr bool errMsg string - //Settings - textSelectionColor imgui.Vec4 - editorBgColor imgui.Vec4 - //Cache window size winWidth float32 winHeight float32 @@ -79,17 +68,12 @@ func main() { } g := Gopad{ - Win: window, - ImGUIInfo: nmageimgui.NewImGUI(), - CurrDir: dir, - editors: []Editor{ - {fileName: "**scratch**"}, - }, - editorToClose: -1, - + Win: window, + ImGUIInfo: nmageimgui.NewImGUI(), + CurrDir: dir, + editors: []Editor{*NewScratchEditor()}, + editorToClose: -1, sidebarWidthFactor: 0.15, - textSelectionColor: imgui.Vec4{X: 84 / 255.0, Y: 153 / 255.0, Z: 199 / 255.0, W: 0.4}, - editorBgColor: imgui.Vec4{X: 0.1, Y: 0.1, Z: 0.1, W: 1}, } // engine.SetVSync(true) @@ -128,9 +112,9 @@ func (g *Gopad) Init() { } g.editors = append(g.editors, Editor{ - fileName: filepath.Base(os.Args[i]), - filePath: os.Args[i], - fileContents: string(b), + FileName: filepath.Base(os.Args[i]), + FilePath: os.Args[i], + FileContents: string(b), }) } @@ -190,7 +174,7 @@ func (g *Gopad) Update() { //Save if needed e := g.getActiveEditor() - if !e.isModified { + if !e.IsModified { return } @@ -203,18 +187,18 @@ func (g *Gopad) Update() { func (g *Gopad) saveEditor(e *Editor) { - if e.fileName == "**scratch**" { - e.isModified = false + if e.FileName == "**scratch**" { + e.IsModified = false return } - err := os.WriteFile(e.filePath, []byte(e.fileContents), os.ModePerm) + err := os.WriteFile(e.FilePath, []byte(e.FileContents), os.ModePerm) if err != nil { g.triggerError("Failed to save file. Error: " + err.Error()) return } - e.isModified = false + e.IsModified = false } func (g *Gopad) triggerError(errMsg string) { @@ -316,12 +300,12 @@ func (g *Gopad) drawEditors() { flags = imgui.TabItemFlagsSetSelected } - if e.isModified { + if e.IsModified { flags |= imgui.TabItemFlagsUnsavedDocument } open := true - if !imgui.BeginTabItemV(e.fileName, &open, flags) { + if !imgui.BeginTabItemV(e.FileName, &open, flags) { if !open { g.editorToClose = i @@ -350,19 +334,8 @@ func (g *Gopad) drawEditors() { imgui.End() //Draw text area - imgui.SetNextWindowPos(imgui.Vec2{X: g.sidebarWidthPx, Y: g.mainMenuBarHeight + tabsHeight}) + g.getActiveEditor().Render(&imgui.Vec2{X: g.sidebarWidthPx, Y: g.mainMenuBarHeight + tabsHeight}, &imgui.Vec2{X: g.winWidth - g.sidebarWidthPx, Y: g.winHeight - g.mainMenuBarHeight - tabsHeight}) - fullWinSize := imgui.Vec2{X: g.winWidth - g.sidebarWidthPx, Y: g.winHeight - g.mainMenuBarHeight - tabsHeight} - imgui.SetNextWindowSize(fullWinSize) - imgui.BeginV("editorText", nil, imgui.WindowFlagsNoCollapse|imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoMove) - - imgui.PushStyleColor(imgui.StyleColorFrameBg, g.editorBgColor) - imgui.PushStyleColor(imgui.StyleColorTextSelectedBg, g.textSelectionColor) - - fullWinSize.Y -= 18 - - e := g.getActiveEditor() - imgui.InputTextMultilineV(e.fileName, &e.fileContents, fullWinSize, imgui.ImGuiInputTextFlagsCallbackEdit|imgui.InputTextFlagsAllowTabInput, g.textEditCB) if shouldForceSwitch || prevActiveEditor != g.activeEditor { imgui.SetKeyboardFocusHereV(-1) } @@ -372,11 +345,6 @@ func (g *Gopad) drawEditors() { imgui.End() } -func (g *Gopad) textEditCB(d imgui.InputTextCallbackData) int32 { - g.getActiveEditor().isModified = true - return 0 -} - func (g *Gopad) getActiveEditor() *Editor { return g.getEditor(g.activeEditor) } @@ -384,7 +352,7 @@ func (g *Gopad) getActiveEditor() *Editor { func (g *Gopad) getEditor(index int) *Editor { if len(g.editors) == 0 { - e := Editor{fileName: "**scratch**"} + e := Editor{FileName: "**scratch**"} g.editors = append(g.editors, e) g.activeEditor = 0 return &e @@ -430,7 +398,7 @@ func (g *Gopad) handleFileClick(fPath string) { for i := 0; i < len(g.editors); i++ { e := &g.editors[i] - if e.filePath == fPath { + if e.FilePath == fPath { editorIndex = i break } @@ -443,16 +411,7 @@ func (g *Gopad) handleFileClick(fPath string) { } //Read new file and switch to it - b, err := os.ReadFile(fPath) - if err != nil { - panic(err) - } - - g.editors = append(g.editors, Editor{ - fileName: filepath.Base(fPath), - filePath: fPath, - fileContents: string(b), - }) + g.editors = append(g.editors, *NewEditor(fPath)) g.activeEditor = len(g.editors) - 1 } diff --git a/settings/settings.go b/settings/settings.go new file mode 100755 index 0000000..f6480bc --- /dev/null +++ b/settings/settings.go @@ -0,0 +1,8 @@ +package settings + +import "github.com/inkyblackness/imgui-go/v4" + +var ( + TextSelectionColor imgui.Vec4 = imgui.Vec4{X: 84 / 255.0, Y: 153 / 255.0, Z: 199 / 255.0, W: 0.4} + EditorBgColor imgui.Vec4 = imgui.Vec4{X: 0.1, Y: 0.1, Z: 0.1, W: 1} +)