mirror of
https://github.com/bloeys/gopad.git
synced 2025-12-29 06:58:21 +00:00
359 lines
7.3 KiB
Go
359 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
|
|
"github.com/bloeys/nmage/engine"
|
|
"github.com/bloeys/nmage/input"
|
|
"github.com/bloeys/nmage/logging"
|
|
nmageimgui "github.com/bloeys/nmage/ui/imgui"
|
|
"github.com/inkyblackness/imgui-go/v4"
|
|
"github.com/veandco/go-sdl2/sdl"
|
|
)
|
|
|
|
//TODO: Cache os.ReadDir so we don't have to use lots of disk
|
|
|
|
type Editor struct {
|
|
fileName string
|
|
filePath string
|
|
fileContents string
|
|
}
|
|
|
|
type Gopad struct {
|
|
Win *engine.Window
|
|
mainFont imgui.Font
|
|
ImGUIInfo nmageimgui.ImguiInfo
|
|
Quitting bool
|
|
|
|
sidebarSize float32
|
|
|
|
CurrDir string
|
|
CurrDirContents []fs.DirEntry
|
|
|
|
editors []Editor
|
|
editorToClose int
|
|
activeEditor int
|
|
lastActiveEditor int
|
|
}
|
|
|
|
var ()
|
|
|
|
func main() {
|
|
|
|
if err := engine.Init(); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
window, err := engine.CreateOpenGLWindowCentered("nMage", 1280, 720, engine.WindowFlags_RESIZABLE|engine.WindowFlags_ALLOW_HIGHDPI)
|
|
if err != nil {
|
|
logging.ErrLog.Fatalln("Failed to create window. Err: ", err)
|
|
}
|
|
defer window.Destroy()
|
|
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
g := Gopad{
|
|
Win: window,
|
|
ImGUIInfo: nmageimgui.NewImGUI(),
|
|
CurrDir: dir,
|
|
editors: []Editor{
|
|
{fileName: "**scratch**"},
|
|
},
|
|
editorToClose: -1,
|
|
}
|
|
|
|
// engine.SetVSync(true)
|
|
engine.Run(&g)
|
|
}
|
|
|
|
func (g *Gopad) Init() {
|
|
|
|
g.Win.EventCallbacks = append(g.Win.EventCallbacks, g.handleWindowEvents)
|
|
|
|
//Setup font
|
|
var fontSize float32 = 16
|
|
fConfig := imgui.NewFontConfig()
|
|
defer fConfig.Delete()
|
|
|
|
fConfig.SetOversampleH(2)
|
|
fConfig.SetOversampleV(2)
|
|
g.mainFont = g.ImGUIInfo.AddFontTTF("./res/fonts/courier-prime.regular.ttf", fontSize, &fConfig, nil)
|
|
|
|
//Sidebar
|
|
g.CurrDirContents = getDirContents(g.CurrDir)
|
|
|
|
w, _ := g.Win.SDLWin.GetSize()
|
|
g.sidebarSize = float32(w) * 0.10
|
|
|
|
}
|
|
|
|
func (g *Gopad) handleWindowEvents(event sdl.Event) {
|
|
|
|
switch e := event.(type) {
|
|
|
|
case *sdl.TextEditingEvent:
|
|
case *sdl.TextInputEvent:
|
|
// g.buffer = append(g.buffer, []rune(e.GetText())...)
|
|
|
|
case *sdl.WindowEvent:
|
|
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
|
|
w, _ := g.Win.SDLWin.GetSize()
|
|
g.sidebarSize = float32(w) * 0.10
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *Gopad) FrameStart() {
|
|
|
|
//Remove deleted editors
|
|
if g.editorToClose == -1 {
|
|
return
|
|
}
|
|
|
|
g.editors = append(g.editors[:g.editorToClose], g.editors[g.editorToClose+1:]...)
|
|
g.editorToClose = -1
|
|
|
|
if g.activeEditor >= len(g.editors) {
|
|
g.activeEditor = len(g.editors) - 1
|
|
}
|
|
|
|
g.lastActiveEditor = g.activeEditor
|
|
}
|
|
|
|
func (g *Gopad) Update() {
|
|
|
|
if input.IsQuitClicked() {
|
|
g.Quitting = true
|
|
}
|
|
|
|
if x, _ := input.GetMousePos(); x > int32(g.sidebarSize) && input.MouseClicked(sdl.BUTTON_LEFT) {
|
|
sdl.StartTextInput()
|
|
}
|
|
|
|
if input.KeyClicked(sdl.K_ESCAPE) {
|
|
sdl.StopTextInput()
|
|
}
|
|
|
|
// if input.KeyClicked(sdl.K_BACKSPACE) && len(g.buffer) > 0 {
|
|
// g.buffer = g.buffer[:len(g.buffer)-1]
|
|
// }
|
|
|
|
// if input.KeyClicked(sdl.K_RETURN) || input.KeyClicked(sdl.K_RETURN2) {
|
|
// g.buffer = append(g.buffer, rune('\n'))
|
|
// }
|
|
}
|
|
|
|
func (g *Gopad) Render() {
|
|
|
|
//Global imgui settings
|
|
imgui.PushFont(g.mainFont)
|
|
|
|
g.drawSidebar()
|
|
g.drawEditors()
|
|
|
|
imgui.PopFont()
|
|
}
|
|
|
|
func (g *Gopad) drawSidebar() {
|
|
|
|
_, h := g.Win.SDLWin.GetSize()
|
|
imgui.SetNextWindowPos(imgui.Vec2{X: 0, Y: 0})
|
|
imgui.SetNextWindowSize(imgui.Vec2{X: g.sidebarSize, Y: float32(h)})
|
|
imgui.BeginV("sidebar", nil, imgui.WindowFlagsNoCollapse|imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoMove)
|
|
|
|
imgui.PushStyleColor(imgui.StyleColorButton, imgui.Vec4{W: 0})
|
|
for i := 0; i < len(g.CurrDirContents); i++ {
|
|
|
|
c := g.CurrDirContents[i]
|
|
if c.IsDir() {
|
|
g.drawDir(c, g.CurrDir+"/"+c.Name()+"/")
|
|
} else {
|
|
g.drawFile(c, g.CurrDir+"/"+c.Name())
|
|
}
|
|
}
|
|
|
|
imgui.PopStyleColor()
|
|
imgui.End()
|
|
}
|
|
|
|
func (g *Gopad) drawEditors() {
|
|
|
|
//Draw editor area window
|
|
w, h := g.Win.SDLWin.GetSize()
|
|
imgui.SetNextWindowPos(imgui.Vec2{X: g.sidebarSize, Y: 0})
|
|
imgui.SetNextWindowSize(imgui.Vec2{X: float32(w) - g.sidebarSize})
|
|
imgui.BeginV("editor", nil, imgui.WindowFlagsNoCollapse|imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoMove)
|
|
|
|
//Draw tabs
|
|
isEditorsEnabled := imgui.BeginTabBarV("editorTabs", 0)
|
|
for i := 0; i < len(g.editors); i++ {
|
|
|
|
e := &g.editors[i]
|
|
|
|
shouldForceSwitch := g.activeEditor == i && g.activeEditor != g.lastActiveEditor
|
|
flags := imgui.TabItemFlagsNone
|
|
if shouldForceSwitch {
|
|
flags = imgui.TabItemFlagsSetSelected
|
|
}
|
|
|
|
open := true
|
|
if !imgui.BeginTabItemV(e.fileName, &open, flags) {
|
|
|
|
if !open {
|
|
g.editorToClose = i
|
|
}
|
|
continue
|
|
}
|
|
if !open {
|
|
g.editorToClose = i
|
|
}
|
|
|
|
//If these two aren't equal it means we programmatically changed the active editor (instead of a mouse click),
|
|
//and so we shouldn't change based on what imgui is telling us
|
|
if g.activeEditor == g.lastActiveEditor {
|
|
g.activeEditor = i
|
|
g.lastActiveEditor = i
|
|
}
|
|
imgui.EndTabItem()
|
|
}
|
|
g.lastActiveEditor = g.activeEditor
|
|
|
|
if isEditorsEnabled {
|
|
imgui.EndTabBar()
|
|
}
|
|
|
|
tabsHeight := imgui.WindowHeight()
|
|
imgui.End()
|
|
|
|
//Draw text area
|
|
fullWinSize := imgui.Vec2{X: float32(w) - g.sidebarSize, Y: float32(h) - tabsHeight}
|
|
imgui.SetNextWindowPos(imgui.Vec2{X: g.sidebarSize, Y: tabsHeight})
|
|
imgui.SetNextWindowSize(fullWinSize)
|
|
imgui.BeginV("editorText", nil, imgui.WindowFlagsNoCollapse|imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoMove)
|
|
|
|
imgui.PushStyleColor(imgui.StyleColorFrameBg, imgui.Vec4{X: 0.1, Y: 0.1, Z: 0.1, W: 1})
|
|
fullWinSize.Y -= 18
|
|
imgui.InputTextMultilineV("", &g.getActiveEditor().fileContents, fullWinSize, 0, nil)
|
|
imgui.PopStyleColor()
|
|
imgui.End()
|
|
}
|
|
|
|
// func (g *Gopad) textEditCB(d imgui.InputTextCallbackData) int32 {
|
|
// return 0
|
|
// }
|
|
|
|
func (g *Gopad) getActiveEditor() *Editor {
|
|
return g.getEditor(g.activeEditor)
|
|
}
|
|
|
|
func (g *Gopad) getEditor(index int) *Editor {
|
|
|
|
if len(g.editors) == 0 {
|
|
e := Editor{fileName: "**scratch**"}
|
|
g.editors = append(g.editors, e)
|
|
g.activeEditor = 0
|
|
return &e
|
|
}
|
|
|
|
if index >= 0 && index < len(g.editors) {
|
|
return &g.editors[index]
|
|
}
|
|
|
|
panic(fmt.Sprint("Invalid editor index: ", index))
|
|
}
|
|
|
|
func (g *Gopad) drawDir(dir fs.DirEntry, path string) {
|
|
|
|
isEnabled := imgui.TreeNodeV(dir.Name(), imgui.TreeNodeFlagsSpanAvailWidth)
|
|
if !isEnabled {
|
|
return
|
|
}
|
|
|
|
contents := getDirContents(path)
|
|
for _, c := range contents {
|
|
if c.IsDir() {
|
|
g.drawDir(c, path+c.Name()+"/")
|
|
} else {
|
|
g.drawFile(c, path+c.Name())
|
|
}
|
|
}
|
|
|
|
imgui.TreePop()
|
|
}
|
|
|
|
func (g *Gopad) drawFile(f fs.DirEntry, path string) {
|
|
|
|
if imgui.Button(f.Name()) {
|
|
g.handleFileClick(path)
|
|
}
|
|
}
|
|
|
|
func (g *Gopad) handleFileClick(fPath string) {
|
|
|
|
//Check if we already have the file open
|
|
editorIndex := -1
|
|
for i := 0; i < len(g.editors); i++ {
|
|
|
|
e := &g.editors[i]
|
|
if e.filePath == fPath {
|
|
editorIndex = i
|
|
break
|
|
}
|
|
}
|
|
|
|
//If already found switch to it
|
|
if editorIndex >= 0 {
|
|
g.activeEditor = editorIndex
|
|
return
|
|
}
|
|
|
|
//Read new file and switch to it
|
|
b, err := os.ReadFile(fPath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
g.editors = append(g.editors, Editor{
|
|
fileName: path.Base(fPath),
|
|
filePath: fPath,
|
|
fileContents: string(b),
|
|
})
|
|
g.activeEditor = len(g.editors) - 1
|
|
}
|
|
|
|
func (g *Gopad) FrameEnd() {
|
|
|
|
}
|
|
|
|
func (g *Gopad) ShouldRun() bool {
|
|
return !g.Quitting
|
|
}
|
|
|
|
func (g *Gopad) GetWindow() *engine.Window {
|
|
return g.Win
|
|
}
|
|
|
|
func (g *Gopad) GetImGUI() nmageimgui.ImguiInfo {
|
|
return g.ImGUIInfo
|
|
}
|
|
|
|
func (g *Gopad) Deinit() {
|
|
g.Win.Destroy()
|
|
}
|
|
|
|
func getDirContents(dir string) []fs.DirEntry {
|
|
|
|
contents, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return contents
|
|
}
|