mirror of
https://github.com/bloeys/gopad.git
synced 2025-12-29 15:08:21 +00:00
Starting custom editor window
This commit is contained in:
189
editor.go
Executable file
189
editor.go
Executable file
@ -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
|
||||||
|
}
|
||||||
BIN
gopad-icon.ico
BIN
gopad-icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 32 KiB |
BIN
gopad-logo.png
BIN
gopad-logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 34 KiB |
71
main.go
Normal file → Executable file
71
main.go
Normal file → Executable file
@ -16,13 +16,6 @@ import (
|
|||||||
|
|
||||||
//TODO: Cache os.ReadDir so we don't have to use lots of disk
|
//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 {
|
type Gopad struct {
|
||||||
Win *engine.Window
|
Win *engine.Window
|
||||||
mainFont imgui.Font
|
mainFont imgui.Font
|
||||||
@ -45,10 +38,6 @@ type Gopad struct {
|
|||||||
haveErr bool
|
haveErr bool
|
||||||
errMsg string
|
errMsg string
|
||||||
|
|
||||||
//Settings
|
|
||||||
textSelectionColor imgui.Vec4
|
|
||||||
editorBgColor imgui.Vec4
|
|
||||||
|
|
||||||
//Cache window size
|
//Cache window size
|
||||||
winWidth float32
|
winWidth float32
|
||||||
winHeight float32
|
winHeight float32
|
||||||
@ -82,14 +71,9 @@ func main() {
|
|||||||
Win: window,
|
Win: window,
|
||||||
ImGUIInfo: nmageimgui.NewImGUI(),
|
ImGUIInfo: nmageimgui.NewImGUI(),
|
||||||
CurrDir: dir,
|
CurrDir: dir,
|
||||||
editors: []Editor{
|
editors: []Editor{*NewScratchEditor()},
|
||||||
{fileName: "**scratch**"},
|
|
||||||
},
|
|
||||||
editorToClose: -1,
|
editorToClose: -1,
|
||||||
|
|
||||||
sidebarWidthFactor: 0.15,
|
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)
|
// engine.SetVSync(true)
|
||||||
@ -128,9 +112,9 @@ func (g *Gopad) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g.editors = append(g.editors, Editor{
|
g.editors = append(g.editors, Editor{
|
||||||
fileName: filepath.Base(os.Args[i]),
|
FileName: filepath.Base(os.Args[i]),
|
||||||
filePath: os.Args[i],
|
FilePath: os.Args[i],
|
||||||
fileContents: string(b),
|
FileContents: string(b),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +174,7 @@ func (g *Gopad) Update() {
|
|||||||
|
|
||||||
//Save if needed
|
//Save if needed
|
||||||
e := g.getActiveEditor()
|
e := g.getActiveEditor()
|
||||||
if !e.isModified {
|
if !e.IsModified {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,18 +187,18 @@ func (g *Gopad) Update() {
|
|||||||
|
|
||||||
func (g *Gopad) saveEditor(e *Editor) {
|
func (g *Gopad) saveEditor(e *Editor) {
|
||||||
|
|
||||||
if e.fileName == "**scratch**" {
|
if e.FileName == "**scratch**" {
|
||||||
e.isModified = false
|
e.IsModified = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.WriteFile(e.filePath, []byte(e.fileContents), os.ModePerm)
|
err := os.WriteFile(e.FilePath, []byte(e.FileContents), os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.triggerError("Failed to save file. Error: " + err.Error())
|
g.triggerError("Failed to save file. Error: " + err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.isModified = false
|
e.IsModified = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gopad) triggerError(errMsg string) {
|
func (g *Gopad) triggerError(errMsg string) {
|
||||||
@ -316,12 +300,12 @@ func (g *Gopad) drawEditors() {
|
|||||||
flags = imgui.TabItemFlagsSetSelected
|
flags = imgui.TabItemFlagsSetSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.isModified {
|
if e.IsModified {
|
||||||
flags |= imgui.TabItemFlagsUnsavedDocument
|
flags |= imgui.TabItemFlagsUnsavedDocument
|
||||||
}
|
}
|
||||||
|
|
||||||
open := true
|
open := true
|
||||||
if !imgui.BeginTabItemV(e.fileName, &open, flags) {
|
if !imgui.BeginTabItemV(e.FileName, &open, flags) {
|
||||||
|
|
||||||
if !open {
|
if !open {
|
||||||
g.editorToClose = i
|
g.editorToClose = i
|
||||||
@ -350,19 +334,8 @@ func (g *Gopad) drawEditors() {
|
|||||||
imgui.End()
|
imgui.End()
|
||||||
|
|
||||||
//Draw text area
|
//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 {
|
if shouldForceSwitch || prevActiveEditor != g.activeEditor {
|
||||||
imgui.SetKeyboardFocusHereV(-1)
|
imgui.SetKeyboardFocusHereV(-1)
|
||||||
}
|
}
|
||||||
@ -372,11 +345,6 @@ func (g *Gopad) drawEditors() {
|
|||||||
imgui.End()
|
imgui.End()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gopad) textEditCB(d imgui.InputTextCallbackData) int32 {
|
|
||||||
g.getActiveEditor().isModified = true
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Gopad) getActiveEditor() *Editor {
|
func (g *Gopad) getActiveEditor() *Editor {
|
||||||
return g.getEditor(g.activeEditor)
|
return g.getEditor(g.activeEditor)
|
||||||
}
|
}
|
||||||
@ -384,7 +352,7 @@ func (g *Gopad) getActiveEditor() *Editor {
|
|||||||
func (g *Gopad) getEditor(index int) *Editor {
|
func (g *Gopad) getEditor(index int) *Editor {
|
||||||
|
|
||||||
if len(g.editors) == 0 {
|
if len(g.editors) == 0 {
|
||||||
e := Editor{fileName: "**scratch**"}
|
e := Editor{FileName: "**scratch**"}
|
||||||
g.editors = append(g.editors, e)
|
g.editors = append(g.editors, e)
|
||||||
g.activeEditor = 0
|
g.activeEditor = 0
|
||||||
return &e
|
return &e
|
||||||
@ -430,7 +398,7 @@ func (g *Gopad) handleFileClick(fPath string) {
|
|||||||
for i := 0; i < len(g.editors); i++ {
|
for i := 0; i < len(g.editors); i++ {
|
||||||
|
|
||||||
e := &g.editors[i]
|
e := &g.editors[i]
|
||||||
if e.filePath == fPath {
|
if e.FilePath == fPath {
|
||||||
editorIndex = i
|
editorIndex = i
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -443,16 +411,7 @@ func (g *Gopad) handleFileClick(fPath string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Read new file and switch to it
|
//Read new file and switch to it
|
||||||
b, err := os.ReadFile(fPath)
|
g.editors = append(g.editors, *NewEditor(fPath))
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.editors = append(g.editors, Editor{
|
|
||||||
fileName: filepath.Base(fPath),
|
|
||||||
filePath: fPath,
|
|
||||||
fileContents: string(b),
|
|
||||||
})
|
|
||||||
g.activeEditor = len(g.editors) - 1
|
g.activeEditor = len(g.editors) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
settings/settings.go
Executable file
8
settings/settings.go
Executable file
@ -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}
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user