Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

8835 lines (7355 sloc) 281.082 kb
// A work in progress implementation of Conception in Go.
package main
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/printer"
"go/token"
"io"
"io/ioutil"
"log"
"math"
"net/http"
_ "net/http/pprof"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
text_scanner "text/scanner"
"time"
"github.com/bradfitz/iter"
"github.com/go-gl/gl/v2.1/gl"
"github.com/go-gl/glfw/v3.1/glfw"
"github.com/go-gl/mathgl/mgl64"
intmath "github.com/pkg/math"
"github.com/shurcooL/go-goon"
"github.com/shurcooL/go-goon/bypass"
"github.com/shurcooL/go/exp/11"
"github.com/shurcooL/go/exp/12"
"github.com/shurcooL/go/exp/13"
"github.com/shurcooL/go/exp/14"
. "github.com/shurcooL/go/gists/gist4727543"
. "github.com/shurcooL/go/gists/gist5258650"
. "github.com/shurcooL/go/gists/gist5259939"
. "github.com/shurcooL/go/gists/gist5423254"
. "github.com/shurcooL/go/gists/gist5504644"
. "github.com/shurcooL/go/gists/gist5639599"
. "github.com/shurcooL/go/gists/gist5953185"
. "github.com/shurcooL/go/gists/gist6003701"
. "github.com/shurcooL/go/gists/gist6096872"
. "github.com/shurcooL/go/gists/gist6418290"
. "github.com/shurcooL/go/gists/gist6418462"
. "github.com/shurcooL/go/gists/gist6445065"
"github.com/shurcooL/go/gists/gist6545684"
. "github.com/shurcooL/go/gists/gist7390843"
. "github.com/shurcooL/go/gists/gist7480523"
. "github.com/shurcooL/go/gists/gist7576154"
. "github.com/shurcooL/go/gists/gist7576804"
. "github.com/shurcooL/go/gists/gist7651991"
. "github.com/shurcooL/go/gists/gist7802150"
"github.com/shurcooL/go/gists/gist8065433"
"github.com/shurcooL/go/markdown_http"
"github.com/shurcooL/go/pipe_util"
"github.com/shurcooL/go/trim"
"github.com/shurcooL/go/u/u10"
"github.com/shurcooL/go/u/u4"
"github.com/shurcooL/go/u/u5"
"github.com/shurcooL/go/u/u6"
"github.com/shurcooL/go/vcs"
"github.com/shurcooL/go/vfs_util"
"github.com/shurcooL/gostatus/status"
"github.com/shurcooL/markdownfmt/markdown"
"golang.org/x/net/websocket"
"golang.org/x/tools/go/types"
"golang.org/x/tools/godoc/vfs"
goimports "golang.org/x/tools/imports"
"gopkg.in/pipe.v2"
"honnef.co/go/importer"
"sourcegraph.com/sourcegraph/go-diff/diff"
sg_vcs "sourcegraph.com/sourcegraph/go-vcs/vcs"
"sourcegraph.com/sourcegraph/vcsstore/vcsclient"
"github.com/shurcooL/Conception-go/caret"
"github.com/shurcooL/Conception-go/events"
)
var modeFlag = flag.Int("mode", 1, "Mode.")
var headlessFlag = flag.Bool("headless", false, "Headless mode.")
var keepRunning = true
var oFontBase, oFontBackground uint32
var redraw bool = true
var windowPointer *Pointer
var mousePointer *Pointer
var keyboardPointer *Pointer
var websocketPointer *Pointer // TEST
var buildOutput caret.MultilineContentI
var goCompileErrorsTest GoCompileErrorsTest
var goCompileErrorsManagerTest GoCompileErrorsManagerTest
var booVcs *exp12.Directory
var lodBias float64 = -66.67
var windowFocusedEvent DepNode2ManualI = &DepNode2Manual{} // TEST.
// Colors
var (
nearlyWhiteColor = mgl64.Vec3{0.975, 0.975, 0.975}
veryLightColor = mgl64.Vec3{0.95, 0.95, 0.95}
lightColor = mgl64.Vec3{0.85, 0.85, 0.85}
grayColor = mgl64.Vec3{0.75, 0.75, 0.75}
darkColor = mgl64.Vec3{0.35, 0.35, 0.35}
veryDarkColor = mgl64.Vec3{0.1, 0.1, 0.1}
nearlyBlackColor = mgl64.Vec3{0.025, 0.025, 0.025}
highlightColor = mgl64.Vec3{0.898, 0.765, 0.396} // Yellowish on-hover border color.
selectedTextColor = mgl64.Vec3{195 / 255.0, 212 / 255.0, 242 / 255.0}
selectedTextDarkColor = selectedTextColor.Mul(0.75)
selectedTextInactiveColor = mgl64.Vec3{225 / 255.0, 235 / 255.0, 250 / 255.0}
selectedEntryColor = mgl64.Vec3{0.21, 0.45, 0.84}
selectedEntryInactiveColor = lightColor
lightRedColor = mgl64.Vec3{1, 0.867, 0.867}
lightGreenColor = mgl64.Vec3{0.867, 1, 0.867}
mediumRedColor = mgl64.Vec3{1, 0.767, 0.767}
mediumGreenColor = mgl64.Vec3{0.767, 1, 0.767}
darkRedColor = mgl64.Vec3{1, 0.667, 0.667}
darkGreenColor = mgl64.Vec3{0.667, 1, 0.667}
)
var np = mgl64.Vec2{} // np stands for "No Position" and it's basically the (0, 0) position, used when it doesn't matter
// TODO: Remove these
var globalWindow *glfw.Window
var keepUpdatedTEST = []DepNode2I{}
var globalParsedFile *parsedFile
var globalGoSymbols SliceStringer
var con2RunBinPath = filepath.Join(os.TempDir(), "Conception-go", "Con2RunBin")
func CheckGLError() {
errorCode := gl.GetError()
if errorCode != 0 {
log.Panicln("GL Error:", errorCode)
}
}
// ---
type ChangeListener interface {
NotifyChange()
}
type ChangeListenerFunc func()
func (f ChangeListenerFunc) NotifyChange() {
f()
}
// ---
type DepNodeI interface {
AddChangeListener(l ChangeListener)
}
type DepNode struct {
changeListeners []ChangeListener
}
func (this *DepNode) AddChangeListener(l ChangeListener) {
this.changeListeners = append(this.changeListeners, l)
l.NotifyChange() // TODO: In future, don't literally NotifyChange() right away, as this can lead to duplicate work; instead mark as "need to update" for next run
}
// Pre-condition: l is a change listener that exists
func (this *DepNode) RemoveChangeListener(l ChangeListener) {
for i := range this.changeListeners {
if this.changeListeners[i] == l {
// Delete
copy(this.changeListeners[i:], this.changeListeners[i+1:])
this.changeListeners[len(this.changeListeners)-1] = nil
this.changeListeners = this.changeListeners[:len(this.changeListeners)-1]
//println("removed ith element of originally this many", i, len(this.changeListeners)+1)
return
}
}
panic("RemoveChangeListener: ChangeListener to be deleted wasn't found.")
}
func (this *DepNode) NotifyAllListeners() {
// TODO: In future, don't literally NotifyChange() right away, as this can lead to duplicate work; instead mark as "need to update" for next run
for _, changeListener := range this.changeListeners {
changeListener.NotifyChange()
}
}
// ---
type Widgeter interface {
PollLogic()
io.Closer
Layout()
LayoutNeeded()
Render()
Hit(mgl64.Vec2) []Widgeter
ProcessEvent(InputEvent) // TODO: Upgrade to MatchEventQueue() or so
ContainsWidget(widget, target Widgeter) bool // Returns true if target is widget or within it.
Pos() *mgl64.Vec2
Size() *mgl64.Vec2
HoverPointers() map[*Pointer]bool
Parent() Widgeter
SetParent(Widgeter)
ParentToLocal(mgl64.Vec2) mgl64.Vec2
DepNodeI
}
type Widgeters []Widgeter
type Widget struct {
pos mgl64.Vec2
size mgl64.Vec2
hoverPointers map[*Pointer]bool
parent Widgeter
DepNode
}
func NewWidget(pos, size mgl64.Vec2) Widget {
return Widget{pos: pos, size: size, hoverPointers: map[*Pointer]bool{}}
}
func (_ *Widget) PollLogic() {}
func (_ *Widget) Close() error { return nil }
func (w *Widget) Layout() {
if w.parent != nil {
w.parent.Layout()
}
}
func (_ *Widget) LayoutNeeded() {}
func (_ *Widget) Render() {}
func (w *Widget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
LocalPosition := w.ParentToLocal(ParentPosition)
Hit := (LocalPosition[0] >= 0 &&
LocalPosition[1] >= 0 &&
LocalPosition[0] <= w.size[0] &&
LocalPosition[1] <= w.size[1])
if Hit {
return []Widgeter{w}
} else {
return nil
}
}
func (w *Widget) ProcessEvent(inputEvent InputEvent) {}
func (_ *Widget) ContainsWidget(widget, target Widgeter) bool {
return widget == target
}
func (w *Widget) Pos() *mgl64.Vec2 { return &w.pos }
func (w *Widget) Size() *mgl64.Vec2 { return &w.size }
func (w *Widget) HoverPointers() map[*Pointer]bool {
return w.hoverPointers
}
func (w *Widget) Parent() Widgeter { return w.parent }
func (w *Widget) SetParent(p Widgeter) { w.parent = p }
func (w *Widget) ParentToLocal(ParentPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) {
return ParentPosition.Sub(w.pos)
}
type WidgeterS struct{ Widgeter }
func (w WidgeterS) GlobalToParent(GlobalPosition mgl64.Vec2) (ParentPosition mgl64.Vec2) {
switch w.Parent() {
case nil:
ParentPosition = GlobalPosition
default:
ParentPosition = WidgeterS{w.Parent()}.GlobalToLocal(GlobalPosition)
}
return ParentPosition
}
func (w WidgeterS) GlobalToLocal(GlobalPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) {
return w.ParentToLocal(WidgeterS{w}.GlobalToParent(GlobalPosition))
}
// ---
type CustomWidget struct {
Widget
PollLogicFunc func(this *CustomWidget)
RenderFunc func()
ProcessEventFunc func(inputEvent InputEvent)
CloseFunc func() error
}
func (this *CustomWidget) PollLogic() {
if this.PollLogicFunc != nil {
this.PollLogicFunc(this)
} else {
this.Widget.PollLogic()
}
}
func (this *CustomWidget) Render() {
if this.RenderFunc != nil {
this.RenderFunc()
} else {
this.Widget.Render()
}
}
func (this *CustomWidget) ProcessEvent(inputEvent InputEvent) {
if this.ProcessEventFunc != nil {
this.ProcessEventFunc(inputEvent)
} else {
this.Widget.ProcessEvent(inputEvent)
}
}
func (this *CustomWidget) Close() error {
if this.CloseFunc == nil {
return nil
}
return this.CloseFunc()
}
// ---
type Test1Widget struct {
Widget
}
func NewTest1Widget(pos mgl64.Vec2) *Test1Widget {
return &Test1Widget{Widget: NewWidget(pos, mgl64.Vec2{300, 300})}
}
func (w *Test1Widget) Render() {
DrawNBox(w.pos, w.size)
gl.Color3d(0, 0, 0)
//PrintText(w.pos, goon.Sdump(inputEventQueue))
//x := GetDocPackageAll("gist.github.com/5694308.git")
//PrintText(w.pos, strings.Join(x.Imports, "\n"))
/*files, _ := ioutil.ReadDir("/Users/Dmitri/Dropbox/Work/2013/GoLand/src/")
for lineIndex, file := range files {
if file.IsDir() {
PrintText(w.pos.Add(mathgl.Vec2d{0, float64(16 * lineIndex)}), ">>>> " + file.Name() + "/ (FOLDER)")
} else {
PrintText(w.pos.Add(mathgl.Vec2d{0, float64(16 * lineIndex)}), file.Name())
}
}*/
//PrintText(w.pos, tryReadFile("/Users/Dmitri/Dropbox/Work/2013/GoLand/src/PrintPackageSummary.go"))
//pkg := GetThisGoPackage()
//PrintText(w.pos, pkg.ImportPath+" - "+pkg.Name)
//PrintText(w.pos, string(debug.Stack()))
//PrintText(w.pos, GetThisGoSourceFilepath())
//PrintText(w.pos.Add(mathgl.Vec2d{0, 16}), GetThisGoSourceDir())
//PrintText(w.pos.Add(mathgl.Vec2d{0, 2 * 16}), GetThisGoPackage().ImportPath)
/*x := GetDocPackageAll(BuildPackageFromSrcDir(GetThisGoSourceDir()))
for lineIndex, y := range x.Vars {
PrintText(w.pos.Add(mathgl.Vec2d{0, float64(16 * lineIndex)}), SprintAstBare(y.Decl))
}*/
/*kat := widgets[len(widgets)-2].(*KatWidget)
PrintText(w.pos, fmt.Sprintf("%d %s", kat.mode, kat.mode.String()))*/
}
// ---
type Test2Widget struct {
*TextBoxWidget
field *float64
}
func NewTest2Widget(pos mgl64.Vec2, field *float64) *Test2Widget {
return &Test2Widget{TextBoxWidget: NewTextBoxWidgetExternalContent(pos, NewMultilineContentFuncInstant(func() string { return trim.LastNewline(goon.Sdump(*field)) }), nil), field: field}
}
func (w *Test2Widget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
if len(w.Widget.Hit(ParentPosition)) > 0 {
return []Widgeter{w}
} else {
return nil
}
}
func (w *Test2Widget) ProcessEvent(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.Pointer.State.Button(0) && (inputEvent.EventTypes[events.SLIDER_EVENT] && inputEvent.InputId == 0) {
*w.field += inputEvent.Sliders[0]
}
}
// ---
type parsedFile struct {
fset *token.FileSet
fileAst *ast.File
err error
DepNode2
}
func (t *parsedFile) Update() {
started := time.Now()
defer func() { fmt.Println("parsedFile.Update:", time.Since(started).Seconds()*1000, "ms") }()
source := t.GetSources()[0].(caret.MultilineContentI)
fset := token.NewFileSet()
fileAst, err := parser.ParseFile(fset, "", source.Content(), parser.ParseComments|parser.AllErrors)
{
//fileAst.Decls[0].(*ast.GenDecl).Specs = append(fileAst.Decls[0].(*ast.GenDecl).Specs, &ast.ImportSpec{Path: &ast.BasicLit{Kind: token.STRING, Value: `"yay/new/import"`}})
//astutil.AddImport(fset, fileAst, "yay/new/import")
}
t.fset = fset
t.fileAst = fileAst
t.err = err
}
func NewTest3Widget(pos mgl64.Vec2, source *TextBoxWidget) (*LiveGoroutineExpeWidget, *parsedFile) {
parsedFile := &parsedFile{}
parsedFile.AddSources(source.Content)
params := func() interface{} {
return []interface{}{
source.caretPosition.Logical(),
parsedFile.fset,
parsedFile.fileAst,
}
}
action := func(params interface{}) string {
index := params.([]interface{})[0].(uint32)
fset := params.([]interface{})[1].(*token.FileSet)
fileAst := params.([]interface{})[2].(*ast.File)
query := func(i interface{}) bool {
if f, ok := i.(ast.Node); ok && (uint32(f.Pos())-1 <= index && index <= uint32(f.End())-1) {
return true
}
return false
}
found := FindAll(fileAst, query)
if len(found) == 0 {
return ""
}
smallest := uint64(math.MaxUint64)
var smallestV interface{}
for v := range found {
size := uint64(v.(ast.Node).End() - v.(ast.Node).Pos())
if size < smallest {
smallestV = v
smallest = size
}
}
out := fmt.Sprintf("%d-%d, ", smallestV.(ast.Node).Pos()-1, smallestV.(ast.Node).End()-1)
out += fmt.Sprintf("%p, %T\n", smallestV, smallestV)
out += SprintAst(fset, smallestV) + "\n\n"
// This is can be huge if ran on root AST node of large Go files, so don't
if _, huge := smallestV.(*ast.File); !huge {
buf := new(bytes.Buffer)
_ = ast.Fprint(buf, fset, smallestV, nil)
out += buf.String()
out += goon.Sdump(smallestV)
}
return out
}
w := NewLiveGoroutineExpeWidget(pos, true, []DepNode2I{parsedFile, source.caretPosition}, params, action)
return w, parsedFile
}
// ---
type typeCheckedPackage struct {
fset *token.FileSet
files []*ast.File
tpkg *types.Package
info *types.Info
DepNode2
}
func (t *typeCheckedPackage) Update() {
started := time.Now()
defer func() { fmt.Println("typeCheckedPackage.Update:", time.Since(started).Seconds()*1000, "ms") }()
goPackageSelecter := t.GetSources()[0].(GoPackageSelecter)
if goPackageSelecter.GetSelectedGoPackage() == nil {
t.fset = nil
t.files = nil
t.tpkg = nil
t.info = nil
return
}
bpkg := goPackageSelecter.GetSelectedGoPackage().Bpkg
fset := token.NewFileSet()
files, err := ParseFiles(fset, bpkg.Dir, append(bpkg.GoFiles, bpkg.CgoFiles...)...)
if err != nil {
t.fset = nil
t.files = nil
t.tpkg = nil
t.info = nil
return
}
t.fset = fset
t.files = files
imp := importer.New()
imp.Config.UseGcFallback = true
cfg := &types.Config{Import: imp.Import}
info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Scopes: make(map[ast.Node]*types.Scope),
}
tpkg, err := cfg.Check(bpkg.ImportPath, fset, files, info)
if err == nil {
t.tpkg = tpkg
t.info = info
} else {
t.tpkg = nil
t.info = nil
}
}
// HACK
var Test4WidgetIdent *ast.Ident
func FindFileAst(fset *token.FileSet, file *token.File, fileAsts []*ast.File) *ast.File {
for _, fileAst := range fileAsts {
if fset.File(fileAst.Package) == file {
return fileAst
}
}
return nil
}
func NewTest4Widget(pos mgl64.Vec2, goPackageSelecter GoPackageSelecter, source *TextBoxWidget) (*LiveGoroutineExpeWidget, *typeCheckedPackage) {
typeCheckedPackage := &typeCheckedPackage{}
typeCheckedPackage.AddSources(goPackageSelecter, source.Content)
params := func() interface{} {
fileUri, _ := source.Content.GetUriForProtocol("file://")
return []interface{}{
source.caretPosition.Logical(),
fileUri,
typeCheckedPackage.fset,
typeCheckedPackage.files,
typeCheckedPackage.info,
}
}
action := func(params interface{}) string {
index := params.([]interface{})[0].(uint32)
fileUri := params.([]interface{})[1].(FileUri)
fset := params.([]interface{})[2].(*token.FileSet)
files := params.([]interface{})[3].([]*ast.File)
//tpkg := typeCheckedPackage.tpkg
info := params.([]interface{})[4].(*types.Info)
if fset == nil {
return "fset == nil"
}
// Figure out the file index and token.Pos of caret in that file
var fileAst *ast.File
var caretPos token.Pos
fset.Iterate(func(file *token.File) bool {
if fileUri == FileUri("file://"+file.Name()) {
fileAst = FindFileAst(fset, file, files)
caretPos = file.Pos(int(index))
return false
}
return true
})
if fileAst == nil {
return "fileAst == null, caretPos == token.NoPos"
}
var found2 []ast.Node
ast.Inspect(fileAst, func(n ast.Node) bool {
if n != nil && n.Pos() <= caretPos && caretPos <= n.End() {
found2 = append(found2, n)
return true
}
return false
})
out := ""
// TODO: Remove after some time, to ensure ast.Inspect() returns same results...
{
query := func(i interface{}) bool {
if f, ok := i.(ast.Node); ok && (f.Pos() <= caretPos && caretPos <= f.End()) {
return true
}
return false
}
found := FindAll(fileAst, query)
foundDiff := false
if len(found) != len(found2) {
fmt.Printf("%+v\n%+v\n", found, found2)
foundDiff = true
//panic("found vs. found2 diff")
}
for _, v := range found2 {
if _, ok := found[v]; !ok {
fmt.Printf("%+v\n%+v\n", found, found2)
foundDiff = true
//panic("found vs. found2 diff")
}
}
if foundDiff {
smallest := uint64(math.MaxUint64)
var smallestV interface{}
for v := range found {
size := uint64(v.(ast.Node).End() - v.(ast.Node).Pos())
if size < smallest {
smallestV = v
smallest = size
}
out += fmt.Sprintf("%T %d-%d [%d]\n", v, v.(ast.Node).Pos()-1, v.(ast.Node).End()-1, size)
}
out += "\n"
out += fmt.Sprintf("%d-%d, ", smallestV.(ast.Node).Pos()-1, smallestV.(ast.Node).End()-1)
out += fmt.Sprintf("%p, %T\n", smallestV, smallestV)
out += SprintAst(fset, smallestV) + "\n\n"
}
}
if len(found2) == 0 {
return ""
}
//out := ""
smallestV := found2[len(found2)-1]
for _, v := range found2 {
size := v.End() - v.Pos()
out += fmt.Sprintf("%T %d-%d [%d]\n", v, v.Pos()-1, v.End()-1, size)
}
out += "\n"
out += fmt.Sprintf("%d-%d, %p, %T\n\n", smallestV.Pos()-1, smallestV.End()-1, (interface{})(smallestV), smallestV)
out += SprintAst(fset, smallestV) + "\n\n"
if ident, ok := smallestV.(*ast.Ident); ok {
Test4WidgetIdent = ident // HACK
if obj := findTypesObject(info, ident); obj != nil {
out += ">>> " + TypeChainString(obj.Type())
if constObj, ok := obj.(*types.Const); ok {
out += fmt.Sprintf(" = %v", constObj.Val())
}
out += "\n\n"
} else {
out += "nil obj\n\n"
}
}
// This is can be huge if ran on root AST node of large Go files, so don't
if _, huge := smallestV.(*ast.File); !huge {
buf := new(bytes.Buffer)
_ = ast.Fprint(buf, fset, smallestV, nil)
out += buf.String()
out += goon.Sdump(smallestV)
out += goon.SdumpExpr(fset)
}
return out
}
w := NewLiveGoroutineExpeWidget(pos, true, []DepNode2I{typeCheckedPackage, source.caretPosition}, params, action)
return w, typeCheckedPackage
}
func NewTypeUnderCaretWidget(pos mgl64.Vec2, goPackageSelecter GoPackageSelecter, source *TextBoxWidget, typeCheckedPackage *typeCheckedPackage) *LiveGoroutineExpeWidget {
params := func() interface{} {
fileUri, _ := source.Content.GetUriForProtocol("file://")
return []interface{}{
source.caretPosition.Logical(),
fileUri,
typeCheckedPackage.fset,
typeCheckedPackage.files,
typeCheckedPackage.info,
}
}
action := func(params interface{}) string {
index := params.([]interface{})[0].(uint32)
fileUri := params.([]interface{})[1].(FileUri)
fset := params.([]interface{})[2].(*token.FileSet)
files := params.([]interface{})[3].([]*ast.File)
//tpkg := typeCheckedPackage.tpkg
info := params.([]interface{})[4].(*types.Info)
if fset == nil {
return "fset == nil"
}
// Figure out the file index and token.Pos of caret in that file
var fileAst *ast.File
var caretPos token.Pos
fset.Iterate(func(file *token.File) bool {
if fileUri == FileUri("file://"+file.Name()) {
fileAst = FindFileAst(fset, file, files)
caretPos = file.Pos(int(index))
return false
}
return true
})
if fileAst == nil {
return "fileAst == null, caretPos == token.NoPos"
}
var found2 []ast.Node
ast.Inspect(fileAst, func(n ast.Node) bool {
if n != nil && n.Pos() <= caretPos && caretPos <= n.End() {
found2 = append(found2, n)
return true
}
return false
})
if len(found2) == 0 {
return ""
}
out := ""
smallestV := found2[len(found2)-1]
if ident, ok := smallestV.(*ast.Ident); ok {
Test4WidgetIdent = ident // HACK
if obj := findTypesObject(info, ident); obj != nil {
out += TypeChainString(obj.Type())
if constObj, ok := obj.(*types.Const); ok {
out += fmt.Sprintf(" = %v", constObj.Val())
}
} else {
out += "nil Object"
}
}
return out
}
w := NewLiveGoroutineExpeWidget(pos, true, []DepNode2I{typeCheckedPackage, source.caretPosition}, params, action)
return w
}
func findTypesObject(info *types.Info, ident *ast.Ident) (obj types.Object) {
if info != nil {
switch {
case info.Uses[ident] != nil:
obj = info.Uses[ident]
case info.Defs[ident] != nil:
obj = info.Defs[ident]
}
}
return obj
}
// ---
type SliceStringerS struct {
entries []fmt.Stringer
DepNode2Manual
}
func NewSliceStringerS(entries ...string) *SliceStringerS {
s := &SliceStringerS{}
for _, entry := range entries {
s.entries = append(s.entries, json.Number(entry))
}
return s
}
func NewSliceStringerAllGoPackages(path string) *SliceStringerS {
f, err := os.Open(path)
if err != nil {
panic(err)
}
defer f.Close()
var importers u5.Importers
if err := json.NewDecoder(f).Decode(&importers); err != nil {
panic(err)
}
s := &SliceStringerS{}
for _, entry := range importers.Results {
s.entries = append(s.entries, json.Number(entry.Path))
}
return s
}
func (this *SliceStringerS) Get(index uint64) fmt.Stringer {
return this.entries[index]
}
func (this *SliceStringerS) Len() uint64 {
return uint64(len(this.entries))
}
var oracleModes = NewSliceStringerS("callees", "callers", "callgraph", "callstack", "describe", "freevars", "implements", "peers", "referrers")
func NewTest6OracleWidget(pos mgl64.Vec2, goPackageSelecter GoPackageSelecter, source *TextBoxWidget) Widgeter {
mode := NewSelecterWidget(np, oracleModes, nil)
params := func() interface{} {
fileUri, _ := source.Content.GetUriForProtocol("file://")
return []interface{}{
source.caretPosition.Logical(),
fileUri,
goPackageSelecter,
mode.GetSelected().String(),
}
}
action := func(params interface{}) string {
caretPosition := params.([]interface{})[0].(uint32)
fileUri := params.([]interface{})[1].(FileUri)
goPackageSelecter := params.([]interface{})[2].(GoPackageSelecter)
mode := params.([]interface{})[3].(string)
if pkg := goPackageSelecter.GetSelectedGoPackage(); pkg != nil && fileUri != "" && mode != "" {
cmd := exec.Command("oracle", fmt.Sprintf("--pos=%s:#%d", fileUri[len("file://"):], caretPosition), mode, pkg.Bpkg.ImportPath)
out, err := cmd.CombinedOutput()
if err != nil {
return strings.Join(cmd.Args, " ") + "\nError:\n" + err.Error() + "\nOutput:\n" + string(out)
}
return strings.Join(cmd.Args, " ") + "\n" + string(out)
} else {
return "<no file or mode selected>"
}
}
w := NewLiveGoroutineExpeWidget(pos, false, []DepNode2I{source.caretPosition, goPackageSelecter, mode}, params, action)
return NewFlowLayoutWidget(pos, Widgeters{mode, w}, nil)
}
// ---
// NOTE: I'm probably not going to use doc.Package because it duplicates AST stuff from other packages, and doesn't point back to real code...
// Instead, I mimic doc.Package functionality, but stack it on top of types.Package rather than a duplicated ast.Package.
/*type docPackage struct {
dpkg *doc.Package
DepNode2
}
func (this *docPackage) Update() {
goPackage := this.GetSources()[0].(ImportPathFoundSelecter)
importPath := ""
if goPackage.GetSelected() != nil {
importPath = goPackage.GetSelected().ImportPath()
}
// TODO: Factor out bpkg into buildPackage DepNode2
bpkg, err := BuildPackageFromImportPath(importPath)
if err != nil {
this.dpkg = nil
return
}
dpkg, err := GetDocPackageAll(bpkg, nil)
if err != nil {
this.dpkg = nil
return
}
this.dpkg = dpkg
}*/
// ---
type NodeStringer interface {
ast.Node
fmt.Stringer
}
type nodeStringer struct {
ast.Node
str string
}
func NewNodeStringer(node ast.Node) NodeStringer {
return nodeStringer{Node: node, str: SprintAstBare(node)}
}
func (this nodeStringer) String() string { return this.str }
type twoNodeStringer struct {
pos, end token.Pos
str string
}
func NewTwoNodeStringer(node0, node1 ast.Node, str string) NodeStringer {
return twoNodeStringer{pos: node0.Pos(), end: node1.End(), str: str}
}
func (this twoNodeStringer) Pos() token.Pos { return this.pos }
func (this twoNodeStringer) End() token.Pos { return this.end }
func (this twoNodeStringer) String() string { return this.str }
// ---
type goSymbols struct {
entries []NodeStringer
DepNode2
}
func (this *goSymbols) Get(index uint64) fmt.Stringer {
return this.entries[index]
}
func (this *goSymbols) Len() uint64 {
return uint64(len(this.entries))
}
/*func (this *goSymbols) Update() {
dpkg := this.GetSources()[0].(*docPackage).dpkg
if dpkg == nil {
this.entries = nil
return
}
this.entries = nil
for _, f := range dpkg.Funcs {
this.entries = append(this.entries, NewNodeStringer(f.Decl))
}
for _, t := range dpkg.Types {
for _, f := range t.Funcs {
this.entries = append(this.entries, NewNodeStringer(f.Decl))
}
for _, m := range t.Methods {
this.entries = append(this.entries, NewNodeStringer(m.Decl))
}
}
}
func NewTest5Widget(pos mathgl.Vec2d, goPackage *GoPackageListingPureWidget, source *TextBoxWidget) *ListWidget {
docPackage := &docPackage{}
docPackage.AddSources(goPackage, source.Content)
goSymbols := &goSymbols{}
goSymbols.AddSources(docPackage)
w := NewListWidget(np, goSymbols)
return w
}*/
type goSymbolsB struct {
goSymbols
}
func (this *goSymbolsB) Update() {
files := this.GetSources()[0].(*typeCheckedPackage).files
if files == nil {
this.entries = nil
return
}
// Mimic doc.Package functionality, but stack it on top of types.Package rather than a duplicated ast.Package
// https://code.google.com/p/go/source/browse/src/pkg/go/doc/reader.go?name=release#456
this.entries = nil
for _, fileAst := range files {
for _, decl := range fileAst.Decls {
switch d := decl.(type) {
case *ast.FuncDecl:
funcDeclSignature := &ast.FuncDecl{Recv: d.Recv, Name: d.Name, Type: d.Type}
nodeStringer := nodeStringer{Node: d, str: SprintAstBare(funcDeclSignature)}
this.entries = append(this.entries, nodeStringer)
}
}
}
}
type goSymbolsC struct {
goSymbols
}
func (this *goSymbolsC) Update() {
fileAst := this.GetSources()[0].(*parsedFile).fileAst
if fileAst == nil {
this.entries = nil
return
}
// Mimic doc.Package functionality, but stack it on top of ast.File rather than a duplicated ast.Package
// https://code.google.com/p/go/source/browse/src/pkg/go/doc/reader.go?name=release#456
this.entries = nil
for _, decl := range fileAst.Decls {
switch d := decl.(type) {
case *ast.FuncDecl:
if d.Recv != nil {
name := "(" + SprintAstBare(d.Recv.List[0].Type) + ") " + d.Name.String()
this.entries = append(this.entries, NewTwoNodeStringer(d.Recv, d.Name, name))
} else {
this.entries = append(this.entries, NewNodeStringer(d.Name))
}
case *ast.GenDecl:
switch d.Tok {
case token.TYPE:
for _, spec := range d.Specs {
ident := spec.(*ast.TypeSpec).Name
this.entries = append(this.entries, NewNodeStringer(ident))
}
case token.CONST, token.VAR:
for _, spec := range d.Specs {
for _, ident := range spec.(*ast.ValueSpec).Names {
this.entries = append(this.entries, NewNodeStringer(ident))
}
}
}
}
}
}
// =====
type goPackagesSliceStringer struct {
*exp14.GoPackages
}
func (this *goPackagesSliceStringer) Get(index uint64) fmt.Stringer {
return this.Entries[index]
}
func (this *goPackagesSliceStringer) Len() uint64 {
return uint64(len(this.Entries))
}
// ---
type GoPackageSelecter interface {
GetSelectedGoPackage() *GoPackage
DepNode2I
}
type GoPackageSelecterAdapter struct {
Selecter
}
func (this *GoPackageSelecterAdapter) GetSelectedGoPackage() *GoPackage {
if selected := this.GetSelected(); selected != nil {
return selected.(*GoPackage)
} else {
return nil
}
}
// TODO: Move to the right place.
//var goPackages = &exp14.GoPackages{SkipGoroot: true}
var goPackages = &exp14.GoPackages{SkipGoroot: false}
func NewGoPackageListingWidget(pos, size mgl64.Vec2) *SearchableListWidget {
//goPackagesSliceStringer := &goPackagesSliceStringer{&exp14.GoPackages{SkipGoroot: false}}
goPackagesSliceStringer := &goPackagesSliceStringer{goPackages}
w := NewSearchableListWidget(pos, size, goPackagesSliceStringer)
return w
}
// =====
type GoCompileErrorsManagerTest struct {
//Sources []*GoCompileErrorsTest // TODO: Migrate to using DepNode2
DepNode2
All map[FileUri]map[int][]string // FileUri -> LineIndex -> []Message.
}
func (this *GoCompileErrorsManagerTest) Update() {
this.All = make(map[FileUri]map[int][]string)
for _, source := range this.GetSources() {
for _, goCompilerError := range source.(*GoCompileErrorsTest).Out {
if _, ok := this.All[goCompilerError.FileUri]; !ok {
this.All[goCompilerError.FileUri] = make(map[int][]string)
}
this.All[goCompilerError.FileUri][goCompilerError.ErrorMessage.LineIndex] = append(this.All[goCompilerError.FileUri][goCompilerError.ErrorMessage.LineIndex], goCompilerError.ErrorMessage.Message)
}
}
redraw = true // TODO: Think about this and deprecate if possible
}
// ---
type GoCompileErrorsTest struct {
DepNode2
Dir string // Assume build output is relative to this dir. If empty, assume cwd.
Out []GoCompilerError
}
type GoErrorMessage struct {
LineIndex int
Message string
}
type GoCompilerError struct {
FileUri FileUri
ErrorMessage GoErrorMessage
}
func (this *GoCompileErrorsTest) Update() {
reduceFunc := func(in string) interface{} {
x := strings.Index(in, ":") // Find first colon
if x == -1 {
return nil
}
fileUri, err := filepath.Abs(filepath.Join(this.Dir, in[:x]))
if err != nil {
return nil
}
// TODO: Check if file exists? Maybe?
in = in[x+1:]
x = strings.Index(in, ":") // Find second colon
if x == -1 {
return nil
}
lineNumber, err := strconv.Atoi(in[:x])
if err != nil {
return nil
}
lineIndex := lineNumber - 1 // Convert line number (e.g. 1) to line index (e.g. 0)
in = in[x+1:]
message := trim.FirstSpace(in)
return GoCompilerError{FileUri: FileUri("file://" + fileUri), ErrorMessage: GoErrorMessage{LineIndex: lineIndex, Message: message}}
}
source := this.DepNode2.GetSources()[0].(caret.MultilineContentI)
outChan := GoReduceLinesFromReader(NewContentReader(source), 4, reduceFunc) // TODO: Preserve order (for error messages on same line) by using an order preserving version of GoReduce...
//outChan := GoReduceLinesFromReader(NewContentReader(this.DepNode2.Sources[0].(caret.MultilineContentI)), 4, reduceFunc)
this.Out = nil
for out := range outChan {
this.Out = append(this.Out, out.(GoCompilerError))
}
}
// ---
type GpcFileWidget struct {
Widget
p gist6545684.Polygon
}
func NewGpcFileWidget(pos mgl64.Vec2, path string) *GpcFileWidget {
p, err := gist6545684.ReadGpcFile(path)
if err != nil {
log.Fatalln("ReadGpcFile:", err)
}
return &GpcFileWidget{Widget: NewWidget(pos, mgl64.Vec2{0, 0}), p: p}
}
func (w *GpcFileWidget) Render() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
gl.Color3d(0, 0, 0)
for _, contour := range w.p.Contours {
gl.Begin(gl.LINE_LOOP)
for _, vertex := range contour.Vertices {
gl.Vertex2dv((*float64)(&vertex[0]))
}
gl.End()
}
}
// ---
type ButtonTriggerWidget struct {
*ButtonWidget
DepNode2Manual
}
func NewButtonTriggerWidget(pos mgl64.Vec2) *ButtonTriggerWidget {
w := &ButtonTriggerWidget{}
w.ButtonWidget = NewButtonWidget(pos, func() { ExternallyUpdated(&w.DepNode2Manual) })
return w
}
// ---
type ButtonLabelWidget struct {
*ButtonWidget
label string
}
func NewButtonLabelWidget(pos mgl64.Vec2, label string, action func()) *ButtonLabelWidget {
w := &ButtonLabelWidget{ButtonWidget: NewButtonWidget(pos, action), label: label}
w.ButtonWidget.Widget.Size()[0] = float64(fontWidth*len(label) + 8)
w.ButtonWidget.Widget.Size()[1] = fontHeight
return w
}
func (w *ButtonLabelWidget) Render() {
w.ButtonWidget.Render()
gl.Color3d(0, 0, 0)
NewOpenGlStream(w.Pos().Add(mgl64.Vec2{4, 0})).PrintLine(w.label)
}
// ---
type ButtonWidget struct {
Widget
action func()
tooltip Widgeter
}
func NewButtonWidget(pos mgl64.Vec2, action func()) *ButtonWidget {
w := &ButtonWidget{Widget: NewWidget(pos, mgl64.Vec2{fontHeight, fontHeight})}
w.setAction(action)
return w
}
func (w *ButtonWidget) setAction(action func()) {
w.action = action
if action != nil {
// HACK: This isn't thread-safe in any way
//go func() { w.tooltip = NewTextLabelWidgetString(np, GetSourceAsString(action)) }()
}
}
func (w *ButtonWidget) Render() {
// HACK: Brute-force check the mouse pointer if it contains this widget
isOriginHit := false
for _, hit := range mousePointer.OriginMapping {
if w == hit {
isOriginHit = true
break
}
}
isHit := len(w.HoverPointers()) > 0
// HACK: Assumes mousePointer rather than considering all connected pointing pointers
if isOriginHit && mousePointer.State.IsActive() && isHit {
//DrawGBox(w.pos, w.size)
DrawInnerRoundedBox(w.pos, w.size, highlightColor, grayColor)
} else if (isHit && !mousePointer.State.IsActive()) || isOriginHit {
//DrawYBox(w.pos, w.size)
DrawInnerRoundedBox(w.pos, w.size, highlightColor, nearlyWhiteColor)
} else {
//DrawNBox(w.pos, w.size)
DrawInnerRoundedBox(w.pos, w.size, mgl64.Vec3{0.3, 0.3, 0.3}, nearlyWhiteColor)
}
// Tooltip
if w.tooltip != nil && isHit {
mousePointerPositionLocal := WidgeterS{w}.GlobalToLocal(mgl64.Vec2{mousePointer.State.Axes[0], mousePointer.State.Axes[1]})
tooltipOffset := mgl64.Vec2{0, 16}
*w.tooltip.Pos() = w.pos.Add(mousePointerPositionLocal).Add(tooltipOffset)
w.tooltip.Render()
}
}
func (w *ButtonWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
if len(w.Widget.Hit(ParentPosition)) > 0 {
return []Widgeter{w}
} else {
return nil
}
}
func (w *ButtonWidget) ProcessEvent(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.InputId == 0 && inputEvent.Buttons[0] == false &&
inputEvent.Pointer.Mapping.ContainsWidget(w) && /* TODO: GetHoverer() */ // IsHit(this button) should be true
inputEvent.Pointer.OriginMapping.ContainsWidget(w) { /* TODO: GetHoverer() */ // Make sure we're releasing pointer over same button that it originally went active on, and nothing is in the way (i.e. button is hoverer)
if w.action != nil {
w.action()
//println(GetSourceAsString(w.action))
w.Layout()
//w.NotifyAllListeners()
}
}
}
// ---
type TriButtonWidget struct {
*ButtonWidget
state bool
}
func NewTriButtonWidget(pos mgl64.Vec2, action func()) *TriButtonWidget {
w := &TriButtonWidget{ButtonWidget: NewButtonWidget(pos, nil)}
w.setAction(action)
return w
}
// Pre-conditions: Currently, nil action is not supported.
func (w *TriButtonWidget) setAction(action func()) {
w.action = func() {
w.state = !w.state
action()
}
}
func (w *TriButtonWidget) Render() {
gl.Color3dv((*float64)(&darkColor[0]))
if !w.state {
gl.Begin(gl.TRIANGLES)
gl.Vertex2d(float64(w.pos[0]+w.size[0]*0.25), float64(w.pos[1]+w.size[0]*0.15))
gl.Vertex2d(float64(w.pos[0]+w.size[0]*0.95), float64(w.pos[1]+w.size[1]*0.5))
gl.Vertex2d(float64(w.pos[0]+w.size[0]*0.25), float64(w.pos[1]+w.size[1]*0.85))
gl.End()
} else {
gl.Begin(gl.TRIANGLES)
gl.Vertex2d(float64(w.pos[0]+w.size[0]*0.15), float64(w.pos[1]+w.size[0]*0.25))
gl.Vertex2d(float64(w.pos[0]+w.size[0]*0.85), float64(w.pos[1]+w.size[1]*0.25))
gl.Vertex2d(float64(w.pos[0]+w.size[0]*0.5), float64(w.pos[1]+w.size[1]*0.95))
gl.End()
}
}
func (w *TriButtonWidget) State() bool {
return w.state
}
// ---
type TriButtonExternalStateWidget struct {
*ButtonWidget
state func() bool
}
func NewTriButtonExternalStateWidget(pos mgl64.Vec2, state func() bool, action func()) *TriButtonExternalStateWidget {
w := &TriButtonExternalStateWidget{ButtonWidget: NewButtonWidget(pos, action), state: state}
return w
}
func (w *TriButtonExternalStateWidget) Render() {
// HACK: Brute-force check the mouse pointer if it contains this widget
isOriginHit := false
for _, hit := range mousePointer.OriginMapping {
if w == hit {
isOriginHit = true
break
}
}
isHit := len(w.HoverPointers()) > 0
// HACK: Assumes mousePointer rather than considering all connected pointing pointers
if isOriginHit && mousePointer.State.IsActive() && isHit {
DrawGBox(w.pos, w.size)
} else if (isHit && !mousePointer.State.IsActive()) || isOriginHit {
DrawYBox(w.pos, w.size)
} else {
DrawNBox(w.pos, w.size)
}
if w.state() {
DrawBorderlessBox(w.pos.Add(w.size.Mul(2.0/14)), w.size.Mul(10.0/14), mgl64.Vec3{0.9, 0.3, 0.01})
} else {
DrawBorderlessBox(w.pos.Add(w.size.Mul(2.0/14)), w.size.Mul(10.0/14), mgl64.Vec3{0.9, 0.9, 0.9})
}
}
// ---
type BoxWidget struct {
Widget
Name string
}
var boxWidgetTooltip = NewTextLabelWidgetString(np, GetSourceAsString((*BoxWidget).ProcessEvent))
func (w *BoxWidget) Render() {
// HACK: Brute-force check the mouse pointer if it contains this widget
isOriginHit := false
for _, hit := range mousePointer.OriginMapping {
if w == hit {
isOriginHit = true
break
}
}
isHit := len(w.HoverPointers()) > 0
// HACK: Assumes mousePointer rather than considering all connected pointing pointers
if isOriginHit && mousePointer.State.IsActive() && isHit {
DrawGBox(w.pos, w.size)
} else if (isHit && !mousePointer.State.IsActive()) || isOriginHit {
DrawYBox(w.pos, w.size)
} else {
DrawNBox(w.pos, w.size)
}
// Tooltip
if isHit {
mousePointerPositionLocal := WidgeterS{w}.GlobalToLocal(mgl64.Vec2{mousePointer.State.Axes[0], mousePointer.State.Axes[1]})
tooltipOffset := mgl64.Vec2{0, -4 - boxWidgetTooltip.Size()[1]}
*boxWidgetTooltip.Pos() = w.pos.Add(mousePointerPositionLocal).Add(tooltipOffset)
boxWidgetTooltip.Render()
}
}
func (w *BoxWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
if len(w.Widget.Hit(ParentPosition)) > 0 {
return []Widgeter{w}
} else {
return nil
}
}
func (w *BoxWidget) ProcessEvent(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.InputId == 0 && inputEvent.Buttons[0] == false &&
inputEvent.Pointer.Mapping.ContainsWidget(w) && /* TODO: GetHoverer() */ // IsHit(this button) should be true
inputEvent.Pointer.OriginMapping.ContainsWidget(w) { /* TODO: GetHoverer() */ // Make sure we're releasing pointer over same button that it originally went active on, and nothing is in the way (i.e. button is hoverer)
fmt.Printf("%q BoxWidget pressed!\n", w.Name)
x, y := globalWindow.GetPos()
globalWindow.SetPos(x-16, y)
}
}
// ---
type WindowWidget struct {
Widget
chrome *CompositeWidget
Name string
child Widgeter
}
func NewWindowWidget(pos, size mgl64.Vec2, child Widgeter) *WindowWidget {
w := &WindowWidget{Widget: NewWidget(pos, size), child: child}
closeButton := NewButtonWidget(np, func() {
w.Parent().(RemoveWidgeter).RemoveWidget(w)
w.SetParent(nil) // TODO: Not sure if needed
})
w.chrome = NewCompositeWidget(np, []Widgeter{closeButton})
w.chrome.SetParent(w)
w.child.SetParent(w)
w.Layout() // TODO: Should this be automatic from above SetParent()?
return w
}
func (w *WindowWidget) PollLogic() {
w.chrome.PollLogic()
w.child.PollLogic()
for i := range w.pos {
w.pos[i] = math.Floor(w.pos[i] + 0.5)
}
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.PollLogic()
}
func (w *WindowWidget) Close() error {
// TODO: Errors.
_ = w.chrome.Close()
_ = w.child.Close()
return nil
}
func (w *WindowWidget) Layout() {
w.size = *w.child.Size()
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.Layout()
}
func (w *WindowWidget) LayoutNeeded() {
w.chrome.LayoutNeeded()
w.child.LayoutNeeded()
}
func (w *WindowWidget) Render() {
DrawGradientBox(w.pos, mgl64.Vec2{w.size[0], fontHeight}, mgl64.Vec3{0.3, 0.3, 0.3}, nearlyWhiteColor, lightColor)
// Title
gl.Color3dv((*float64)(&nearlyBlackColor[0]))
NewOpenGlStream(w.pos.Add(mgl64.Vec2{60})).PrintLine(w.Name)
gl.PushMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
w.chrome.Render()
gl.Translated(float64(0), float64(fontHeight+1), 0)
w.child.Render()
gl.PopMatrix()
}
func (w *WindowWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
LocalPosition := w.Widget.ParentToLocal(ParentPosition) // HACK: Should use actual ParentToLocal properly.
Hit := (LocalPosition[0] >= 0 &&
LocalPosition[1] >= 0 &&
LocalPosition[0] <= w.size[0] &&
LocalPosition[1] <= fontHeight)
if Hit {
hits := []Widgeter{w}
hits = append(hits, w.chrome.Hit(LocalPosition)...)
return hits
} else {
return w.child.Hit(LocalPosition.Sub(mgl64.Vec2{0, fontHeight + 1}))
}
}
func (w *WindowWidget) ParentToLocal(ParentPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) {
return w.Widget.ParentToLocal(ParentPosition).Sub(mgl64.Vec2{0, fontHeight + 1})
}
func (w *WindowWidget) ProcessEvent(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.Pointer.State.Button(0) && (inputEvent.EventTypes[events.SLIDER_EVENT] && inputEvent.InputId == 0) {
w.pos[0] += inputEvent.Sliders[0]
w.pos[1] += inputEvent.Sliders[1]
}
}
// ---
func (widgets Widgeters) ContainsWidget(target Widgeter) bool {
for _, widget := range widgets {
if widget.ContainsWidget(widget, target) {
return true
}
}
return false
}
// ---
func DrawBorderlessBox(pos, size mgl64.Vec2, backgroundColor mgl64.Vec3) {
gl.Color3dv((*float64)(&backgroundColor[0]))
gl.Rectd(float64(pos[0]), float64(pos[1]), float64(pos.Add(size)[0]), float64(pos.Add(size)[1]))
}
func DrawBorderlessGradientBox(pos, size mgl64.Vec2, topColor, bottomColor mgl64.Vec3) {
gl.Begin(gl.TRIANGLE_STRIP)
gl.Color3dv((*float64)(&topColor[0]))
gl.Vertex2d(float64(pos[0]), float64(pos[1]))
gl.Vertex2d(float64(pos.Add(size)[0]), float64(pos[1]))
gl.Color3dv((*float64)(&bottomColor[0]))
gl.Vertex2d(float64(pos[0]), float64(pos.Add(size)[1]))
gl.Vertex2d(float64(pos.Add(size)[0]), float64(pos.Add(size)[1]))
gl.End()
}
func DrawBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) {
DrawBorderlessBox(pos.Add(mgl64.Vec2{-1, -1}), size.Add(mgl64.Vec2{2, 2}), borderColor)
DrawBorderlessBox(pos, size, backgroundColor)
}
func DrawNBox(pos, size mgl64.Vec2) {
DrawBox(pos, size, mgl64.Vec3{0.3, 0.3, 0.3}, nearlyWhiteColor)
}
func DrawYBox(pos, size mgl64.Vec2) {
DrawBox(pos, size, highlightColor, nearlyWhiteColor)
}
func DrawGBox(pos, size mgl64.Vec2) {
DrawBox(pos, size, highlightColor, grayColor)
}
func DrawLGBox(pos, size mgl64.Vec2) {
DrawBox(pos, size, mgl64.Vec3{0.6, 0.6, 0.6}, lightColor)
}
func DrawGradientBox(pos, size mgl64.Vec2, borderColor, topColor, bottomColor mgl64.Vec3) {
gl.Color3dv((*float64)(&borderColor[0]))
gl.Rectd(float64(pos[0]-1), float64(pos[1]-1), float64(pos.Add(size)[0]+1), float64(pos.Add(size)[1]+1))
DrawBorderlessGradientBox(pos, size, topColor, bottomColor)
}
func DrawInnerRoundedBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) {
if size[0] == 0 || size[1] == 0 {
return
}
const OuterDistance = 1.5
gl.Begin(gl.POLYGON)
gl.Color3dv((*float64)(&borderColor[0]))
gl.Vertex2d(float64(pos[0]+OuterDistance), float64(pos[1]))
gl.Vertex2d(float64(pos[0]), float64(pos[1]+OuterDistance))
gl.Vertex2d(float64(pos[0]), float64(pos[1]-OuterDistance+size[1]))
gl.Vertex2d(float64(pos[0]+OuterDistance), float64(pos[1]+size[1]))
gl.Vertex2d(float64(pos[0]-OuterDistance+size[0]), float64(pos[1]+size[1]))
gl.Vertex2d(float64(pos[0]+size[0]), float64(pos[1]-OuterDistance+size[1]))
gl.Vertex2d(float64(pos[0]+size[0]), float64(pos[1]+OuterDistance))
gl.Vertex2d(float64(pos[0]-OuterDistance+size[0]), float64(pos[1]))
gl.End()
const InnerDistance = math.Sqrt2 + 0.5
gl.Begin(gl.POLYGON)
gl.Color3dv((*float64)(&backgroundColor[0]))
gl.Vertex2d(float64(pos[0]+InnerDistance), float64(pos[1]+1))
gl.Vertex2d(float64(pos[0]+1), float64(pos[1]+InnerDistance))
gl.Vertex2d(float64(pos[0]+1), float64(pos[1]-InnerDistance+size[1]))
gl.Vertex2d(float64(pos[0]+InnerDistance), float64(pos[1]-1+size[1]))
gl.Vertex2d(float64(pos[0]-InnerDistance+size[0]), float64(pos[1]-1+size[1]))
gl.Vertex2d(float64(pos[0]-1+size[0]), float64(pos[1]-InnerDistance+size[1]))
gl.Vertex2d(float64(pos[0]-1+size[0]), float64(pos[1]+InnerDistance))
gl.Vertex2d(float64(pos[0]-InnerDistance+size[0]), float64(pos[1]+1))
gl.End()
}
const Tau = 2 * math.Pi
func DrawCircle(pos mgl64.Vec2, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) {
const x = 64
gl.Color3dv((*float64)(&borderColor[0]))
gl.Begin(gl.TRIANGLE_FAN)
gl.Vertex2d(float64(pos[0]), float64(pos[1]))
for i := 0; i <= x; i++ {
gl.Vertex2d(float64(pos[0]+math.Sin(Tau*float64(i)/x)*size[0]/2), float64(pos[1]+math.Cos(Tau*float64(i)/x)*size[1]/2))
}
gl.End()
gl.Color3dv((*float64)(&backgroundColor[0]))
gl.Begin(gl.TRIANGLE_FAN)
gl.Vertex2d(float64(pos[0]), float64(pos[1]))
for i := 0; i <= x; i++ {
gl.Vertex2d(float64(pos[0]+math.Sin(Tau*float64(i)/x)*(size[0]/2-1)), float64(pos[1]+math.Cos(Tau*float64(i)/x)*(size[1]/2-1)))
}
gl.End()
}
func DrawCircleBorder(pos mgl64.Vec2, size mgl64.Vec2, borderColor mgl64.Vec3) {
DrawCircleBorderCustom(pos, size, borderColor, 1, 64, 0, 64)
}
func DrawCircleBorderCustom(pos mgl64.Vec2, size mgl64.Vec2, borderColor mgl64.Vec3, borderWidth float64, totalSlices, startSlice, endSlice int32) {
var x = float64(totalSlices)
gl.Color3dv((*float64)(&borderColor[0]))
gl.Begin(gl.TRIANGLE_STRIP)
for i := startSlice; i <= endSlice; i++ {
gl.Vertex2d(float64(pos[0]+math.Sin(Tau*float64(i)/x)*size[0]/2), float64(pos[1]-math.Cos(Tau*float64(i)/x)*size[1]/2))
gl.Vertex2d(float64(pos[0]+math.Sin(Tau*float64(i)/x)*(size[0]/2-borderWidth)), float64(pos[1]-math.Cos(Tau*float64(i)/x)*(size[1]/2-borderWidth)))
}
gl.End()
}
// ---
type KatWidget struct {
Widget
target mgl64.Vec2
rotation float64
mode KatMode
skillActive bool
}
const ShunpoRadius = 120
type KatMode uint8
const (
/*AutoAttack KatMode = iota
Shunpo*/
AutoAttack KatMode = 17 * iota
_
Shunpo
)
func (mode KatMode) String() string {
//fmt.Printf("%T %T\n", AutoAttack, Shunpo)
x, _ := GetDocPackageAll(BuildPackageFromSrcDir(GetThisGoSourceDir()))
for _, y := range x.Types {
if y.Name == "KatMode" {
for _, c := range y.Consts {
goon.DumpExpr(c.Names, mode)
return c.Names[mode]
}
}
}
panic(0)
}
func NewKatWidget(pos mgl64.Vec2) *KatWidget {
w := &KatWidget{Widget: NewWidget(pos, mgl64.Vec2{16, 16}), target: pos}
UniversalClock.AddChangeListener(w)
return w
}
func (w *KatWidget) Render() {
// HACK: Should iterate over all typing pointers, not just assume keyboard pointer and its first mapping
/*hasTypingFocus := keyboardPointer != nil && keyboardPointer.OriginMapping.ContainsWidget(w)
isHit := len(w.HoverPointers()) > 0
if !hasTypingFocus && !isHit {
DrawCircle(w.pos, w.size, mathgl.Vec3d{0.3, 0.3, 0.3}, mathgl.Vec3d{1, 1, 1})
} else {
DrawCircle(w.pos, w.size, highlightColor, mathgl.Vec3d{1, 1, 1})
}*/
// Shadow
{
gl.PushMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
gl.Enable(gl.BLEND)
gl.Begin(gl.TRIANGLE_FAN)
{
gl.Color4d(0, 0, 0, 0.3)
gl.Vertex2d(0, 0)
gl.Color4d(0, 0, 0, 0)
nSlices := 16
PLAYER_HALF_WIDTH := 7.74597
dShadowRadius := PLAYER_HALF_WIDTH * 1.75
for nSlice := 0; nSlice <= nSlices; nSlice++ {
gl.Vertex2d(float64(math.Cos(Tau*float64(nSlice)/float64(nSlices))*dShadowRadius), float64(math.Sin(Tau*float64(nSlice)/float64(nSlices))*dShadowRadius))
}
}
gl.End()
gl.Disable(gl.BLEND)
gl.PopMatrix()
}
// eX0 Player
{
gl.PushMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
gl.Rotated(float64(w.rotation), 0, 0, 1)
DrawCircleBorderCustom(np, mgl64.Vec2{16, 16}, mgl64.Vec3{1, 0, 0}, 2, 12, 1, 11)
// Draw the gun
{
gl.Begin(gl.QUADS)
gl.Vertex2d(float64(-1), -float64(3+10))
gl.Vertex2d(float64(-1), -float64(3-1))
gl.Vertex2d(float64(1), -float64(3-1))
gl.Vertex2d(float64(1), -float64(3+10))
gl.End()
}
gl.PopMatrix()
}
if w.mode == Shunpo && !w.skillActive {
DrawCircleBorder(w.pos, mgl64.Vec2{ShunpoRadius * 2, ShunpoRadius * 2}, mgl64.Vec3{0.7, 0.7, 0.7})
}
}
func (w *KatWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
if w.pos.Sub(ParentPosition).Len() <= w.size[0]/2 {
return []Widgeter{w}
} else {
return nil
}
}
func (w *KatWidget) ProcessEvent(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.InputId == 0 && inputEvent.Buttons[0] == false &&
inputEvent.Pointer.Mapping.ContainsWidget(w) && /* TODO: GetHoverer() */ // IsHit(this button) should be true
inputEvent.Pointer.OriginMapping.ContainsWidget(w) { /* TODO: GetHoverer() */ // Make sure we're releasing pointer over same button that it originally went active on, and nothing is in the way (i.e. button is hoverer)
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = []Widgeter{w}
}
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.InputId == 0 && inputEvent.Buttons[0] == true &&
w.mode == Shunpo {
w.target = WidgeterS{w}.GlobalToParent(mgl64.Vec2{inputEvent.Pointer.State.Axes[0], inputEvent.Pointer.State.Axes[1]})
w.skillActive = true
}
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.Pointer.State.Button(1) {
pointerPos := WidgeterS{w}.GlobalToParent(mgl64.Vec2{inputEvent.Pointer.State.Axes[0], inputEvent.Pointer.State.Axes[1]})
if pointerPos.Sub(w.pos).Len() > w.size[0]*2/3 || w.target.Sub(w.pos).Len() > w.size[0]*2/3 {
w.target = pointerPos
}
w.mode = AutoAttack
w.skillActive = false
} else if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.InputId == 'E' && inputEvent.Buttons[0] == true {
w.mode = Shunpo
}
if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.BUTTON_EVENT] && glfw.Key(inputEvent.InputId) == glfw.KeyEscape && inputEvent.Buttons[0] == true {
if w.mode == Shunpo {
// TODO: Make this consume the event, so the window doesn't get closed...
w.mode = AutoAttack
}
}
}
func (w *KatWidget) NotifyChange() {
var timePassed float64 = UniversalClock.TimePassed
// HACK: Should iterate over all typing pointers, not just assume keyboard pointer and its first mapping
hasTypingFocus := keyboardPointer != nil && keyboardPointer.OriginMapping.ContainsWidget(w)
var speed = float64(100.0)
if hasTypingFocus {
if keyboardPointer.State.Button(int(glfw.KeyLeftShift)) || keyboardPointer.State.Button(int(glfw.KeyRightShift)) {
speed *= 0.4
} else if keyboardPointer.State.Button(int(glfw.KeySpace)) {
speed *= 10
}
if keyboardPointer.State.Button(int(glfw.KeyLeft)) && !keyboardPointer.State.Button(int(glfw.KeyRight)) {
w.rotation -= 180 * timePassed
redraw = true
} else if keyboardPointer.State.Button(int(glfw.KeyRight)) && !keyboardPointer.State.Button(int(glfw.KeyLeft)) {
w.rotation += 180 * timePassed
redraw = true
}
var direction mgl64.Vec2
if keyboardPointer.State.Button('A') && !keyboardPointer.State.Button('D') {
direction[0] = -1
redraw = true
} else if keyboardPointer.State.Button('D') && !keyboardPointer.State.Button('A') {
direction[0] = +1
redraw = true
}
if keyboardPointer.State.Button('W') && !keyboardPointer.State.Button('S') {
direction[1] = -1
redraw = true
} else if keyboardPointer.State.Button('S') && !keyboardPointer.State.Button('W') {
direction[1] = +1
redraw = true
}
if direction.Len() != 0 {
rotM := mgl64.Rotate2D(mgl64.DegToRad(w.rotation))
direction = rotM.Mul2x1(direction)
w.target = w.pos.Add(direction.Normalize().Mul(speed * timePassed))
w.pos = w.target
}
}
if w.target.Sub(w.pos).Len() <= speed*timePassed {
w.pos = w.target
} else {
moveBy := w.target.Sub(w.pos)
moveBy = moveBy.Normalize().Mul(speed * timePassed)
w.pos = w.pos.Add(moveBy)
redraw = true
}
if w.skillActive && w.target.Sub(w.pos).Len() <= ShunpoRadius {
w.pos = w.target
w.mode = AutoAttack
w.skillActive = false
}
}
// ---
type AddWidgeter interface {
AddWidget(Widgeter)
}
type RemoveWidgeter interface {
RemoveWidget(Widgeter)
}
// ---
type CompositeWidget struct {
Widget
Widgets Widgeters
}
func NewCompositeWidget(pos mgl64.Vec2, Widgets Widgeters) *CompositeWidget {
w := &CompositeWidget{Widget: NewWidget(pos, np), Widgets: Widgets}
for _, widget := range w.Widgets {
widget.SetParent(w)
}
w.Layout() // TODO: Should this be automatic from above SetParent()?
return w
}
func (w *CompositeWidget) AddWidget(widget Widgeter) {
w.Widgets = append(w.Widgets, widget)
widget.SetParent(w)
w.Layout()
}
func (w *CompositeWidget) RemoveWidget(target Widgeter) {
for index, widget := range w.Widgets {
if widget == target {
copy(w.Widgets[index:], w.Widgets[index+1:])
w.Widgets[len(w.Widgets)-1] = nil
w.Widgets = w.Widgets[:len(w.Widgets)-1]
return
}
}
}
func (w *CompositeWidget) PollLogic() {
for _, widget := range w.Widgets {
widget.PollLogic()
}
}
func (w *CompositeWidget) Close() error {
// TODO: Errors.
for _, widget := range w.Widgets {
_ = widget.Close()
}
return nil
}
func (w *CompositeWidget) Layout() {
w.size = np
for _, widget := range w.Widgets {
bottomRight := widget.Pos().Add(*widget.Size())
for d := 0; d < len(w.size); d++ {
if bottomRight[d] > w.size[d] {
w.size[d] = bottomRight[d]
}
}
}
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.Layout()
}
func (w *CompositeWidget) LayoutNeeded() {
for _, widget := range w.Widgets {
widget.LayoutNeeded()
}
}
func (w *CompositeWidget) Render() {
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
defer gl.Translated(float64(-w.pos[0]), float64(-w.pos[1]), 0)
for _, widget := range w.Widgets {
widget.Render()
}
}
func (w *CompositeWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
LocalPosition := w.ParentToLocal(ParentPosition)
hits := []Widgeter{}
for _, widget := range w.Widgets {
hits = append(hits, widget.Hit(LocalPosition)...)
}
return hits
}
// ---
type FlowLayoutType uint8
const (
HorizontalLayout FlowLayoutType = iota
VerticalLayout
)
type FlowLayoutWidget struct {
CompositeWidget // THINK: Should I use a pointer or value?
options FlowLayoutWidgetOptions
}
type FlowLayoutWidgetOptions struct {
FlowLayoutType
}
func NewFlowLayoutWidget(pos mgl64.Vec2, Widgets []Widgeter, options *FlowLayoutWidgetOptions) *FlowLayoutWidget {
if options == nil {
options = &FlowLayoutWidgetOptions{}
}
w := &FlowLayoutWidget{CompositeWidget: CompositeWidget{Widget: NewWidget(pos, mgl64.Vec2{}), Widgets: Widgets}, options: *options}
// TODO: This is a hack, I'm manually overriding parents of each widget that were set in NewCompositeWidget()
for _, widget := range w.Widgets {
widget.SetParent(w)
}
w.Layout() // TODO: Should this be automatic from above SetParent()?
return w
}
// TEST
func (w *FlowLayoutWidget) SetWidgets(widgets []Widgeter) {
w.Widgets = widgets
// TODO: This is a hack, I'm manually overriding parents of each widget that were set in NewCompositeWidget()
for _, widget := range w.Widgets {
widget.SetParent(w)
}
w.Layout() // TODO: Should this be automatic from above SetParent()?
}
func (w *FlowLayoutWidget) AddWidget(widget Widgeter) {
w.Widgets = append(w.Widgets, widget)
widget.SetParent(w)
w.Layout()
}
func (w *FlowLayoutWidget) Layout() {
w.size = np
var combinedOffset float64
for _, widget := range w.CompositeWidget.Widgets {
pos := np
pos[w.options.FlowLayoutType] = combinedOffset
*widget.Pos() = pos
combinedOffset += widget.Size()[w.options.FlowLayoutType] + 2
bottomRight := widget.Pos().Add(*widget.Size())
for d := 0; d < len(w.size); d++ {
if bottomRight[d] > w.size[d] {
w.size[d] = bottomRight[d]
}
}
}
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.Layout()
}
// ---
type CanvasWidget struct {
CompositeWidget
offset mgl64.Vec2
options CanvasWidgetOptions
PopupTest Widgeter
}
type CanvasWidgetOptions struct {
Scrollable bool
}
func NewCanvasWidget(pos mgl64.Vec2, Widgets []Widgeter, options *CanvasWidgetOptions) *CanvasWidget {
if options == nil {
options = &CanvasWidgetOptions{}
}
w := &CanvasWidget{CompositeWidget: CompositeWidget{Widget: NewWidget(pos, mgl64.Vec2{}), Widgets: Widgets}, options: *options}
for _, widget := range w.Widgets {
widget.SetParent(w)
}
return w
}
func (w *CanvasWidget) AddWidget(widget Widgeter) {
w.Widgets = append(w.Widgets, widget)
widget.SetParent(w)
w.Layout()
}
// HACK: Offset all widgets by (1, 1) so their border is visible. Need to generalize this.
func (w *CanvasWidget) offsetBy1Px() {
for _, widget := range w.Widgets {
*widget.Pos() = widget.Pos().Add(mgl64.Vec2{1, 1})
}
}
func (w *CanvasWidget) PollLogic() {
var timePassed float64 = UniversalClock.TimePassed
if w.options.Scrollable {
// HACK: Should iterate over all typing pointers, not just assume keyboard pointer and its first mapping
// HACK: Instead of checking for exclusive focus, should have a waterfall model with low priority checking.
hasExclusiveTypingFocus := keyboardPointer != nil && len(keyboardPointer.OriginMapping) == 1 && w == keyboardPointer.OriginMapping[0]
if hasExclusiveTypingFocus {
var speed = float64(1000.0)
if keyboardPointer.State.Button(int(glfw.KeyLeftShift)) || keyboardPointer.State.Button(int(glfw.KeyRightShift)) {
speed *= 0.4
} else if keyboardPointer.State.Button(int(glfw.KeySpace)) {
speed *= 10
}
var direction mgl64.Vec2
if keyboardPointer.State.Button(int(glfw.KeyLeft)) && !keyboardPointer.State.Button(int(glfw.KeyRight)) {
direction[0] = +1
redraw = true
} else if keyboardPointer.State.Button(int(glfw.KeyRight)) && !keyboardPointer.State.Button(int(glfw.KeyLeft)) {
direction[0] = -1
redraw = true
}
if keyboardPointer.State.Button(int(glfw.KeyUp)) && !keyboardPointer.State.Button(int(glfw.KeyDown)) {
direction[1] = +1
redraw = true
} else if keyboardPointer.State.Button(int(glfw.KeyDown)) && !keyboardPointer.State.Button(int(glfw.KeyUp)) {
direction[1] = -1
redraw = true
}
if direction.Len() != 0 {
w.offset = w.offset.Add(direction.Normalize().Mul(speed * timePassed))
} else {
for i := range w.offset {
w.offset[i] = math.Floor(w.offset[i] + 0.5)
}
}
}
}
if w.PopupTest != nil {
w.PopupTest.PollLogic()
}
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.CompositeWidget.PollLogic()
}
func (w *CanvasWidget) Layout() {
// HACK
var windowSize0, windowSize1 int
if globalWindow != nil {
windowSize0, windowSize1 = globalWindow.GetSize()
}
windowSize := mgl64.Vec2{float64(windowSize0), float64(windowSize1)} // HACK: This is not updated as window resizes, etc.
w.size = windowSize
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.Layout()
}
func (w *CanvasWidget) LayoutNeeded() {
if w.PopupTest != nil {
w.PopupTest.LayoutNeeded()
}
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.CompositeWidget.LayoutNeeded()
}
func (w *CanvasWidget) Render() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(w.pos[0], w.pos[1], 0)
// Background.
{
backgroundTopColor := mgl64.Vec3{52 / 255.0, 102 / 255.0, 164 / 255.0}
backgroundBottomColor := mgl64.Vec3{227 / 255.0, 189 / 255.0, 162 / 255.0}
DrawBorderlessGradientBox(w.pos, w.size, backgroundTopColor, backgroundBottomColor)
}
gl.Translated(w.offset[0], w.offset[1], 0)
for _, widget := range w.Widgets {
widget.Render()
}
if w.PopupTest != nil {
gl.PushMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
w.PopupTest.Render()
gl.PopMatrix()
}
}
func (w *CanvasWidget) ProcessEvent(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.WINDOWING && inputEvent.EventTypes[events.AXIS_EVENT] {
w.Layout()
}
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.InputId == 0 && inputEvent.Buttons[0] == true &&
inputEvent.Pointer.Mapping.ContainsWidget(w) && /* TODO: GetHoverer() */ // IsHit(this button) should be true
inputEvent.Pointer.OriginMapping.ContainsWidget(w) { /* TODO: GetHoverer() */ // Make sure we're releasing pointer over same button that it originally went active on, and nothing is in the way (i.e. button is hoverer)
// HACK: Give exclusive keyboard capture if Canvas is the only thing clicked, and assume others will steal/add focus if they want.
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = []Widgeter{w}
}
if w.options.Scrollable {
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.SLIDER_EVENT] && inputEvent.InputId == 2 {
w.offset[1] += inputEvent.Sliders[0] * 10
w.offset[0] += inputEvent.Sliders[1] * 10
}
}
// TODO: Make this happen as a PostProcessEvent if it hasn't been processed by an earlier widget, etc.
if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.Buttons[0] == true {
switch glfw.Key(inputEvent.InputId) {
// TEST, DEBUG: Cmd+O shortcut to open a new window, for testing purposes
case glfw.KeyO:
if inputEvent.ModifierKey&glfw.ModSuper != 0 {
w2 := NewWindowWidget(mgl64.Vec2{600, 400}, mgl64.Vec2{300, 60}, NewTextBoxWidget(np))
w2.Name = "New Window"
// Add new widget to canvas
w.AddWidget(w2)
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = []Widgeter{w2}
}
case glfw.KeyP:
if inputEvent.ModifierKey == glfw.ModSuper {
textBox := NewTextBoxWidget(np)
w.PopupTest = NewSpacerWidget(mgl64.Vec2{200, 0}, textBox)
originalMapping := keyboardPointer.OriginMapping // HACK
closeOnEscape := &CustomWidget{
Widget: NewWidget(np, np),
PollLogicFunc: func(this *CustomWidget) {
if !keyboardPointer.OriginMapping.ContainsWidget(this.Parent()) {
w.PopupTest = nil
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = originalMapping
}
},
ProcessEventFunc: func(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.Buttons[0] == true {
switch glfw.Key(inputEvent.InputId) {
case glfw.KeyEnter:
content := textBox.Content.Content()
{
globalPosition := mgl64.Vec2{mousePointer.State.Axes[0], mousePointer.State.Axes[1]}
localPosition := WidgeterS{w}.GlobalToLocal(globalPosition)
body := NewTextFileWidget(np, content)
w2 := NewWindowWidget(localPosition, mgl64.Vec2{}, body)
w2.Name = content
// Add new widget to canvas
w.AddWidget(w2)
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = []Widgeter{w2}
}
w.PopupTest = nil
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = originalMapping
case glfw.KeyEscape:
w.PopupTest = nil
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = originalMapping
}
}
},
}
// TODO: textBox.AddExtension(closeOnEscape).
{
closeOnEscape.SetParent(textBox)
textBox.ExtensionsTest = append(textBox.ExtensionsTest, closeOnEscape)
}
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = []Widgeter{textBox}
}
case glfw.KeyEscape:
keepRunning = false
}
}
}
func (w *CanvasWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
LocalPosition := w.ParentToLocal(ParentPosition)
if len(w.Widget.Hit(ParentPosition)) > 0 {
hits := []Widgeter{w}
for _, widget := range w.Widgets {
hits = append(hits, widget.Hit(LocalPosition)...)
}
return hits
} else {
return nil
}
}
func (w *CanvasWidget) ParentToLocal(ParentPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) {
return w.Widget.ParentToLocal(ParentPosition).Sub(w.offset)
}
// ---
type CollapsibleWidget struct {
Widget
state *TriButtonWidget //*TriButtonExternalStateWidget
label Widgeter // HACK: Manually adding widgets, this doesn't scale, need to use CompositeWidget or make Widgeter naturally extendable, etc.
child Widgeter
}
func NewCollapsibleWidget(pos mgl64.Vec2, child Widgeter, title string) *CollapsibleWidget {
w := &CollapsibleWidget{Widget: NewWidget(pos, np), child: child}
w.child.SetParent(w)
//state := true
//w.state = NewTriButtonExternalStateWidget(np, func() bool { return state }, func() { state = !state })
w.state = NewTriButtonWidget(np, func() {})
//w.state.state = true // HACK
w.state.SetParent(w) // For its Layout() to work...
w.label = NewTextLabelWidgetString(mgl64.Vec2{w.state.Size()[0] + 2, 0}, title) // HACK, should use FlowLayout or something
w.label.SetParent(w) // For its Layout() to work...
// HACK: Set position of child
w.child.Pos()[0] = w.state.Size()[0] + 2
w.child.Pos()[1] = w.state.Size()[1] + 2
// TODO: This needs to be automated, easy to forget, moved into Layout2(), etc.
w.Layout() // TODO: Should this be automatic from above SetParent()?
return w
}
func (w *CollapsibleWidget) PollLogic() {
w.state.PollLogic()
w.label.PollLogic()
if w.state.State() {
w.child.PollLogic()
}
}
func (w *CollapsibleWidget) Close() error {
// TODO: Errors.
_ = w.state.Close()
_ = w.label.Close()
_ = w.child.Close()
return nil
}
func (w *CollapsibleWidget) Layout() {
// HACK
Widgets := []Widgeter{w.state, w.label}
if w.state.State() {
Widgets = append(Widgets, w.child)
}
w.size = np
for _, widget := range Widgets {
bottomRight := widget.Pos().Add(*widget.Size())
for d := 0; d < len(w.size); d++ {
if bottomRight[d] > w.size[d] {
w.size[d] = bottomRight[d]
}
}
}
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.Layout()
}
func (w *CollapsibleWidget) LayoutNeeded() {
w.state.LayoutNeeded()
w.label.LayoutNeeded()
if w.state.State() {
w.child.LayoutNeeded()
}
}
func (w *CollapsibleWidget) Render() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
w.state.Render()
w.label.Render()
if w.state.State() {
w.child.Render()
}
}
func (w *CollapsibleWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
LocalPosition := w.ParentToLocal(ParentPosition)
hits := w.state.Hit(LocalPosition)
if w.state.State() {
hits = append(hits, w.child.Hit(LocalPosition)...)
}
return hits
}
// ---
type BackgroundWidget struct {
Widget
child Widgeter
border int
}
func NewBackgroundWidget(pos mgl64.Vec2, child Widgeter) *BackgroundWidget {
w := &BackgroundWidget{Widget: NewWidget(pos, np), child: child, border: 2}
w.child.SetParent(w)
w.Layout() // TODO: Should this be automatic from above SetParent()?
return w
}
func (w *BackgroundWidget) PollLogic() {
w.child.PollLogic()
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.PollLogic()
}
func (w *BackgroundWidget) Close() error {
// TODO: Errors.
_ = w.child.Close()
return nil
}
func (w *BackgroundWidget) Layout() {
w.Widget.size = w.child.Size().Add(mgl64.Vec2{float64(w.border * 2), float64(w.border * 2)})
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.Layout()
}
func (w *BackgroundWidget) LayoutNeeded() {
w.child.LayoutNeeded()
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.LayoutNeeded()
}
func (w *BackgroundWidget) Render() {
DrawBox(w.pos, w.size, lightColor, lightColor)
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(float64(w.pos[0]+float64(w.border)), float64(w.pos[1]+float64(w.border)), 0)
w.child.Render()
}
func (w *BackgroundWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
LocalPosition := w.ParentToLocal(ParentPosition)
hits := w.child.Hit(LocalPosition)
return hits
}
func (w *BackgroundWidget) ParentToLocal(ParentPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) {
return w.Widget.ParentToLocal(ParentPosition).Sub(mgl64.Vec2{float64(w.border), float64(w.border)})
}
// ---
type OptionalHighlighter struct {
highlighter Highlighter
stateFunc func() bool
}
func NewOptionalHighlighter(highlighter Highlighter, stateFunc func() bool) *OptionalHighlighter {
return &OptionalHighlighter{
highlighter: highlighter,
stateFunc: stateFunc,
}
}
func (this *OptionalHighlighter) Highlighter() Highlighter {
if !this.stateFunc() {
return nil
}
return this.highlighter
}
// ---
type ScrollPaneWidget struct {
Widget
child Widgeter
}
func NewScrollPaneWidget(pos, size mgl64.Vec2, child Widgeter) *ScrollPaneWidget {
w := &ScrollPaneWidget{Widget: NewWidget(pos, size), child: child}
w.child.SetParent(w)
return w
}
func (w *ScrollPaneWidget) PollLogic() {
w.child.PollLogic()
}
func (w *ScrollPaneWidget) Close() error {
// TODO: Errors.
_ = w.child.Close()
return nil
}
func (w *ScrollPaneWidget) Layout() {
// Keep the child widget within the scroll pane
for i := 0; i < 2; i++ {
if w.child.Pos()[i]+w.child.Size()[i] < w.size[i] {
w.child.Pos()[i] = w.size[i] - w.child.Size()[i]
}
if w.child.Pos()[i] > 0 {
w.child.Pos()[i] = 0
}
}
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.Layout()
}
/*func (w *ScrollPaneWidget) scrollToPoint(target mathgl.Vec2d) {
for i := 0; i < 2; i++ {
if w.child.Pos()[i]+target[i] < 0 {
w.child.Pos()[i] = -target[i]
} else if w.child.Pos()[i]+target[i] > w.size[i] {
w.child.Pos()[i] = w.size[i] - target[i]
}
}
}*/
func (w *ScrollPaneWidget) ScrollToArea(target, size mgl64.Vec2) {
for i := 0; i < 2; i++ {
// Move the minimum amount that increases overlap
w.child.Pos()[i] += math.Min(math.Dim(-w.child.Pos()[i], target[i]), math.Dim(-w.child.Pos()[i]+w.size[i], target[i]+size[i]))
w.child.Pos()[i] -= math.Min(math.Dim(target[i], -w.child.Pos()[i]), math.Dim(target[i]+size[i], -w.child.Pos()[i]+w.size[i]))
}
// TODO: Needed?
//w.Layout()
}
func (w *ScrollPaneWidget) CenterOnArea(target, size mgl64.Vec2) {
for i := 0; i < 2; i++ {
w.child.Pos()[i] = -(target[i] + size[i]/2) + w.size[i]/2
}
// This is needed to normalize the view area, as the above potentially places the viewport outside of the widget
w.Layout()
}
func (w *ScrollPaneWidget) ScrollToBottom() {
w.child.Pos()[1] = w.child.Pos()[1] - w.size[1]
// TODO: This shouldn't be needed if above logic is smarter.
// This is needed to normalize the view area, as the above potentially places the viewport outside of the widget
w.Layout()
}
func NearInt64(value float64) int64 {
if value >= 0 {
return int64(value + 0.5)
} else {
return int64(value - 0.5)
}
}
func SetScissor(pos, size mgl64.Vec2) {
var ModelMatrix [16]float64
var ProjectionMatrix [16]float64
var Viewport [4]int32
gl.GetDoublev(gl.MODELVIEW_MATRIX, &ModelMatrix[0])
gl.GetDoublev(gl.PROJECTION_MATRIX, &ProjectionMatrix[0])
gl.GetIntegerv(gl.VIEWPORT, &Viewport[0])
p0 := mgl64.Project(mgl64.Vec3{pos[0], pos[1] + size[1] /* Inverted y coordinate. */, 0},
mgl64.Mat4{float64(ModelMatrix[0]), float64(ModelMatrix[1]), float64(ModelMatrix[2]), float64(ModelMatrix[3]), float64(ModelMatrix[4]), float64(ModelMatrix[5]), float64(ModelMatrix[6]), float64(ModelMatrix[7]), float64(ModelMatrix[8]), float64(ModelMatrix[9]), float64(ModelMatrix[10]), float64(ModelMatrix[11]), float64(ModelMatrix[12]), float64(ModelMatrix[13]), float64(ModelMatrix[14]), float64(ModelMatrix[15])},
mgl64.Mat4{float64(ProjectionMatrix[0]), float64(ProjectionMatrix[1]), float64(ProjectionMatrix[2]), float64(ProjectionMatrix[3]), float64(ProjectionMatrix[4]), float64(ProjectionMatrix[5]), float64(ProjectionMatrix[6]), float64(ProjectionMatrix[7]), float64(ProjectionMatrix[8]), float64(ProjectionMatrix[9]), float64(ProjectionMatrix[10]), float64(ProjectionMatrix[11]), float64(ProjectionMatrix[12]), float64(ProjectionMatrix[13]), float64(ProjectionMatrix[14]), float64(ProjectionMatrix[15])},
int(Viewport[0]), int(Viewport[1]), int(Viewport[2]), int(Viewport[3]))
p1 := mgl64.Project(mgl64.Vec3{size[0], size[1], 0},
mgl64.Mat4{float64(ModelMatrix[0]), float64(ModelMatrix[1]), float64(ModelMatrix[2]), float64(ModelMatrix[3]), float64(ModelMatrix[4]), float64(ModelMatrix[5]), float64(ModelMatrix[6]), float64(ModelMatrix[7]), float64(ModelMatrix[8]), float64(ModelMatrix[9]), float64(ModelMatrix[10]), float64(ModelMatrix[11]), float64(ModelMatrix[12]), float64(ModelMatrix[13]), float64(ModelMatrix[14]), float64(ModelMatrix[15])},
mgl64.Mat4{float64(ProjectionMatrix[0]), float64(ProjectionMatrix[1]), float64(ProjectionMatrix[2]), float64(ProjectionMatrix[3]), float64(ProjectionMatrix[4]), float64(ProjectionMatrix[5]), float64(ProjectionMatrix[6]), float64(ProjectionMatrix[7]), float64(ProjectionMatrix[8]), float64(ProjectionMatrix[9]), float64(ProjectionMatrix[10]), float64(ProjectionMatrix[11]), float64(ProjectionMatrix[12]), float64(ProjectionMatrix[13]), float64(ProjectionMatrix[14]), float64(ProjectionMatrix[15])},
int(Viewport[0]), int(Viewport[1]), int(Viewport[2]), int(Viewport[3]))
p2 := mgl64.Project(mgl64.Vec3{0, 0, 0},
mgl64.Mat4{float64(ModelMatrix[0]), float64(ModelMatrix[1]), float64(ModelMatrix[2]), float64(ModelMatrix[3]), float64(ModelMatrix[4]), float64(ModelMatrix[5]), float64(ModelMatrix[6]), float64(ModelMatrix[7]), float64(ModelMatrix[8]), float64(ModelMatrix[9]), float64(ModelMatrix[10]), float64(ModelMatrix[11]), float64(ModelMatrix[12]), float64(ModelMatrix[13]), float64(ModelMatrix[14]), float64(ModelMatrix[15])},
mgl64.Mat4{float64(ProjectionMatrix[0]), float64(ProjectionMatrix[1]), float64(ProjectionMatrix[2]), float64(ProjectionMatrix[3]), float64(ProjectionMatrix[4]), float64(ProjectionMatrix[5]), float64(ProjectionMatrix[6]), float64(ProjectionMatrix[7]), float64(ProjectionMatrix[8]), float64(ProjectionMatrix[9]), float64(ProjectionMatrix[10]), float64(ProjectionMatrix[11]), float64(ProjectionMatrix[12]), float64(ProjectionMatrix[13]), float64(ProjectionMatrix[14]), float64(ProjectionMatrix[15])},
int(Viewport[0]), int(Viewport[1]), int(Viewport[2]), int(Viewport[3]))
pos0 := NearInt64(float64(p0[0]))
pos1 := NearInt64(float64(p0[1]))
size0 := NearInt64(float64(p1[0] - p2[0]))
size1 := NearInt64(float64(p2[1] - p1[1]))
// Crop the scissor box by the parent scissor box
// TODO
gl.Scissor(int32(pos0), int32(pos1), int32(size0), int32(size1))
}
func (w *ScrollPaneWidget) LayoutNeeded() {
w.child.LayoutNeeded()
}
func (w *ScrollPaneWidget) Render() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
SetScissor(mgl64.Vec2{-1, -1}, w.size.Add(mgl64.Vec2{2, 2}))
gl.Enable(gl.SCISSOR_TEST)
w.child.Render()
gl.Disable(gl.SCISSOR_TEST)
// Draw scrollbars, if needed
{
const scrollbarWidth = 2
// Vertical
if w.child.Size()[1] > w.size[1] {
DrawBorderlessBox(mgl64.Vec2{w.size[0] - scrollbarWidth + 1, -w.child.Pos()[1]/w.child.Size()[1]*(w.size[1]+2) - 1},
mgl64.Vec2{scrollbarWidth, w.size[1] / w.child.Size()[1] * (w.size[1] + 2)},
darkColor)
}
// Horizontal
if w.child.Size()[0] > w.size[0] {
DrawBorderlessBox(mgl64.Vec2{-w.child.Pos()[0]/w.child.Size()[0]*(w.size[0]+2) - 1, w.size[1] - scrollbarWidth + 1},
mgl64.Vec2{w.size[0] / w.child.Size()[0] * (w.size[0] + 2), scrollbarWidth},
darkColor)
}
}
}
func (w *ScrollPaneWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
LocalPosition := w.ParentToLocal(ParentPosition)
if len(w.Widget.Hit(ParentPosition)) > 0 {
hits := []Widgeter{w}
hits = append(hits, w.child.Hit(LocalPosition)...)
return hits
} else {
return nil
}
}
func (w *ScrollPaneWidget) ProcessEvent(inputEvent InputEvent) {
var moved bool
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.SLIDER_EVENT] && inputEvent.InputId == 2 {
w.child.Pos()[0] += inputEvent.Sliders[1] * 10
w.child.Pos()[1] += inputEvent.Sliders[0] * 10
moved = true
}
// TODO: This should move cursor so it's on screen?
if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.Buttons[0] == true {
switch glfw.Key(inputEvent.InputId) {
case glfw.KeyUp:
if inputEvent.ModifierKey == glfw.ModControl|glfw.ModAlt {
w.child.Pos()[1] += fontHeight
moved = true
}
case glfw.KeyDown:
if inputEvent.ModifierKey == glfw.ModControl|glfw.ModAlt {
w.child.Pos()[1] -= fontHeight
moved = true
}
}
}
if moved {
// HACK: Snap to nearest point. This prevents smaller scroll increments from being possible.
w.child.Pos()[0] = float64(NearInt64(w.child.Pos()[0]))
w.child.Pos()[1] = float64(NearInt64(w.child.Pos()[1]))
w.Layout()
}
}
// ---
type SpacerWidget struct {
Widget
child Widgeter
border int
}
func NewSpacerWidget(pos mgl64.Vec2, child Widgeter) *SpacerWidget {
w := &SpacerWidget{Widget: NewWidget(pos, np), child: child, border: 2}
w.child.SetParent(w)
w.Layout() // TODO: Should this be automatic from above SetParent()?
return w
}
func (w *SpacerWidget) PollLogic() {
w.child.PollLogic()
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.PollLogic()
}
func (w *SpacerWidget) Close() error {
// TODO: Errors.
_ = w.child.Close()
return nil
}
func (w *SpacerWidget) Layout() {
w.Widget.size = w.child.Size().Add(mgl64.Vec2{float64(w.border * 2), float64(w.border * 2)})
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.Layout()
}
func (w *SpacerWidget) LayoutNeeded() {
w.child.LayoutNeeded()
// TODO: Standardize this mess... have graph-level func that don't get overriden, and class-specific funcs to be overridden
w.Widget.LayoutNeeded()
}
func (w *SpacerWidget) Render() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(float64(w.pos[0]+float64(w.border)), float64(w.pos[1]+float64(w.border)), 0)
w.child.Render()
}
func (w *SpacerWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
LocalPosition := w.ParentToLocal(ParentPosition)
hits := w.child.Hit(LocalPosition)
return hits
}
func (w *SpacerWidget) ParentToLocal(ParentPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) {
return w.Widget.ParentToLocal(ParentPosition).Sub(mgl64.Vec2{float64(w.border), float64(w.border)})
}
// ---
type UnderscoreSepToCamelCaseWidget struct {
Widget
window *glfw.Window
}
func (w *UnderscoreSepToCamelCaseWidget) Render() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
//s := w.window.GetClipboardString()
s := "get_clipboard_string"
// E.g., get_clipboard_string -> GetClipboardString
s += " -> " + UnderscoreSepToCamelCase(s)
w.size[0] = float64(8 * len(s))
w.size[1] = 16
gl.Color3d(0.3, 0.3, 0.3)
gl.Rectd(0-1, 0-1, float64(w.size[0]+1), float64(w.size[1]+1))
gl.Color3d(1, 1, 1)
gl.Rectd(0, 0, float64(w.size[0]), float64(w.size[1]))
gl.Color3d(0, 0, 0)
NewOpenGlStream(mgl64.Vec2{0, 0}).PrintText(s)
}
// ---
type ChannelExpeWidget struct {
*FlowLayoutWidget
cmd *exec.Cmd
ch ChanWriter
}
func NewChannelExpeWidget(pos mgl64.Vec2) *ChannelExpeWidget {
w := &ChannelExpeWidget{ch: make(ChanWriter)}
action := func() {
// Comments are currently not preserved in the tooltip
if w.cmd == nil {
w.cmd = exec.Command("ping", "google.com")
w.cmd.Stdout = w.ch
w.cmd.Stderr = w.ch
err := w.cmd.Start()
if err != nil {
panic(err)
}
go w.cmd.Wait() // It looks like I need to wait for the process, else it doesn't terminate properly
} else {
//w.cmd.Process.Kill()
w.cmd.Process.Signal(os.Interrupt)
w.cmd = nil
}
}
w.FlowLayoutWidget = NewFlowLayoutWidget(pos,
[]Widgeter{
NewTriButtonExternalStateWidget(np, func() bool { return w.cmd != nil }, action),
NewTextBoxWidget(np),
}, nil)
UniversalClock.AddChangeListener(w)
return w
}
func (w *ChannelExpeWidget) NotifyChange() {
select {
case b, ok := <-w.ch:
if ok {
box := w.FlowLayoutWidget.Widgets[1].(*TextBoxWidget)
SetViewGroup(box.Content, box.Content.Content()+string(b))
redraw = true
}
default:
}
}
// ---
type FooWidget2 struct {
*FlowLayoutWidget
}
func NewFooWidget2(pos mgl64.Vec2) Widgeter {
listWidget := NewTextBoxWidget(np)
listWidgetScrollPane := NewScrollPaneWidget(np, mgl64.Vec2{fontWidth * 80, fontHeight * 12}, listWidget)
searchField := NewTextBoxWidgetOptions(np, TextBoxWidgetOptions{SingleLine: true})
actions := &CustomWidget{
Widget: NewWidget(np, np),
ProcessEventFunc: func(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.Buttons[0] == true {
switch glfw.Key(inputEvent.InputId) {
case glfw.KeyEnter:
type tokenText struct {
t rune
s string
}
var s text_scanner.Scanner
s.Init(strings.NewReader(searchField.Content.Content()))
s.Mode = text_scanner.ScanIdents | text_scanner.ScanStrings
var tokens []tokenText
for tok := s.Scan(); tok != text_scanner.EOF; tok = s.Scan() {
tokens = append(tokens, tokenText{t: tok, s: s.TokenText()})
}
var out string
var cmd []string
for _, tt := range tokens {
out += fmt.Sprintf("%v:%v ", text_scanner.TokenString(tt.t), tt.s)
cmd = append(cmd, tt.s)
}
switch {
case len(cmd) == 1 && cmd[0] == "exit":
fmt.Println("Closing.")
keepRunning = false
}
SetViewGroup(listWidget.Content, listWidget.Content.Content()+out+"\n")
SetViewGroup(searchField.Content, "")
}
}
},
}
searchField.ExtensionsTest = append(searchField.ExtensionsTest, actions)
widgets := []Widgeter{listWidgetScrollPane, searchField}
w := &FooWidget2{FlowLayoutWidget: NewFlowLayoutWidget(pos, widgets, &FlowLayoutWidgetOptions{FlowLayoutType: VerticalLayout})}
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
keyboardPointer.OriginMapping = []Widgeter{searchField}
return w
}
type FooWidget struct {
Widget
text string
}
func NewFooWidget(pos mgl64.Vec2) Widgeter {
w := &FooWidget{Widget: NewWidget(pos, mgl64.Vec2{180, 30})}
return w
}
func (w *FooWidget) Render() {
hasTypingFocus := keyboardPointer != nil && keyboardPointer.OriginMapping.ContainsWidget(w)
if hasTypingFocus {
DrawYBox(w.pos, w.size)
} else {
DrawNBox(w.pos, w.size)
}
glt := NewOpenGlStream(w.pos)
gl.Color3d(0, 0, 0)
glt.PrintText(w.text)
if hasTypingFocus {
expandedCaretPosition, caretLine := len(w.text), 0
// Draw caret
gl.PushMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
gl.Color3d(0, 0, 0)
gl.Recti(int32(expandedCaretPosition*fontWidth-1), int32(caretLine*fontHeight), int32(expandedCaretPosition*fontWidth+1), int32(caretLine*fontHeight)+fontHeight)
gl.PopMatrix()
}
}
func (w *FooWidget) ProcessEvent(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.InputId == 0 && inputEvent.Buttons[0] == true &&
inputEvent.Pointer.Mapping.ContainsWidget(w) && /* TODO: GetHoverer() */ // IsHit(this button) should be true
inputEvent.Pointer.OriginMapping.ContainsWidget(w) { /* TODO: GetHoverer() */ // Make sure we're releasing pointer over same button that it originally went active on, and nothing is in the way (i.e. button is hoverer)
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
if !keyboardPointer.OriginMapping.ContainsWidget(w) {
keyboardPointer.OriginMapping = []Widgeter{w}
}
}
if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.Buttons[0] == true {
switch glfw.Key(inputEvent.InputId) {
case glfw.KeyBackspace:
if len(w.text) > 0 {
w.text = w.text[:len(w.text)-1]
}
}
}
if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.CHARACTER_EVENT] && inputEvent.InputId < 128 {
w.text += string(inputEvent.InputId)
}
}
func (w *FooWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
if len(w.Widget.Hit(ParentPosition)) > 0 {
return []Widgeter{w}
} else {
return nil
}
}
// ---
type commandNode struct {
w *LiveCmdExpeWidget
template CmdFactory
DepNode2
}
func (this *commandNode) Update() {
if this.w.cmd != nil && this.w.cmd.ProcessState == nil {
//w.cmd.Process.Kill()
this.w.cmd.Process.Signal(os.Interrupt)
//w.cmd.Process.Signal(syscall.SIGTERM)
//fmt.Println("sigint'ed process", this.w.cmd.Process.Pid)
this.w.cmd = nil
}
SetViewGroup(this.w.Content, "")
MakeUpdatedLock.Unlock() // HACK: Needed because (*CmdTemplateDynamic2) NewCommand() calls MakeUpdated().
this.w.cmd = this.template.NewCommand()
MakeUpdatedLock.Lock() // HACK
this.w.stdoutChan = make(ChanWriter)
this.w.stderrChan = make(ChanWriter)
this.w.cmd.Stdout = this.w.stdoutChan
this.w.cmd.Stderr = this.w.stderrChan
err := this.w.cmd.Start()
if err != nil {
this.w.cmd = nil
return
}
//fmt.Printf("started new process %v %+v\n", this.w.cmd.Process.Pid, this.w.cmd.Args)
go func(cmd *exec.Cmd) {
_ = cmd.Wait()
//fmt.Println("waited til end of", cmd.Process.Pid)
this.w.finishedChan <- cmd.ProcessState
}(this.w.cmd)
}
type LiveCmdExpeWidget struct {
*TextBoxWidget
commandNode *commandNode
cmd *exec.Cmd
stdoutChan ChanWriter
stderrChan ChanWriter
finishedChan chan *os.ProcessState
DepNode2Manual // FinishedDepNode2
}
func NewLiveCmdExpeWidget(pos mgl64.Vec2, dependees []DepNode2I, template CmdFactory) *LiveCmdExpeWidget {
w := &LiveCmdExpeWidget{TextBoxWidget: NewTextBoxWidget(pos), finishedChan: make(chan *os.ProcessState)}
// THINK: The only reason to have a separate command node is because current NotifyChange() does not tell the originator of change, so I can't tell UniversalClock's changes from dependee changes (and I need to do different actions for each)
w.commandNode = &commandNode{w: w, template: template}
w.commandNode.AddSources(dependees...)
return w
}
func (w *LiveCmdExpeWidget) PollLogic() {
MakeUpdated(w.commandNode) // THINK: Is this a hack or is this the way to go?
w.layout2Test()
w.TextBoxWidget.PollLogic()
}
func (w *LiveCmdExpeWidget) Close() error {
// TODO: Kill live cmd...
// TODO: Errors.
_ = w.TextBoxWidget.Close()
return nil
}
func (w *LiveCmdExpeWidget) layout2Test() {
select {
case b, ok := <-w.stdoutChan:
if ok {
SetViewGroup(w.Content, w.Content.Content()+string(b))
redraw = true
}
default:
}
select {
case b, ok := <-w.stderrChan:
if ok {
SetViewGroup(w.Content, w.Content.Content()+string(b))
redraw = true
}
default:
}
select {
/*case processState := <-w.finishedChan:
if processState.Success() {
// TODO: Is ChangeListener stuff a good fit for these not-really-change events?
w.SuccessDepNode.NotifyAllListeners()
}*/
case <-w.finishedChan:
ExternallyUpdated(w)
default:
}
}
func (w *LiveCmdExpeWidget) Render() {
w.TextBoxWidget.Render()
}
// ---
type WriterNotifier struct {
io.Writer
n func()
}
func (wn *WriterNotifier) Write(p []byte) (n int, err error) {
wn.n()
n, err = wn.Writer.Write(p)
wn.n()
return
}
type pipeNode struct {
w *LivePipeExpeWidget
p PipeFactory
s *pipe.State
DepNode2
}
func (this *pipeNode) Update() {
if this.s != nil {
this.s.Kill()
this.s = nil
}
SetViewGroup(this.w.Content, "")
notify := func() {
//println("yo, notify, yo?")
//redraw = true
//glfw.PostEmptyEvent()
}
this.w.stdoutChan = &WriterNotifier{make(ChanWriter), notify}
this.w.stderrChan = &WriterNotifier{make(ChanWriter), notify}
var p pipe.Pipe
MakeUpdatedLock.Unlock() // HACK: Needed because NewPipe() calls MakeUpdated().
this.s, p = this.p.NewPipe(this.w.stdoutChan, this.w.stderrChan)
MakeUpdatedLock.Lock() // HACK
go func(s *pipe.State, p pipe.Pipe) {
err := p(s)
if err == nil {
err = this.s.RunTasks()
}
//close(this.w.stdoutChan) // This is causing panics because pipe tries to write to closed channel.
//close(this.w.stderrChan)
this.w.finishedChan <- err
}(this.s, p)
}
type LivePipeExpeWidget struct {
*TextBoxWidget
pipeNode *pipeNode
stdoutChan *WriterNotifier
stderrChan *WriterNotifier
finishedChan chan error
DepNode2Manual // FinishedDepNode2
}
func NewLivePipeExpeWidget(pos mgl64.Vec2, dependees []DepNode2I, p PipeFactory) *LivePipeExpeWidget {
w := &LivePipeExpeWidget{TextBoxWidget: NewTextBoxWidget(pos), finishedChan: make(chan error)}
// THINK: The only reason to have a separate pipe node is because current NotifyChange() does not tell the originator of change, so I can't tell UniversalClock's changes from dependee changes (and I need to do different actions for each)
w.pipeNode = &pipeNode{w: w, p: p}
w.pipeNode.AddSources(dependees...)
return w
}
func (w *LivePipeExpeWidget) PollLogic() {
MakeUpdated(w.pipeNode) // THINK: Is this a hack or is this the way to go?
w.layout2Test()
w.TextBoxWidget.PollLogic()
}
func (w *LivePipeExpeWidget) Close() error {
if w.pipeNode.s != nil {
w.pipeNode.s.Kill()
w.pipeNode.s = nil
}
// TODO: Errors.
_ = w.TextBoxWidget.Close()
return nil
}
func (w *LivePipeExpeWidget) layout2Test() {
select {
case b, ok := <-w.stdoutChan.Writer.(ChanWriter):
if ok {
SetViewGroup(w.Content, w.Content.Content()+string(b))
redraw = true
}
default:
}
select {
case b, ok := <-w.stderrChan.Writer.(ChanWriter):
if ok {
SetViewGroup(w.Content, w.Content.Content()+string(b))
redraw = true
}
default:
}
select {
/*case processState := <-w.finishedChan:
if processState.Success() {
// TODO: Is ChangeListener stuff a good fit for these not-really-change events?
w.SuccessDepNode.NotifyAllListeners()
}*/
case <-w.finishedChan:
ExternallyUpdated(w)
default:
}
}
func (w *LivePipeExpeWidget) Render() {
w.TextBoxWidget.Render()
}
// ---
type actionNode struct {
owner *LiveGoroutineExpeWidget
params func() interface{}
action func(interface{}) string
DepNode2
}
func (this *actionNode) Update() {
// TODO: See if it's possible to have a _general_ solution to EOL goroutines whose work has become obsolete
// In order not to overload the system, only start a new goroutine if all others have finished
if this.owner.lastStartedT == this.owner.lastFinishedT {
this.owner.lastStartedT++
ti := this.owner.lastStartedT
//this.owner.Content.Set(this.action()); _ = ti
go func(params interface{}) {
//defer close(outChan)
//started := time.Now()
ts := timestampString{this.action(params), ti}
//fmt.Println(time.Since(started).Seconds())
this.owner.outChan <- ts
}(this.params())
} else {
// TODO: Make the update happen later, once the current goroutine is done?
}
}
type timestampString struct {
s string
t uint32
}
type LiveGoroutineExpeWidget struct {
*FlowLayoutWidget
actionNode *actionNode
outChan chan timestampString
lastStartedT, lastFinishedT uint32
live bool
}
func NewLiveGoroutineExpeWidget(pos mgl64.Vec2, live bool, dependees []DepNode2I, params func() interface{}, action func(interface{}) string) *LiveGoroutineExpeWidget {
w := &LiveGoroutineExpeWidget{outChan: make(chan timestampString), live: live}
refreshButton := NewButtonWidget(np, func() { MakeUpdated(w.actionNode) })
liveToggle := NewTriButtonExternalStateWidget(np, func() bool { return w.live }, func() { w.live = !w.live })
textBoxWidget := NewTextBoxWidget(pos)
w.FlowLayoutWidget = NewFlowLayoutWidget(pos, []Widgeter{refreshButton, liveToggle, textBoxWidget}, nil)
// THINK: The only reason to have a separate action node is because current NotifyChange() does not tell the originator of change, so I can't tell UniversalClock's changes from dependee changes (and I need to do different actions for each)
w.actionNode = &actionNode{owner: w, params: params, action: action}
w.actionNode.AddSources(dependees...)
return w
}
func (w *LiveGoroutineExpeWidget) PollLogic() {
if w.live {
MakeUpdated(w.actionNode) // THINK: Is this a hack or is this the way to go?
}
w.layout2Test()
w.FlowLayoutWidget.PollLogic()
}
func (w *LiveGoroutineExpeWidget) Close() error {
// TODO: Kill live goroutine?
// TODO: Errors.
_ = w.FlowLayoutWidget.Close()
return nil
}
func (w *LiveGoroutineExpeWidget) layout2Test() {
select {
case s, ok := <-w.outChan:
if ok {
if s.t > w.lastFinishedT {
w.lastFinishedT = s.t
SetViewGroup(w.Widgets[2].(*TextBoxWidget).Content, s.s) // HACK: Should get Content in a better way
redraw = true
}
}
default:
}
}
func (w *LiveGoroutineExpeWidget) Render() {
w.FlowLayoutWidget.Render()
}
// ---
type ConnectionWidget struct {
Widget
}
func NewConnectionWidget(pos mgl64.Vec2) *ConnectionWidget {
w := &ConnectionWidget{Widget: NewWidget(pos, mgl64.Vec2{fontHeight, fontHeight})}
return w
}
func (w *ConnectionWidget) Render() {
// HACK: Brute-force check the mouse pointer if it contains this widget
isOriginHit := false
for _, hit := range mousePointer.OriginMapping {
if w == hit {
isOriginHit = true
break
}
}
isHit := len(w.HoverPointers()) > 0
// HACK: Assumes mousePointer rather than considering all connected pointing pointers
if isOriginHit && mousePointer.State.IsActive() && isHit {
DrawCircle(w.pos, w.size, highlightColor, nearlyWhiteColor)
} else if (isHit && !mousePointer.State.IsActive()) || isOriginHit {
DrawCircle(w.pos, w.size, highlightColor, nearlyWhiteColor)
} else {
DrawCircle(w.pos, w.size, mgl64.Vec3{0.3, 0.3, 0.3}, nearlyWhiteColor)
}
DrawCircle(w.pos, w.size.Mul(0.5625), mgl64.Vec3{0.3, 0.3, 0.3}, mgl64.Vec3{0.3, 0.3, 0.3})
if isOriginHit && mousePointer.State.IsActive() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
globalPosition := mgl64.Vec2{mousePointer.State.Axes[0], mousePointer.State.Axes[1]}
localPosition := WidgeterS{w}.GlobalToLocal(globalPosition)
gl.Color3d(0, 0, 0)
gl.Begin(gl.LINES)
gl.Vertex2d(float64(0), float64(0))
gl.Vertex2d(float64(localPosition[0]), float64(localPosition[1]))
gl.End()
}
}
func (w *ConnectionWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
if w.pos.Sub(ParentPosition).Len() <= w.size[0]/2 {
return []Widgeter{w}
} else {
return nil
}
}
// ---
type HttpServerTestWidget struct {
*FlowLayoutWidget
started bool
stopServerChan chan struct{}
}
func NewHttpServerTestWidget(pos mgl64.Vec2) *HttpServerTestWidget {
const httpServerAddr = ":8060"
w := &HttpServerTestWidget{stopServerChan: make(chan struct{})}
action := func() {
if !w.started {
go func() {
err := ListenAndServeStoppable(httpServerAddr, nil, w.stopServerChan)
if err != nil {
log.Println("HttpServerTestWidget:", err)
w.started = false
}
}()
} else {
w.stopServerChan <- struct{}{}
}
w.started = !w.started // TODO: Factor this out to toggle-button?
}
action()
w.FlowLayoutWidget = NewFlowLayoutWidget(pos,
[]Widgeter{
NewTriButtonExternalStateWidget(np, func() bool { return w.started }, action),
}, nil)
if publicIps, err := gist8065433.GetPublicIps(); err == nil && len(publicIps) >= 1 {
w.FlowLayoutWidget.AddWidget(NewTextLabelWidgetString(np, "http://"+publicIps[0]+httpServerAddr))
}
return w
}
// ---
type VcsLocalInvalidator struct {
DepNode2
init bool
Repo *exp13.VcsState
}
func (this *VcsLocalInvalidator) Update() {
// Don't do anything for initialization, only for further changes.
if !this.init {
this.init = true
return
}
//fileView := this.GetSources()[0].(*FileView)
//println("changed:", fileView.path, "of:", this.Repo.Vcs.RootPath()[32:])
// Invalidate the Repo.VcsLocal.
ExternallyUpdated(this.Repo.VcsLocal.GetSources()[1].(DepNode2ManualI))
}
func (this *VcsLocalInvalidator) SetTarget() {
}
// ---
type FileOpener struct {
editor ViewGroupI
openedFile *FileView
DepNode2
}
func NewFileOpener(editor ViewGroupI) *FileOpener {
this := &FileOpener{editor: editor}
return this
}
func (this *FileOpener) Update() {
if this.openedFile != nil {
this.editor.RemoveView(this.openedFile)
this.openedFile.Close()
this.openedFile = nil
SetViewGroup(this.editor, "")
}
if path := this.GetSources()[0].(*VfsListingWidget).GetSelectedPath(); strings.HasSuffix(path, ".go") {
this.openedFile = NewFileView(path)
this.openedFile.AddAndSetViewGroup(this.editor, tryReadFile(path))
if goPackage := this.GetSources()[1].(GoPackageSelecter).GetSelectedGoPackage(); goPackage != nil {
MakeUpdatedLock.Unlock() // HACK: Needed because UpdateVcs() calls MakeUpdated().
goPackage.UpdateVcs()
MakeUpdatedLock.Lock() // HACK
if goPackage.Dir.Repo != nil {
vcsLocalInvalidator := &VcsLocalInvalidator{Repo: goPackage.Dir.Repo}
vcsLocalInvalidator.AddSources(&this.openedFile.FileContentChanged)
keepUpdatedTEST = append(keepUpdatedTEST, vcsLocalInvalidator) // TODO: Clear old ones...
}
}
}
}
// ---
type DepDumper struct {
DepNode2
}
func (this *DepDumper) Update() {
goon.Dump(this.GetSources()[0].(*VfsListingWidget).GetSelectedPath())
}
// ---
type SpinnerWidget struct {
Widget
Spinner uint32
DepNode2
}
func (w *SpinnerWidget) Render() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Color3d(0, 0, 0)
gl.Translated(float64(w.pos[0]), float64(w.pos[1]), 0)
//gl.Rotated(float64(spinner), 0, 0, 1)
gl.Rotated(float64(w.Spinner), 0, 0, 1)
gl.Begin(gl.LINES)
gl.Vertex2i(0, -10)
gl.Vertex2i(0, 10)
gl.End()
}
func (w *SpinnerWidget) Update() {
//w.Spinner++
w.Spinner += 45
}
// ---
type fpsSample struct{ Render, Total float64 }
type FpsWidget struct {
Widget
samples []fpsSample
}
func NewFpsWidget(pos mgl64.Vec2) *FpsWidget {
return &FpsWidget{Widget: NewWidget(pos, mgl64.Vec2{30, 40})}
}
func (w *FpsWidget) Render() {
gl.PushMatrix()
defer gl.PopMatrix()
gl.Translated(float64(w.pos[0]), float64(w.pos[1]+w.size[1]), 0)
gl.Begin(gl.LINES)
gl.Color3d(1, 0, 0)
gl.Vertex2d(float64(0), float64(-1000.0/60))
gl.Vertex2d(float64(30), float64(-1000.0/60))
gl.End()
for index, sample := range w.samples {
var color mgl64.Vec3
if sample.Render <= 1000.0/60*1.25 {
color = mgl64.Vec3{0, 0, 0}
} else {
color = mgl64.Vec3{1, 0, 0}
}
DrawBorderlessBox(mgl64.Vec2{float64(30 - len(w.samples) + index), -sample.Render}, mgl64.Vec2{1, sample.Render}, color)
DrawBorderlessBox(mgl64.Vec2{float64(30 - len(w.samples) + index), -sample.Total}, mgl64.Vec2{1, sample.Total - sample.Render}, mgl64.Vec3{0.65, 0.65, 0.65})
}
}
func (w *FpsWidget) PushTimeToRender(sample float64) {
w.samples = append(w.samples, fpsSample{Render: sample})
if len(w.samples) > 30 {
w.samples = w.samples[len(w.samples)-30:]
}
}
func (w *FpsWidget) PushTimeTotal(sample float64) {
w.samples[len(w.samples)-1].Total = sample
}
// ---
func WidgeterIndex(widgeters []Widgeter, w Widgeter) int {
for index := range widgeters {
if w == widgeters[index] {
return index
}
}
panic("WidgeterIndex: not found")
}
// ---
type SearchableListWidget struct {
*CompositeWidget
// Internal access shortcuts (also inside CompositeWidget).
searchField *TextBoxWidget
listWidget *FilterableSelecterWidget
listWidgetScrollPane *ScrollPaneWidget
ExtensionsTest []Widgeter
}
func NewSearchableListWidget(pos, size mgl64.Vec2, entries SliceStringer) *SearchableListWidget {
searchField := NewTextBoxWidgetOptions(np, TextBoxWidgetOptions{SingleLine: true})
//listWidget := NewListWidget(mathgl.Vec2d{0, fontHeight + 2}, entries)
listWidget := NewFilterableSelecterWidget(np, entries, searchField.Content, nil)
listWidgetScrollPane := NewScrollPaneWidget(mgl64.Vec2{0, fontHeight + 2}, size, listWidget)
w := &SearchableListWidget{CompositeWidget: NewCompositeWidget(pos, []Widgeter{searchField, listWidgetScrollPane}), searchField: searchField, listWidget: listWidget, listWidgetScrollPane: listWidgetScrollPane}
return w
}
type entriesPlusOne struct {
SliceStringer
extra *TextBoxWidget
}
func (this *entriesPlusOne) Get(index uint64) fmt.Stringer {
if index < this.SliceStringer.Len() {
return this.SliceStringer.Get(index)
} else {
return json.Number("Create new \"" + this.extra.Content.Content() + "\"...") // HACK: Should use a better suited fmt.Stringer type.
}
}
func (this *entriesPlusOne) Len() uint64 {
return this.SliceStringer.Len() + 1
}
func NewSearchableListWidgetTest(pos, size mgl64.Vec2, entries SliceStringer) *SearchableListWidget {
searchField := NewTextBoxWidgetOptions(np, TextBoxWidgetOptions{SingleLine: true})
listWidget := NewFilterableSelecterWidget(np, &entriesPlusOne{entries, searchField}, searchField.Content, nil)
listWidgetScrollPane := NewScrollPaneWidget(mgl64.Vec2{0, fontHeight + 2}, size, listWidget)
w := &SearchableListWidget{CompositeWidget: NewCompositeWidget(pos, []Widgeter{searchField, listWidgetScrollPane}), searchField: searchField, listWidget: listWidget, listWidgetScrollPane: listWidgetScrollPane}
return w
}
func (w *SearchableListWidget) OnSelectionChanged() Selecter {
return w.listWidget
}
// HACK/TEST: Mostly for external callers...
func (w *SearchableListWidget) SetKeyboardFocus() {
// TODO: Request pointer mapping in a kinder way (rather than forcing it - what if it's active and shouldn't be changed)
// HACK: Temporarily set both widgets as mapping here
keyboardPointer.OriginMapping = append([]Widgeter{w}, w.searchField, w.listWidget, w.listWidgetScrollPane)
}
func (w *SearchableListWidget) ProcessEvent(inputEvent InputEvent) {
for _, extension := range w.ExtensionsTest {
extension.ProcessEvent(inputEvent)
}
if inputEvent.Pointer.VirtualCategory == events.POINTING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.InputId == 0 && inputEvent.Buttons[0] == false &&
inputEvent.Pointer.Mapping.ContainsWidget(w) && // TODO: GetHoverer() // IsHit(this button) should be true
inputEvent.Pointer.OriginMapping.ContainsWidget(w) { // TODO: GetHoverer() // Make sure we're releasing pointer over same button that it originally went active on, and nothing is in the way (i.e. button is hoverer)
w.SetKeyboardFocus()
}
}
func (w *SearchableListWidget) Hit(ParentPosition mgl64.Vec2) []Widgeter {
if len(w.Widget.Hit(ParentPosition)) > 0 {
hits := []Widgeter{w}
hits = append(hits, w.CompositeWidget.Hit(ParentPosition)...)
return hits
} else {
return nil
}
}
// ---
func NewSearchableListWidgetAction(pos, size mgl64.Vec2, entries SliceStringer) *SearchableListWidget {
w := NewSearchableListWidget(pos, size, entries)
actions := &CustomWidget{
Widget: NewWidget(np, np),
ProcessEventFunc: func(inputEvent InputEvent) {
if inputEvent.Pointer.VirtualCategory == events.TYPING && inputEvent.EventTypes[events.BUTTON_EVENT] && inputEvent.Buttons[0] == true {
switch glfw.Key(inputEvent.InputId) {
case glfw.KeyEnter:
fmt.Println(w.listWidget.GetSelected())
case glfw.KeyEscape:
fmt.Println("Escape.")
}
}
},
}
w.ExtensionsTest = append(w.ExtensionsTest, actions)
return w
}
// ---
type FilterableSliceStringer struct {
filteredEntries []fmt.Stringer
DepNode2
}
func NewFilterableSliceStringer(source SliceStringer, filter caret.MultilineContentI) *FilterableSliceStringer {
this := &FilterableSliceStringer{}
this.AddSources(source, filter)
return this
}
func (this *FilterableSliceStringer) Get(index uint64) fmt.Stringer {
return this.filteredEntries[index]
}
func (this *FilterableSliceStringer) Len() uint64 {
return uint64(len(this.filteredEntries))
}
func (this *FilterableSliceStringer) Update() {
source := this.GetSources()[0].(SliceStringer)
filter := this.GetSources()[1].(caret.MultilineContentI)
filterLower := strings.ToLower(filter.Content())
this.filteredEntries = nil
for index := uint64(0); index < source.Len(); index++ {
entry := source.Get(index).String()
if filterLower != "" && !strings.Contains(strings.ToLower(entry), filterLower) { // TODO: Do case folding correctly
continue
}
this.filteredEntries = append(this.filteredEntries, source.Get(index))
}
}
// TODO: Need to output best fuzzy match rating, and sort by that.
// FuzzyMatch returns true if pattern fuzzy matches s. E.g., "fzy" pattern matches "fuzzy".
func FuzzyMatch(s, pattern string) bool {
srs := []rune(s)
for _, c := range pattern {
var matchedC bool
for i, sr := range srs {
if c == sr {
matchedC = true
srs = srs[i+1:]
break
}
}
if !matchedC {
return false
}
}
return true
}
// TODO: Is this the right/best place?
func (this *FilterableSliceStringer) Print(filteredIndex uint64, pos mgl64.Vec2, selected bool) {
entry := this.Get(filteredIndex).String()
filter := this.GetSources()[1].(caret.MultilineContentI)
index := strings.Index(strings.ToLower(entry), strings.ToLower(filter.Content()))
glt := NewOpenGlStream(pos)
if !selected {
gl.Color3dv((*float64)(&veryDarkColor[0]))
glt.PrintText(entry[:index])
gl.Color3d(0, 0, 0)
glt.FontOptions = Bold
glt.PrintText(entry[index : index+len(filter.Content())])
gl.Color3dv((*float64)(&veryDarkColor[0]))
glt.FontOptions = Regular
glt.PrintText(entry[index+len(filter.Content()):])
} else {
gl.Color3dv((*float64)(&veryLightColor[0]))
glt.PrintText(entry[:index])
gl.Color3d(1, 1, 1)
glt.FontOptions = Bold
glt.PrintText(entry[index : index+len(filter.Content())])
gl.Color3