mirror of
https://github.com/bloeys/gopad.git
synced 2025-12-29 15:08:21 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4fc3feda9 | |||
| 1f62c3ae60 | |||
| 5b15017fc6 | |||
| 967c9815a0 | |||
| cf20686a43 | |||
| b0733e9a0c | |||
| 0419ea06d1 | |||
| ea2e757d50 | |||
| 48d0ed975e | |||
| 7df1b81221 | |||
| d84ca2aebb | |||
| 02b5f94ee1 | |||
| 3c3b480ec5 | |||
| eb3ce8e466 | |||
| f562ec674b | |||
| f0c4a82b88 | |||
| a98edabc90 | |||
| d99b122ee5 | |||
| ceda79cffb |
11
README.md
11
README.md
@ -1,3 +1,14 @@
|
||||
# gopad
|
||||
|
||||
Notepad in Golang and using nMage and ImGUI.
|
||||
|
||||
## Developer Details
|
||||
|
||||
### Hide Console on Windows
|
||||
|
||||
Running the executable on windows opens a terminal as well. To hide that we build with: `go build -ldflags -H=windowsgui .`
|
||||
|
||||
### Program Icon
|
||||
|
||||
`*.syso` files are used by `go build` on Windows to add information to the executable that Windows can read, such as version and icon.
|
||||
We use `github.com/tc-hib/go-winres` to generate these via the command: `go-winres simply --icon gopad-icon.ico --manifest gui`
|
||||
|
||||
528
editor.go
Executable file
528
editor.go
Executable file
@ -0,0 +1,528 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
imgui "github.com/AllenDang/cimgui-go"
|
||||
"github.com/bloeys/gopad/settings"
|
||||
)
|
||||
|
||||
const (
|
||||
linesPerNode = 100
|
||||
textPadding = 10
|
||||
)
|
||||
|
||||
type Line struct {
|
||||
// @TODO: This will explode on long lines.
|
||||
// We can use the same strategy of line nodes here, where a line is something like
|
||||
// type Line struct {
|
||||
// Line [RunesPerLine]rune
|
||||
// Next *Line
|
||||
// }
|
||||
chars []rune
|
||||
}
|
||||
|
||||
type LinesNode struct {
|
||||
Lines [linesPerNode]Line
|
||||
Next *LinesNode
|
||||
}
|
||||
|
||||
type Editor struct {
|
||||
FileName string
|
||||
FilePath string
|
||||
FileContents string
|
||||
|
||||
MouseX int
|
||||
MouseY int
|
||||
|
||||
SelectionStart int
|
||||
SelectionLength int
|
||||
|
||||
IsModified bool
|
||||
|
||||
LinesHead *LinesNode
|
||||
LineCount int
|
||||
|
||||
LineHeight float32
|
||||
CharWidth float32
|
||||
|
||||
StartPos float32
|
||||
}
|
||||
|
||||
type MousePosInfo struct {
|
||||
|
||||
//Global represents a grid on the whole window (i.e. 0,0 is the top left of the program window)
|
||||
GridXGlobal int
|
||||
//Global represents a grid on the whole window (i.e. 0,0 is the top left of the program window)
|
||||
GridYGlobal int
|
||||
|
||||
//Editor represents a grid on the editor window (i.e. 0,0 is the top left of the editor window)
|
||||
GridXEditor int
|
||||
//Editor represents a grid on the editor window (i.e. 0,0 is the top left of the editor window)
|
||||
GridYEditor int
|
||||
|
||||
//Line is the currently selected line
|
||||
Line *Line
|
||||
LineNum int
|
||||
}
|
||||
|
||||
func (e *Editor) SetCursorPos(x, y int) {
|
||||
e.MouseX = x
|
||||
e.MouseY = y
|
||||
}
|
||||
|
||||
func (e *Editor) SetStartPos(mouseDeltaNorm int32) {
|
||||
e.StartPos = clampF32(e.StartPos+float32(-mouseDeltaNorm)*settings.ScrollSpeed, 0, float32(e.LineCount))
|
||||
}
|
||||
|
||||
func (e *Editor) RefreshFontSettings() {
|
||||
e.LineHeight = imgui.TextLineHeightWithSpacing()
|
||||
|
||||
//NOTE: Because of 'https://github.com/ocornut/imgui/issues/792', CalcTextSize returns slightly incorrect width
|
||||
//values for sentences than to be expected with singleCharWidth*sentenceCharCount. For example, with 3 chars at width 10
|
||||
//we expect width of 30 (for a fixed-width font), but instead we might get 29.
|
||||
// This is fixed in the newer releases, but imgui-go hasn't updated yet.
|
||||
//
|
||||
//That's why instead of getting width of one char, we get the average width from the width of a sentence, which helps us position
|
||||
//cursors properly for now
|
||||
e.CharWidth = imgui.CalcTextSizeV("abcdefghijklmnopqrstuvwxyz", false, 1000).X / 26
|
||||
}
|
||||
|
||||
func (e *Editor) RoundToGridX(x float32) float32 {
|
||||
return clampF32(float32(math.Round(float64(x/e.CharWidth)))*e.CharWidth, 0, math.MaxFloat32)
|
||||
}
|
||||
|
||||
func (e *Editor) RoundToGridY(x float32) float32 {
|
||||
return clampF32(float32(math.Round(float64(x/e.LineHeight)))*e.LineHeight, 0, math.MaxFloat32)
|
||||
}
|
||||
|
||||
func (e *Editor) UpdateAndDraw(drawStartPos, winSize *imgui.Vec2, newRunes []rune) {
|
||||
|
||||
//Draw window
|
||||
imgui.SetNextWindowPos(*drawStartPos)
|
||||
imgui.SetNextWindowSize(*winSize)
|
||||
imgui.BeginV("editorText", nil, imgui.WindowFlagsNoCollapse|imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoMove)
|
||||
|
||||
imgui.PushStyleColorVec4(imgui.ColFrameBg, settings.EditorBgColor)
|
||||
imgui.PushStyleColorVec4(imgui.ColTextSelectedBg, settings.TextSelectionColor)
|
||||
|
||||
imgui.SetNextItemWidth(winSize.X)
|
||||
|
||||
// We want different lables so multiple editors don't mess eaach other, but we don't want the label to show
|
||||
// so we prefix the whole thing with ##
|
||||
if imgui.InputTextMultiline("##"+e.FilePath, &e.FileContents, imgui.Vec2{X: winSize.X - winSize.X*0.02, Y: winSize.Y - winSize.Y*0.02}, imgui.InputTextFlagsNone|imgui.InputTextFlagsAllowTabInput, nil) {
|
||||
e.IsModified = true
|
||||
}
|
||||
|
||||
// @NOTE: Commented out until rewrite that doesn't use imgui as an editor is complete
|
||||
// //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 += textPadding
|
||||
// drawStartPos.Y += textPadding
|
||||
// paddedDrawStartPos := *drawStartPos
|
||||
|
||||
// //Make edits
|
||||
// posInfo := e.getPositions(&paddedDrawStartPos)
|
||||
// e.Insert(&posInfo, newRunes)
|
||||
|
||||
// if input.KeyClickedCaptured(sdl.K_LEFT) {
|
||||
// e.MoveMouseXByChars(-1, &posInfo)
|
||||
// } else if input.KeyClickedCaptured(sdl.K_RIGHT) {
|
||||
// e.MoveMouseXByChars(1, &posInfo)
|
||||
// }
|
||||
|
||||
// if input.KeyClickedCaptured(sdl.K_UP) {
|
||||
// e.MoveMouseYByLines(-1, &posInfo)
|
||||
// } else if input.KeyClickedCaptured(sdl.K_DOWN) {
|
||||
// e.MoveMouseYByLines(1, &posInfo)
|
||||
// }
|
||||
|
||||
// if input.KeyClickedCaptured(sdl.K_BACKSPACE) {
|
||||
// e.Delete(&posInfo, 1)
|
||||
// }
|
||||
|
||||
// //Draw text
|
||||
// dl := imgui.WindowDrawList()
|
||||
// linesToDraw := int(winSize.Y / e.LineHeight)
|
||||
// startLine := clampInt(int(e.StartPos), 0, e.LineCount)
|
||||
// for i := startLine; i < startLine+linesToDraw; i++ {
|
||||
// dl.AddText(*drawStartPos, imgui.PackedColorFromVec4(imgui.Vec4{X: 1, Y: 1, Z: 1, W: 1}), string(e.GetLine(0+i).chars))
|
||||
// drawStartPos.Y += e.LineHeight
|
||||
// }
|
||||
|
||||
// tabCount, charsToOffsetBy := getTabs(posInfo.Line, posInfo.GridXEditor)
|
||||
// textWidth := float32(len(posInfo.Line.chars)-tabCount+tabCount*settings.TabSize) * e.CharWidth
|
||||
// lineX := clampF32(float32(posInfo.GridXGlobal)+float32(charsToOffsetBy)*e.CharWidth, 0, paddedDrawStartPos.X+textWidth)
|
||||
|
||||
// lineStart := imgui.Vec2{
|
||||
// X: lineX,
|
||||
// Y: paddedDrawStartPos.Y + float32(posInfo.GridYEditor)*e.LineHeight - e.LineHeight*0.25,
|
||||
// }
|
||||
// lineEnd := imgui.Vec2{
|
||||
// X: lineX,
|
||||
// Y: paddedDrawStartPos.Y + float32(posInfo.GridYEditor)*e.LineHeight + e.LineHeight*0.75,
|
||||
// }
|
||||
// dl.AddLineV(lineStart, lineEnd, imgui.PackedColorFromVec4(settings.CursorColor), settings.CursorWidthFactor*e.CharWidth)
|
||||
|
||||
// // charAtCursor := getCharFromCursor(clickedLine, clickedColGridXEditor)
|
||||
// // println("Chars:", "'"+charAtCursor+"'", ";", clickedColGridXEditor)
|
||||
}
|
||||
|
||||
func (e *Editor) Insert(posInfo *MousePosInfo, rs []rune) {
|
||||
|
||||
if len(rs) == 0 {
|
||||
return
|
||||
}
|
||||
e.IsModified = true
|
||||
|
||||
l := posInfo.Line
|
||||
if len(l.chars) == 0 {
|
||||
l.chars = append(l.chars, rs...)
|
||||
return
|
||||
}
|
||||
|
||||
charIndex := getCharIndexFromCursor(posInfo.Line, posInfo.GridXEditor)
|
||||
if charIndex == -1 {
|
||||
charIndex = 0
|
||||
} else if charIndex == len(l.chars)-1 {
|
||||
l.chars = append(l.chars, rs...)
|
||||
return
|
||||
} else {
|
||||
charIndex++
|
||||
}
|
||||
|
||||
//Make a new array that can accomodate the changes
|
||||
c := l.chars
|
||||
newLength := len(c) + len(rs)
|
||||
l.chars = make([]rune, newLength)
|
||||
|
||||
//Copy the left half (before the changes), then copy the changes in the new space, and
|
||||
//lastly copy the pushed elements to the right of the changes
|
||||
copy(l.chars, c[:charIndex])
|
||||
copy(l.chars[charIndex:], rs)
|
||||
copy(l.chars[charIndex+len(rs):], c[charIndex:])
|
||||
|
||||
e.MoveMouseXByChars(len(rs), posInfo)
|
||||
}
|
||||
|
||||
func (e *Editor) Delete(posInfo *MousePosInfo, count int) {
|
||||
|
||||
l := posInfo.Line
|
||||
if len(l.chars) == 0 {
|
||||
return
|
||||
}
|
||||
e.IsModified = true
|
||||
|
||||
if count >= len(l.chars) {
|
||||
l.chars = []rune{}
|
||||
return
|
||||
}
|
||||
|
||||
charIndex := getCharIndexFromCursor(l, posInfo.GridXEditor)
|
||||
if charIndex == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
//Count tabs that will be deleted
|
||||
tabCount := 0
|
||||
for i := charIndex - count + 1; i < charIndex+1; i++ {
|
||||
if l.chars[i] == '\t' {
|
||||
tabCount++
|
||||
}
|
||||
}
|
||||
|
||||
l.chars = append(l.chars[:charIndex-count+1], l.chars[charIndex+1:]...)
|
||||
|
||||
if tabCount == 0 {
|
||||
e.MoveMouseXByChars(-1, posInfo)
|
||||
} else {
|
||||
e.MoveMouseXByChars(-tabCount*settings.TabSize, posInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Editor) MoveMouseXByChars(charCount int, posInfo *MousePosInfo) {
|
||||
|
||||
if posInfo.GridXEditor == 0 && charCount < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
delta := float32(charCount) * e.CharWidth
|
||||
e.MouseX = int(e.RoundToGridX(float32(e.MouseX) + delta))
|
||||
posInfo.GridXGlobal = int(e.RoundToGridX(float32(posInfo.GridXGlobal) + delta))
|
||||
posInfo.GridXEditor = int(e.RoundToGridX(float32(posInfo.GridXEditor) + delta))
|
||||
}
|
||||
|
||||
func (e *Editor) MoveMouseYByLines(lineCount int, posInfo *MousePosInfo) {
|
||||
|
||||
if lineCount < 0 && posInfo.LineNum == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if lineCount > 0 && posInfo.LineNum == e.LineCount {
|
||||
return
|
||||
}
|
||||
|
||||
delta := float32(lineCount) * e.LineHeight
|
||||
e.MouseY = int(e.RoundToGridY(float32(e.MouseY) + delta))
|
||||
posInfo.GridYGlobal = int(e.RoundToGridY(float32(posInfo.GridYGlobal) + delta))
|
||||
posInfo.GridYEditor = int(e.RoundToGridY(float32(posInfo.GridYEditor) + delta))
|
||||
}
|
||||
|
||||
func (e *Editor) getPositions(paddedDrawStartPos *imgui.Vec2) MousePosInfo {
|
||||
|
||||
//Calculate position of cursor in window and grid coords.
|
||||
//Window coords are as reported by SDL, but we correct for padding and snap to the nearest
|
||||
//char window pos.
|
||||
//
|
||||
//Since Gopad only supports fixed-width fonts, we treat the text area as a grid with each
|
||||
//cell having identical width and one char inside.
|
||||
//
|
||||
//'Global' suffix means the position is in window coords.
|
||||
//'Editor' suffix means coords are within the text editor coords, where sidebar and tabs have been adjusted for
|
||||
|
||||
roundedMouseX := e.RoundToGridX(float32(e.MouseX))
|
||||
gridXGlobal := clampInt(int(roundedMouseX), 0, math.MaxInt)
|
||||
|
||||
roundedMouseY := e.RoundToGridY(float32(e.MouseY))
|
||||
gridYGlobal := clampInt(int(roundedMouseY), 0, math.MaxInt)
|
||||
|
||||
gridXEditor := int(
|
||||
roundF32(
|
||||
(float32(gridXGlobal) - paddedDrawStartPos.X) / e.CharWidth,
|
||||
),
|
||||
)
|
||||
|
||||
windowYEditor := clampInt(e.MouseY-int(paddedDrawStartPos.Y), 0, math.MaxInt)
|
||||
gridYEditor := clampInt(windowYEditor/int(e.LineHeight), 0, e.LineCount)
|
||||
|
||||
startLineIndex := clampInt(int(e.StartPos), 0, e.LineCount)
|
||||
return MousePosInfo{
|
||||
|
||||
GridXGlobal: gridXGlobal,
|
||||
GridYGlobal: gridYGlobal,
|
||||
|
||||
GridXEditor: gridXEditor,
|
||||
GridYEditor: gridYEditor,
|
||||
|
||||
Line: e.GetLine(startLineIndex + gridYEditor),
|
||||
LineNum: startLineIndex + gridYEditor,
|
||||
}
|
||||
}
|
||||
|
||||
func getCharFromCursor(l *Line, cursorGridX int) string {
|
||||
i := getCharIndexFromCursor(l, cursorGridX)
|
||||
if i == -1 {
|
||||
return ""
|
||||
}
|
||||
return string(l.chars[i])
|
||||
}
|
||||
|
||||
func getCharIndexFromCursor(l *Line, cursorGridX int) int {
|
||||
|
||||
if cursorGridX <= 0 || len(l.chars) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
gridSize := 0
|
||||
for i := 0; i < len(l.chars) && i <= cursorGridX; i++ {
|
||||
|
||||
if l.chars[i] == '\t' {
|
||||
gridSize += settings.TabSize
|
||||
} else {
|
||||
gridSize++
|
||||
}
|
||||
|
||||
if gridSize < cursorGridX {
|
||||
continue
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
if cursorGridX >= gridSize {
|
||||
return len(l.chars) - 1
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// TODO: The offset chars must be how many grid cols between cursor col and the nearest non-tab char.
|
||||
func getTabs(l *Line, gridPosX int) (tabCount, charsToOffsetBy int) {
|
||||
|
||||
charIndex := getCharIndexFromCursor(l, gridPosX)
|
||||
if charIndex == -1 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
//gridSize represents the visual grid (e.g. \tHi has a visual grid size of 'tabSize+2')
|
||||
gridSize := 0
|
||||
for i := charIndex; i >= 0; i-- {
|
||||
if l.chars[i] == '\t' {
|
||||
tabCount++
|
||||
gridSize += settings.TabSize
|
||||
} else {
|
||||
gridSize++
|
||||
}
|
||||
}
|
||||
|
||||
if l.chars[charIndex] != '\t' {
|
||||
return tabCount, 0
|
||||
}
|
||||
|
||||
return tabCount, gridSize - gridPosX
|
||||
}
|
||||
|
||||
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()
|
||||
if len(fileContents) == 0 {
|
||||
return head, 0
|
||||
}
|
||||
|
||||
lineCount := 0
|
||||
start := 0
|
||||
end := 0
|
||||
currLine := 0
|
||||
currNode := head
|
||||
|
||||
// @PERF: Would be a lot faster to use something like bytes index
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if fileContents[len(fileContents)-1] != '\n' {
|
||||
lineCount++
|
||||
currNode.Lines[currLine].chars = []rune(fileContents[start:end])
|
||||
}
|
||||
|
||||
return head, lineCount
|
||||
}
|
||||
|
||||
func NewLineNode() *LinesNode {
|
||||
|
||||
n := LinesNode{}
|
||||
for i := 0; i < len(n.Lines); i++ {
|
||||
n.Lines[i].chars = []rune{}
|
||||
}
|
||||
|
||||
return &n
|
||||
}
|
||||
|
||||
func clampF32(x, min, max float32) float32 {
|
||||
|
||||
if x > max {
|
||||
return max
|
||||
}
|
||||
|
||||
if x < min {
|
||||
return min
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
func clampInt(x, min, max int) int {
|
||||
|
||||
if x > max {
|
||||
return max
|
||||
}
|
||||
|
||||
if x < min {
|
||||
return min
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
func roundF32(x float32) float32 {
|
||||
return float32(math.Round(float64(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.RefreshFontSettings()
|
||||
e.LinesHead, e.LineCount = ParseLines(e.FileContents)
|
||||
return e
|
||||
}
|
||||
71
file_dialogs_windows.go
Executable file
71
file_dialogs_windows.go
Executable file
@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
comdlg32 = syscall.NewLazyDLL("Comdlg32.dll")
|
||||
procGetOpenFileName = comdlg32.NewProc("GetOpenFileNameW")
|
||||
)
|
||||
|
||||
var (
|
||||
Err_No_File_Chosen = fmt.Errorf("no file was chosen")
|
||||
)
|
||||
|
||||
func OpenFileDialog(title string) (filepath string, err error) {
|
||||
|
||||
type OPENFILENAMEW struct {
|
||||
LStructSize uint32
|
||||
HwndOwner syscall.Handle
|
||||
HInstance syscall.Handle
|
||||
Filter *uint16
|
||||
CustomFilter *uint16
|
||||
MaxCustFilter uint32
|
||||
FilterIndex uint32
|
||||
File *uint16
|
||||
MaxFile uint32
|
||||
FileTitle *uint16
|
||||
MaxFileTitle uint32
|
||||
InitialDir *uint16
|
||||
Title *uint16
|
||||
Flags uint32
|
||||
FileOffset uint16
|
||||
FileExtension uint16
|
||||
DefExt *uint16
|
||||
CustData uintptr
|
||||
FnHook syscall.Handle
|
||||
TemplateName *uint16
|
||||
PvReserved uintptr
|
||||
DwReserved uint32
|
||||
FlagsEx uint32
|
||||
}
|
||||
|
||||
titleByteArr := utf16.Encode([]rune(title + "\x00"))
|
||||
filterByteArr := utf16.Encode([]rune("All Files (*.*)\x00*.*\x00Text Files (*.txt)\x00*.txt\x00\x00"))
|
||||
|
||||
filenameByteArr := make([]uint16, syscall.MAX_LONG_PATH)
|
||||
|
||||
ofn := &OPENFILENAMEW{
|
||||
HwndOwner: 0,
|
||||
HInstance: 0,
|
||||
LStructSize: uint32(unsafe.Sizeof(OPENFILENAMEW{})),
|
||||
Title: &titleByteArr[0],
|
||||
Filter: &filterByteArr[0],
|
||||
File: &filenameByteArr[0],
|
||||
MaxFile: syscall.MAX_LONG_PATH,
|
||||
// OFN_FILEMUSTEXIST
|
||||
Flags: 0x00001000,
|
||||
}
|
||||
|
||||
ret, _, _ := procGetOpenFileName.Call(uintptr(unsafe.Pointer(ofn)))
|
||||
if ret == 0 {
|
||||
return "", Err_No_File_Chosen
|
||||
}
|
||||
|
||||
filepath = syscall.UTF16ToString(filenameByteArr)
|
||||
return filepath, nil
|
||||
}
|
||||
11
go.mod
11
go.mod
@ -1,14 +1,15 @@
|
||||
module github.com/bloeys/gopad
|
||||
|
||||
go 1.17
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/bloeys/nmage v0.0.8
|
||||
github.com/inkyblackness/imgui-go/v4 v4.3.0
|
||||
github.com/veandco/go-sdl2 v0.4.14
|
||||
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2
|
||||
github.com/bloeys/nmage v0.23.1
|
||||
github.com/veandco/go-sdl2 v0.4.35
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bloeys/gglm v0.3.1 // indirect
|
||||
github.com/bloeys/assimp-go v0.4.4 // indirect
|
||||
github.com/bloeys/gglm v0.43.0 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
|
||||
)
|
||||
|
||||
28
go.sum
28
go.sum
@ -1,20 +1,12 @@
|
||||
github.com/bloeys/assimp-go v0.4.2/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
|
||||
github.com/bloeys/gglm v0.3.1 h1:Sy9upW7SBsBfDXrSmEhid3aQ+7J7itej+upwcxOnPMQ=
|
||||
github.com/bloeys/gglm v0.3.1/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
|
||||
github.com/bloeys/nmage v0.0.8 h1:HCoEaTBWTucnXrjQ+8OCUTzG/3rjpV1eliXWUW44+FY=
|
||||
github.com/bloeys/nmage v0.0.8/go.mod h1:4h2tKtMvk9ab8r/+rem4QonPXEBTho6VWvpCMm0M6iM=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2 h1:3HA/5qD8Rimxz/y1sLyVaM7ws1dzjXzMt4hOBiwHggo=
|
||||
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2/go.mod h1:iNfbIyOBN8k3XScMxULbrwYbPsXEAUD0Jb6UwrspQb8=
|
||||
github.com/bloeys/assimp-go v0.4.4 h1:Yn5e/RpE0Oes0YMBy8O7KkwAO4R/RpgrZPJCt08dVIU=
|
||||
github.com/bloeys/assimp-go v0.4.4/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
|
||||
github.com/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
|
||||
github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
|
||||
github.com/bloeys/nmage v0.23.1 h1:+DxcvM1KcDqiij6B50wchHa2h9XHkfIrIjEk87wj5zw=
|
||||
github.com/bloeys/nmage v0.23.1/go.mod h1:0hQAs1tCB/EOVwoac0zl+vsANkqGP9EOKc8cM/+3Now=
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/inkyblackness/imgui-go/v4 v4.3.0 h1:iyAzqWXq/dG5+6ckDPhGivtrIo6AywGQMvENKzun04s=
|
||||
github.com/inkyblackness/imgui-go/v4 v4.3.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/veandco/go-sdl2 v0.4.10/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
github.com/veandco/go-sdl2 v0.4.14 h1:ShagETHJG8NCWVn9rwfZ9WLIaN4c2maw3gfFH+9DlOg=
|
||||
github.com/veandco/go-sdl2 v0.4.14/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
github.com/veandco/go-sdl2 v0.4.35 h1:NohzsfageDWGtCd9nf7Pc3sokMK/MOK+UA2QMJARWzQ=
|
||||
github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
|
||||
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 |
227
main.go
Normal file → Executable file
227
main.go
Normal file → Executable file
@ -6,28 +6,24 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
imgui "github.com/AllenDang/cimgui-go"
|
||||
"github.com/bloeys/gopad/settings"
|
||||
"github.com/bloeys/nmage/engine"
|
||||
"github.com/bloeys/nmage/input"
|
||||
"github.com/bloeys/nmage/logging"
|
||||
"github.com/bloeys/nmage/renderer/rend3dgl"
|
||||
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
|
||||
isModified bool
|
||||
}
|
||||
// @TODO:
|
||||
// - Cache os.ReadDir so we don't have to use lots of disk
|
||||
// - Seems undo is broken
|
||||
|
||||
type Gopad struct {
|
||||
Win *engine.Window
|
||||
mainFont imgui.Font
|
||||
ImGUIInfo nmageimgui.ImguiInfo
|
||||
Quitting bool
|
||||
|
||||
mainMenuBarHeight float32
|
||||
sidebarWidthFactor float32
|
||||
@ -40,22 +36,17 @@ type Gopad struct {
|
||||
editorToClose int
|
||||
activeEditor int
|
||||
lastActiveEditor int
|
||||
newRunes []rune
|
||||
|
||||
//Errors
|
||||
haveErr bool
|
||||
errMsg string
|
||||
|
||||
//Settings
|
||||
textSelectionColor imgui.Vec4
|
||||
editorBgColor imgui.Vec4
|
||||
|
||||
//Cache window size
|
||||
winWidth float32
|
||||
winHeight float32
|
||||
}
|
||||
|
||||
var ()
|
||||
|
||||
func main() {
|
||||
|
||||
chdirErr := os.Chdir(filepath.Dir(os.Args[0]))
|
||||
@ -67,7 +58,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
window, err := engine.CreateOpenGLWindowCentered("nMage", 1280, 720, engine.WindowFlags_RESIZABLE|engine.WindowFlags_ALLOW_HIGHDPI)
|
||||
window, err := engine.CreateOpenGLWindowCentered("nMage", 1280, 720, engine.WindowFlags_RESIZABLE|engine.WindowFlags_ALLOW_HIGHDPI, rend3dgl.NewRend3DGL())
|
||||
if err != nil {
|
||||
logging.ErrLog.Fatalln("Failed to create window. Err: ", err)
|
||||
}
|
||||
@ -79,21 +70,36 @@ func main() {
|
||||
}
|
||||
|
||||
g := Gopad{
|
||||
Win: window,
|
||||
ImGUIInfo: nmageimgui.NewImGUI(),
|
||||
CurrDir: dir,
|
||||
editors: []Editor{
|
||||
{fileName: "**scratch**"},
|
||||
},
|
||||
Win: window,
|
||||
ImGUIInfo: nmageimgui.NewImGui(""),
|
||||
CurrDir: dir,
|
||||
editors: []Editor{NewScratchEditor()},
|
||||
editorToClose: -1,
|
||||
|
||||
// This is to force a focus on the scratch editr on startup
|
||||
lastActiveEditor: -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},
|
||||
newRunes: []rune{},
|
||||
}
|
||||
|
||||
// engine.SetVSync(true)
|
||||
engine.Run(&g)
|
||||
// Init runs within an imgui frame, but imgui frames do NOT allow adding fonts,
|
||||
// so we do it here
|
||||
g.LoadFonts()
|
||||
|
||||
engine.SetVSync(true)
|
||||
engine.Run(&g, g.Win, g.ImGUIInfo)
|
||||
}
|
||||
|
||||
func (g *Gopad) LoadFonts() {
|
||||
|
||||
fConfig := imgui.NewFontConfig()
|
||||
fConfig.SetOversampleH(2)
|
||||
fConfig.SetOversampleV(2)
|
||||
|
||||
g.mainFont = g.ImGUIInfo.AddFontTTF("./res/fonts/courier-prime.regular.ttf", settings.FontSize, &fConfig, nil)
|
||||
|
||||
fConfig.Destroy()
|
||||
}
|
||||
|
||||
func (g *Gopad) Init() {
|
||||
@ -101,15 +107,6 @@ func (g *Gopad) Init() {
|
||||
g.Win.SDLWin.SetTitle("Gopad")
|
||||
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)
|
||||
|
||||
@ -120,21 +117,21 @@ func (g *Gopad) Init() {
|
||||
|
||||
//Read os.Args
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
b, err := os.ReadFile(os.Args[i])
|
||||
if err != nil {
|
||||
errMsg := "Error opening file. Error: " + err.Error()
|
||||
println(errMsg)
|
||||
continue
|
||||
}
|
||||
|
||||
g.editors = append(g.editors, Editor{
|
||||
fileName: filepath.Base(os.Args[i]),
|
||||
filePath: os.Args[i],
|
||||
fileContents: string(b),
|
||||
})
|
||||
e := NewEditor(os.Args[i])
|
||||
g.editors = append(g.editors, e)
|
||||
g.activeEditor = len(g.editors) - 1
|
||||
}
|
||||
|
||||
g.activeEditor = len(g.editors) - 1
|
||||
|
||||
// Prepare editors
|
||||
imgui.PushFont(g.mainFont)
|
||||
for i := 0; i < len(g.editors); i++ {
|
||||
e := &g.editors[i]
|
||||
e.RefreshFontSettings()
|
||||
}
|
||||
imgui.PopFont()
|
||||
}
|
||||
|
||||
func (g *Gopad) handleWindowEvents(event sdl.Event) {
|
||||
@ -143,6 +140,7 @@ func (g *Gopad) handleWindowEvents(event sdl.Event) {
|
||||
|
||||
case *sdl.TextEditingEvent:
|
||||
case *sdl.TextInputEvent:
|
||||
g.newRunes = append(g.newRunes, []rune(e.GetText())...)
|
||||
case *sdl.WindowEvent:
|
||||
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
|
||||
w, h := g.Win.SDLWin.GetSize()
|
||||
@ -153,14 +151,6 @@ func (g *Gopad) handleWindowEvents(event sdl.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gopad) FrameStart() {
|
||||
|
||||
if g.editorToClose > -1 {
|
||||
g.closeEditor(g.editorToClose)
|
||||
g.editorToClose = -1
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gopad) closeEditor(eIndex int) {
|
||||
|
||||
g.editors = append(g.editors[:eIndex], g.editors[eIndex+1:]...)
|
||||
@ -175,50 +165,69 @@ func (g *Gopad) closeEditor(eIndex int) {
|
||||
func (g *Gopad) Update() {
|
||||
|
||||
if input.IsQuitClicked() {
|
||||
g.Quitting = true
|
||||
engine.Quit()
|
||||
return
|
||||
}
|
||||
|
||||
if g.haveErr {
|
||||
g.showErrorPopup()
|
||||
}
|
||||
|
||||
if input.KeyDownCaptured(sdl.K_LCTRL) && input.KeyClickedCaptured(sdl.K_o) {
|
||||
|
||||
filepath, err := OpenFileDialog("Open")
|
||||
if err != nil && err != Err_No_File_Chosen {
|
||||
g.triggerError(err.Error())
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
g.loadFileFromPath(filepath)
|
||||
}
|
||||
}
|
||||
|
||||
if input.MouseClickedCaptued(sdl.BUTTON_LEFT) {
|
||||
x, y := input.GetMousePos()
|
||||
g.getActiveEditor().SetCursorPos(int(x), int(y))
|
||||
}
|
||||
|
||||
if yMove := input.GetMouseWheelYNormCaptured(); yMove != 0 {
|
||||
g.getActiveEditor().SetStartPos(yMove)
|
||||
}
|
||||
|
||||
//Close editor if needed
|
||||
if input.KeyDown(sdl.K_LCTRL) && input.KeyClicked(sdl.K_w) {
|
||||
if input.KeyDownCaptured(sdl.K_LCTRL) && input.KeyClickedCaptured(sdl.K_w) {
|
||||
g.closeEditor(g.activeEditor)
|
||||
g.editorToClose = -1
|
||||
}
|
||||
|
||||
//Save if needed
|
||||
e := g.getActiveEditor()
|
||||
if !e.isModified {
|
||||
return
|
||||
}
|
||||
|
||||
if !input.KeyDown(sdl.K_LCTRL) || !input.KeyClicked(sdl.K_s) {
|
||||
return
|
||||
//Save if needed
|
||||
if e.IsModified {
|
||||
if input.KeyDownCaptured(sdl.K_LCTRL) && input.KeyClickedCaptured(sdl.K_s) {
|
||||
g.saveEditor(e)
|
||||
}
|
||||
}
|
||||
|
||||
g.saveEditor(e)
|
||||
}
|
||||
|
||||
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) {
|
||||
imgui.OpenPopup("err")
|
||||
imgui.OpenPopupStr("err")
|
||||
g.haveErr = true
|
||||
g.errMsg = errMsg
|
||||
}
|
||||
@ -261,7 +270,7 @@ func (g *Gopad) drawMenubar() {
|
||||
|
||||
if imgui.BeginMenu("File") {
|
||||
|
||||
if imgui.MenuItem("Save") {
|
||||
if imgui.MenuItemBool("Save") {
|
||||
g.saveEditor(g.getActiveEditor())
|
||||
}
|
||||
|
||||
@ -280,7 +289,7 @@ func (g *Gopad) drawSidebar() {
|
||||
imgui.SetNextWindowSize(imgui.Vec2{X: g.sidebarWidthPx, Y: g.winHeight - g.mainMenuBarHeight})
|
||||
imgui.BeginV("sidebar", nil, imgui.WindowFlagsNoCollapse|imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoMove)
|
||||
|
||||
imgui.PushStyleColor(imgui.StyleColorButton, imgui.Vec4{W: 0})
|
||||
imgui.PushStyleColorVec4(imgui.ColButton, imgui.Vec4{W: 0})
|
||||
for i := 0; i < len(g.CurrDirContents); i++ {
|
||||
|
||||
c := g.CurrDirContents[i]
|
||||
@ -311,17 +320,17 @@ func (g *Gopad) drawEditors() {
|
||||
|
||||
e := &g.editors[i]
|
||||
|
||||
flags := imgui.TabItemFlagsNone
|
||||
var flags imgui.TabItemFlags = imgui.TabItemFlagsNone
|
||||
if shouldForceSwitch && g.activeEditor == i {
|
||||
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
|
||||
@ -349,20 +358,12 @@ func (g *Gopad) drawEditors() {
|
||||
tabsHeight := imgui.WindowHeight()
|
||||
imgui.End()
|
||||
|
||||
//Draw text area
|
||||
imgui.SetNextWindowPos(imgui.Vec2{X: g.sidebarWidthPx, Y: g.mainMenuBarHeight + tabsHeight})
|
||||
g.getActiveEditor().UpdateAndDraw(
|
||||
&imgui.Vec2{X: g.sidebarWidthPx, Y: g.mainMenuBarHeight + tabsHeight},
|
||||
&imgui.Vec2{X: g.winWidth - g.sidebarWidthPx, Y: g.winHeight - g.mainMenuBarHeight - tabsHeight},
|
||||
g.newRunes,
|
||||
)
|
||||
|
||||
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 +373,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 +380,8 @@ func (g *Gopad) getActiveEditor() *Editor {
|
||||
func (g *Gopad) getEditor(index int) *Editor {
|
||||
|
||||
if len(g.editors) == 0 {
|
||||
e := Editor{fileName: "**scratch**"}
|
||||
e := NewScratchEditor()
|
||||
e.RefreshFontSettings()
|
||||
g.editors = append(g.editors, e)
|
||||
g.activeEditor = 0
|
||||
return &e
|
||||
@ -399,7 +396,7 @@ func (g *Gopad) getEditor(index int) *Editor {
|
||||
|
||||
func (g *Gopad) drawDir(dir fs.DirEntry, path string) {
|
||||
|
||||
isEnabled := imgui.TreeNodeV(dir.Name(), imgui.TreeNodeFlagsSpanAvailWidth)
|
||||
isEnabled := imgui.TreeNodeExStrV(dir.Name(), imgui.TreeNodeFlagsSpanAvailWidth)
|
||||
if !isEnabled {
|
||||
return
|
||||
}
|
||||
@ -419,18 +416,18 @@ func (g *Gopad) drawDir(dir fs.DirEntry, path string) {
|
||||
func (g *Gopad) drawFile(f fs.DirEntry, path string) {
|
||||
|
||||
if imgui.Button(f.Name()) {
|
||||
g.handleFileClick(path)
|
||||
g.loadFileFromPath(path)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gopad) handleFileClick(fPath string) {
|
||||
func (g *Gopad) loadFileFromPath(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 {
|
||||
if e.FilePath == fPath {
|
||||
editorIndex = i
|
||||
break
|
||||
}
|
||||
@ -443,35 +440,25 @@ func (g *Gopad) handleFileClick(fPath string) {
|
||||
}
|
||||
|
||||
//Read new file and switch to it
|
||||
b, err := os.ReadFile(fPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e := NewEditor(fPath)
|
||||
e.RefreshFontSettings()
|
||||
|
||||
g.editors = append(g.editors, Editor{
|
||||
fileName: filepath.Base(fPath),
|
||||
filePath: fPath,
|
||||
fileContents: string(b),
|
||||
})
|
||||
g.editors = append(g.editors, e)
|
||||
g.activeEditor = len(g.editors) - 1
|
||||
}
|
||||
|
||||
func (g *Gopad) FrameEnd() {
|
||||
|
||||
g.newRunes = []rune{}
|
||||
|
||||
// Close editors if needed
|
||||
if g.editorToClose > -1 {
|
||||
g.closeEditor(g.editorToClose)
|
||||
g.editorToClose = -1
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
func (g *Gopad) DeInit() {
|
||||
g.Win.Destroy()
|
||||
}
|
||||
|
||||
|
||||
BIN
rsrc_windows_386.syso
Executable file
BIN
rsrc_windows_386.syso
Executable file
Binary file not shown.
BIN
rsrc_windows_amd64.syso
Executable file
BIN
rsrc_windows_amd64.syso
Executable file
Binary file not shown.
@ -1,57 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/inkyblackness/imgui-go/v4"
|
||||
)
|
||||
|
||||
func selectFolder(startDir string, winWidth float32, winHeight float32) (path string, done bool) {
|
||||
|
||||
if strings.TrimSpace(startDir) == "" {
|
||||
var err error
|
||||
startDir, err = os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
imgui.OpenPopup("selectFolder")
|
||||
|
||||
imgui.SetNextWindowPos(imgui.Vec2{X: float32(winWidth) * 0.5, Y: float32(winHeight) * 0.5})
|
||||
shouldEnd := imgui.BeginPopupModalV("selectFolder", nil, imgui.WindowFlagsNoCollapse)
|
||||
|
||||
drawDir(startDir, true)
|
||||
|
||||
if shouldEnd {
|
||||
imgui.EndPopup()
|
||||
} else {
|
||||
done = true
|
||||
}
|
||||
|
||||
return path, done
|
||||
}
|
||||
|
||||
func drawDir(fPath string, foldersOnly bool) {
|
||||
|
||||
// contents, err := os.ReadDir(fPath)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// for _, c := range contents {
|
||||
|
||||
// if !c.IsDir() {
|
||||
// continue
|
||||
// }
|
||||
|
||||
// isEnabled := imgui.TreeNodeV( dir.Name(), imgui.TreeNodeFlagsSpanAvailWidth)
|
||||
// if !isEnabled {
|
||||
// return
|
||||
// }
|
||||
|
||||
// imgui.bu
|
||||
// }
|
||||
|
||||
}
|
||||
15
settings/settings.go
Executable file
15
settings/settings.go
Executable file
@ -0,0 +1,15 @@
|
||||
package settings
|
||||
|
||||
import imgui "github.com/AllenDang/cimgui-go"
|
||||
|
||||
var (
|
||||
FontSize float32 = 16
|
||||
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}
|
||||
|
||||
//NOTE: Imgui hardcodes tab size to 4 in '#define IM_TABSIZE 4'
|
||||
TabSize int = 4
|
||||
ScrollSpeed float32 = 4
|
||||
CursorWidthFactor float32 = 0.15
|
||||
CursorColor imgui.Vec4 = imgui.Vec4{X: 1, Y: 1, Z: 1, W: 1}
|
||||
)
|
||||
Reference in New Issue
Block a user