From f7ead697d376f73c386b771771b80e09ec58870d Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Sun, 31 Oct 2021 14:21:36 -0500 Subject: [PATCH] REFACTOR: refactor everything to gui.Node struct Signed-off-by: Jeff Carr --- .gitignore | 3 +- area.go | 10 +- box.go | 110 +++++++- button.go | 56 ++++ cmds/gui-demo/Makefile | 5 + cmds/gui-demo/demo-window.go | 33 +++ cmds/gui-demo/main.go | 68 +++++ {gui-example => cmds/gui-example}/Makefile | 0 cmds/gui-example/demo-window.go | 105 +++++++ cmds/gui-example/main.go | 68 +++++ cmds/gui-example/os.go | 99 +++++++ window-debug.go => debug-window.go | 193 ++++++++++--- debug.go | 66 ++++- ...w-template.go => demo-window-andlabs-ui.go | 14 +- demo-window.go | 28 ++ doc.go | 72 +++++ entry.go | 40 ++- example_test.go | 45 +++ find.go | 156 +++++++++++ gui-example/main.go | 43 --- gui.go | 8 + main.go | 28 +- new-structs.go | 249 ++++++++++++++++- structs.go | 173 +++++------- table.go | 12 +- tableCallbacks.go | 3 +- window.go | 260 ++++++++---------- 27 files changed, 1543 insertions(+), 404 deletions(-) create mode 100644 cmds/gui-demo/Makefile create mode 100644 cmds/gui-demo/demo-window.go create mode 100644 cmds/gui-demo/main.go rename {gui-example => cmds/gui-example}/Makefile (100%) create mode 100644 cmds/gui-example/demo-window.go create mode 100644 cmds/gui-example/main.go create mode 100644 cmds/gui-example/os.go rename window-debug.go => debug-window.go (53%) rename window-template.go => demo-window-andlabs-ui.go (80%) create mode 100644 demo-window.go create mode 100644 doc.go create mode 100644 example_test.go create mode 100644 find.go delete mode 100644 gui-example/main.go diff --git a/.gitignore b/.gitignore index bb1e72d..1445c95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.swp -gui-example/gui-example +cmds/gui-example/gui-example +cmds/gui-demo/gui-demo diff --git a/area.go b/area.go index 0cc7531..7c42c6c 100644 --- a/area.go +++ b/area.go @@ -107,10 +107,10 @@ func (ah GuiArea) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) { return false } -func ShowTextBox(box *GuiBox, newText *ui.AttributedString, custom func(*GuiButton), name string) { +func (b *GuiBox) ShowTextBox(newText *ui.AttributedString, custom func(*GuiButton), name string) { log.Println("ShowTextBox() START") - gw := box.Window + gw := b.Window if (gw == nil) { log.Println("ShowTextBox() ERROR gw = nil") return @@ -127,10 +127,10 @@ func ShowTextBox(box *GuiBox, newText *ui.AttributedString, custom func(*GuiButt */ // TODO: allow padded & axis here - box.UiBox.SetPadded(true) + b.UiBox.SetPadded(true) // add(gw.BoxMap["MAINBOX"], newbox) - makeGenericArea(box, newText, custom) - box.UiBox.Append(box.Window.Area.UiArea, true) + makeGenericArea(b, newText, custom) + b.UiBox.Append(b.Window.Area.UiArea, true) } diff --git a/box.go b/box.go index 2640742..7a61f55 100644 --- a/box.go +++ b/box.go @@ -1,6 +1,7 @@ package gui import "log" +import "os" // import "reflect" import "github.com/andlabs/ui" @@ -34,6 +35,7 @@ func add(box *GuiBox, newbox *GuiBox) { newbox.Window.BoxMap["MAINBOX"] = newbox log.Println("gui.add() END") + panic("gui.add() MAINBOX gui.add() END") return } else { log.Println("\tgui.add() ERROR DONT KNOW HOW TO ADD TO A RAW WINDOW YET") @@ -41,33 +43,77 @@ func add(box *GuiBox, newbox *GuiBox) { } log.Println("\tgui.add() ERROR DON'T KNOW HOW TO add to Window as MAINBOX DONE") log.Println("gui.add() END") + panic("gui.add() gui.add() END") return } log.Println("\tgui.add() adding", newbox.Name, "to", box.Name) // copy the box settings over newbox.Window = box.Window - if (box.UiBox == nil) { - log.Println("\tgui.add() ERROR box.UiBox == nil") - panic("crap") + if (box.node == nil) { + box.Dump() + panic("gui.add() ERROR box.node == nil") } + if (newbox.UiBox == nil) { - log.Println("\tgui.add() ERROR newbox.UiBox == nil") - panic("crap") + panic("gui.add() ERROR newbox.UiBox == nil") + } + + if (box.UiBox == nil) { + box.Dump() + // panic("gui.add() ERROR box.UiBox == nil") + return + // TODO: fix this whole add() function // Oct 9 } - // log.Println("\tgui.add() newbox.UiBox == ", newbox.UiBox.GetParent()) - // spew.Dump(newbox.UiBox) box.UiBox.Append(newbox.UiBox, false) + box.Dump() + panic("gui.add()") // add the newbox to the Window.BoxMap[] box.Window.BoxMap[newbox.Name] = newbox log.Println("gui.add() END") } -func NewBox(box *GuiBox, axis int, name string) *GuiBox { - log.Println("VerticalBox START") +func (n *Node) AddBox(axis int, name string) *Node { + newBox := new(GuiBox) + newBox.Window = n.window + newBox.Name = name + + if (n.box == nil) { + n.box = newBox + } + + // make a new box & a new node + newNode := n.makeNode(name, 111, 100 + Config.counter) + newNode.box = newBox + Config.counter += 1 + + var uiBox *ui.Box + if (axis == Xaxis) { + uiBox = ui.NewHorizontalBox() + } else { + uiBox = ui.NewVerticalBox() + } + uiBox.SetPadded(true) + newBox.UiBox = uiBox + newNode.uiBox = uiBox + + n.Append(newNode) + // add(n.box, newBox) + return newNode +} + +func (b *GuiBox) NewBox(axis int, name string) *GuiBox { + log.Println("gui.NewBox() START") + n := b.FindNode() + if (n == nil) { + log.Println("gui.NewBox() SERIOUS ERROR. CAN NOT FIND NODE") + os.Exit(0) + } else { + log.Println("gui.NewBox() node =", n.Name) + } var newbox *GuiBox newbox = new(GuiBox) - newbox.Window = box.Window + newbox.Window = b.Window newbox.Name = name var uiBox *ui.Box @@ -78,13 +124,18 @@ func NewBox(box *GuiBox, axis int, name string) *GuiBox { } uiBox.SetPadded(true) newbox.UiBox = uiBox - add(box, newbox) + add(b, newbox) + // panic("gui.NewBox") return newbox } func HardBox(gw *GuiWindow, axis int, name string) *GuiBox { log.Println("HardBox() START axis =", axis) + if (gw.node == nil) { + gw.Dump() + panic("gui.HardBox() gw.node == nil") + } // add a Vertical Seperator if there is already a box // Is this right? box := gw.BoxMap["MAINBOX"] @@ -134,3 +185,40 @@ func VerticalBreak(box *GuiBox) { tmp := ui.NewVerticalSeparator() box.UiBox.Append(tmp, false) } + +func (n *Node) AddComboBox(title string, s ...string) *Node { + box := n.uiBox + if (box == nil) { + return n + } + + ecbox := ui.NewEditableCombobox() + + for id, name := range s { + log.Println("Adding Combobox Entry:", id, name) + ecbox.Append(name) + } + + ecbox.OnChanged(func(*ui.EditableCombobox) { + test := ecbox.Text() + log.Println("node.Name = '" + n.Name + "' text for '" + title + "' is now: '" + test + "'") + }) + + box.Append(ecbox, false) + + newNode := n.AddNode(title) + newNode.uiText = ecbox + return newNode +} + +func (n *Node) OnChanged(f func()) { + f() +} + +func (n *Node) GetText() string { + if (n.uiText == nil) { + return "" + } + ecbox := n.uiText + return ecbox.Text() +} diff --git a/button.go b/button.go index 825b79b..8ad0a3b 100644 --- a/button.go +++ b/button.go @@ -1,10 +1,12 @@ package gui import "log" +import "reflect" import "github.com/andlabs/ui" import _ "github.com/andlabs/ui/winmanifest" // import "github.com/davecgh/go-spew/spew" + // This is the default mouse click handler // Every mouse click that hasn't been assigned to // something specific will fall into this routine @@ -50,6 +52,60 @@ func guiButtonClick(button *GuiButton) { } } +func (n *Node) AddButton(name string, custom func(*Node)) *Node { + if (n.uiBox == nil) { + log.Println("gui.Node.AppendButton() filed node.UiBox == nil") + return n + } + button := ui.NewButton(name) + log.Println("reflect.TypeOF(uiBox) =", reflect.TypeOf(n.uiBox)) + log.Println("reflect.TypeOF(uiButton) =", reflect.TypeOf(button)) + n.uiBox.Append(button, false) + n.uiButton = button + + newNode := n.makeNode(name, 888, 888 + Config.counter) + newNode.uiButton = button + newNode.custom = custom + + button.OnClicked(func(*ui.Button) { + log.Println("gui.AppendButton() Button Clicked. Running custom()") + custom(newNode) + }) + // panic("AppendButton") + // time.Sleep(3 * time.Second) + return newNode +} + +func (n *Node) CreateButton(custom func(*GuiButton), name string, values interface {}) *Node { + newNode := n.AddBox(Xaxis, "test CreateButton") + box := newNode.FindBox() + if (box == nil) { + panic("node.CreateButton().FindBox() == nil") + } + newUiB := ui.NewButton(name) + newUiB.OnClicked(defaultButtonClick) + + var newB *GuiButton + newB = new(GuiButton) + newB.B = newUiB + if (box.UiBox == nil) { + log.Println("CreateButton() box.Window == nil") + // ErrorWindow(box.Window, "Login Failed", msg) // can't even do this + panic("maybe print an error and return nil? or make a fake button?") + } else { + // uibox := box.UiBox + // uibox.Append(newUiB, true) + } + newB.Box = box + newB.Custom = custom + newB.Values = values + + Data.AllButtons = append(Data.AllButtons, newB) + + box.Append(newB.B, false) + return newNode +} + func CreateButton(box *GuiBox, custom func(*GuiButton), name string, values interface {}) *GuiButton { newUiB := ui.NewButton(name) newUiB.OnClicked(defaultButtonClick) diff --git a/cmds/gui-demo/Makefile b/cmds/gui-demo/Makefile new file mode 100644 index 0000000..2dbc808 --- /dev/null +++ b/cmds/gui-demo/Makefile @@ -0,0 +1,5 @@ +run: build + ./gui-demo + +build: + GO111MODULE="off" go build diff --git a/cmds/gui-demo/demo-window.go b/cmds/gui-demo/demo-window.go new file mode 100644 index 0000000..fd8e8f4 --- /dev/null +++ b/cmds/gui-demo/demo-window.go @@ -0,0 +1,33 @@ +package main + +import "log" + +import "git.wit.org/wit/gui" + +func demoClick (n *gui.Node) { + log.Println("demoClick() Dumping node:") + n.Dump() +} + +func addDemoTab(n *gui.Node, title string) { + newNode := n.AddTab(title, nil) + if (gui.Config.Debug) { + newNode.Dump() + } + newNode.ListChildren(false) + + groupNode1 := newNode.AddGroup("group 1") + groupNode1.AddComboBox("demoCombo1", "foo", "bar", "stuff") + groupNode1.AddComboBox("demoCombo3", "foo 3", "bar", "stuff") + + groupNode1.Dump() + + butNode1 := groupNode1.AddButton("button1", demoClick) + butNode1.Dump() + + butNode2 := groupNode1.AddButton("button2", demoClick) + butNode2.Dump() + + groupNode2 := newNode.AddGroup("group 2") + groupNode2.AddComboBox("demoCombo2", "more 1", "more 2", "more 3") +} diff --git a/cmds/gui-demo/main.go b/cmds/gui-demo/main.go new file mode 100644 index 0000000..8eae878 --- /dev/null +++ b/cmds/gui-demo/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "log" + "os" + "time" + + "git.wit.org/wit/gui" +) + +// This initializes the first window +// +// Then starts a goroutine to demonstrate how to +// inject things into the GUI +func main() { + log.Println("Starting my Control Panel") + + go gui.Main(initGUI) + + watchGUI() +} + +// This initializes the first window +func initGUI() { + gui.Config.Title = "WIT GUI Window Demo 1" + gui.Config.Width = 640 + gui.Config.Height = 480 + gui.Config.Exit = myExit + node1 := gui.NewWindow() + addDemoTab(node1, "A Simple Tab Demo") + + gui.Config.Title = "WIT GUI Window Demo 2" + gui.Config.Width = 640 + gui.Config.Height = 240 + gui.Config.Exit = myExit + node2 := gui.NewWindow() + node2.DemoAndlabsUiTab("A Simple andlabs/ui Tab Demo") +} + +// This demonstrates how to properly interact with the GUI +// You can not involke the GUI from external goroutines in most cases. +func watchGUI() { + var i = 1 + for { + log.Println("Waiting", i, "seconds") + i += 1 + time.Sleep(1 * time.Second) + if i == 4 { + log.Println("Opening a Debug Window via the gui.Queue()") + gui.Config.Width = 800 + gui.Config.Height = 300 + gui.Config.Exit = myDebugExit + gui.Queue(gui.DebugWindow) + } + } +} + +func myExit(n *gui.Node) { + log.Println() + log.Println("Entered myExit() on node.Name =", n.Name) + log.Println() + os.Exit(0) +} + +func myDebugExit(n *gui.Node) { + log.Println("Entered myDebugExit() on node.Name =", n.Name) + log.Println("Don't actually os.Exit()") +} diff --git a/gui-example/Makefile b/cmds/gui-example/Makefile similarity index 100% rename from gui-example/Makefile rename to cmds/gui-example/Makefile diff --git a/cmds/gui-example/demo-window.go b/cmds/gui-example/demo-window.go new file mode 100644 index 0000000..993900d --- /dev/null +++ b/cmds/gui-example/demo-window.go @@ -0,0 +1,105 @@ +package main + +import "log" +import "reflect" + +import "git.wit.org/wit/gui" + +import "github.com/davecgh/go-spew/spew" + +func demoClick (n *gui.Node) { + log.Println("demoClick() Dumping node:") + n.Dump() +} + +var username = "jcarr" +var hostname = "fire" + +func newClick (n *gui.Node) { + var tmp []string + junk := "ssh -v " + username + "@" + hostname + log.Println("junk = " , junk) + xterm(junk) + log.Println("tmp = " , reflect.ValueOf(tmp).Kind()) + // spew.Dump(tmp) +} + +func addDemoTab(n *gui.Node, title string) { + newNode := n.AddTab(title, nil) + if (gui.Config.Debug) { + newNode.Dump() + } + newNode.ListChildren(false) + + groupNode1 := newNode.AddGroup("group 1") + cbNode := groupNode1.AddComboBox("username", "root", "jcarr", "hugo") + cbNode.OnChanged(func () { + username = cbNode.GetText() + }) + groupNode1.AddComboBox("demoCombo3", "foo 3", "bar", "stuff") + + groupNode1.Dump() + + butNode1 := groupNode1.AddButton("button1", demoClick) + butNode1.Dump() + + butNode2 := groupNode1.AddButton("button2", newClick) + butNode2.Dump() + + groupNode2 := newNode.AddGroup("group 2") + groupNode2.AddComboBox("demoCombo2", "more 1", "more 2", "more 3") + + gNode := newNode.AddGroup("domU") + makeSSHbutton(gNode, "hugo@www", "www.wit.org") + makeSSHbutton(gNode, "check.lab", "check.lab.wit.org") + makeSSHbutton(gNode, "gobuild.lab", "gobuild.lab.wit.org") + makeSSHbutton(gNode, "gobuild2.lab", "gobuild2.lab.wit.org") + +/////////////////////////////// Column DNS //////////////////////////////// + gNode = newNode.AddGroup("dns") + makeSSHbutton(gNode, "bind.wit.org", "bind.wit.org") + makeSSHbutton(gNode, "ns1.wit.com", "ns1.wit.com") + makeSSHbutton(gNode, "ns2.wit.com", "ns2.wit.com") + makeSSHbutton(gNode, "coredns", "coredns.lab.wit.org") + +/////////////////////////////// PHYS 530 ////////////////////////////////// + gNode = newNode.AddGroup("phys 530") + // makeXtermButton(gNode, "openwrt", "SUBDOMAIN", "ssh -4 -v root@openwrt") + gNode.AddButton("openwrt", func (*gui.Node) { + stuff := "ssh -4 -v root@openwrt" + xterm(stuff) + }) + makeSSHbutton (gNode, "mirrors", "mirrors.wit.org") + makeSSHbutton (gNode, "node004", "node004.lab.wit.org") + makeSSHbutton (gNode, "lenovo-z70", "lenovo-z70.lab.wit.org") + +/////////////////////////////// PHYS 522 ////////////////////////////////// + gNode = newNode.AddGroup("phys 522") + // makeXtermButton(gNode, "openwrt2", "SUBDOMAIN", "ssh -4 -v root@openwrt2") + gNode.AddButton("openwrt2", func (*gui.Node) { + stuff := "ssh -4 -v root@openwrt2" + xterm(stuff) + }) + makeSSHbutton (gNode, "fire.lab", "fire.lab.wit.org") + makeSSHbutton (gNode, "predator", "predator.lab.wit.org") + +/////////////////////////////// FLOAT ///////////////////////////////////// + gNode = newNode.AddGroup("float") + makeSSHbutton(gNode, "root@asus-n501vw", "asus-n501vw.lab.wit.org") +} + +func makeSSHbutton (n *gui.Node, name string, hostname string) { + bNode := n.AddButton(name, func (*gui.Node) { + var tmp []string + if (username == "") { + username = "root" + } + junk := "ssh -v " + username + "@" + hostname + log.Println("junk = " , junk) + log.Println("username = '" + username + "'") + xterm(junk) + log.Println("tmp = " , reflect.ValueOf(tmp).Kind()) + spew.Dump(tmp) + }) + bNode.Dump() +} diff --git a/cmds/gui-example/main.go b/cmds/gui-example/main.go new file mode 100644 index 0000000..8eae878 --- /dev/null +++ b/cmds/gui-example/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "log" + "os" + "time" + + "git.wit.org/wit/gui" +) + +// This initializes the first window +// +// Then starts a goroutine to demonstrate how to +// inject things into the GUI +func main() { + log.Println("Starting my Control Panel") + + go gui.Main(initGUI) + + watchGUI() +} + +// This initializes the first window +func initGUI() { + gui.Config.Title = "WIT GUI Window Demo 1" + gui.Config.Width = 640 + gui.Config.Height = 480 + gui.Config.Exit = myExit + node1 := gui.NewWindow() + addDemoTab(node1, "A Simple Tab Demo") + + gui.Config.Title = "WIT GUI Window Demo 2" + gui.Config.Width = 640 + gui.Config.Height = 240 + gui.Config.Exit = myExit + node2 := gui.NewWindow() + node2.DemoAndlabsUiTab("A Simple andlabs/ui Tab Demo") +} + +// This demonstrates how to properly interact with the GUI +// You can not involke the GUI from external goroutines in most cases. +func watchGUI() { + var i = 1 + for { + log.Println("Waiting", i, "seconds") + i += 1 + time.Sleep(1 * time.Second) + if i == 4 { + log.Println("Opening a Debug Window via the gui.Queue()") + gui.Config.Width = 800 + gui.Config.Height = 300 + gui.Config.Exit = myDebugExit + gui.Queue(gui.DebugWindow) + } + } +} + +func myExit(n *gui.Node) { + log.Println() + log.Println("Entered myExit() on node.Name =", n.Name) + log.Println() + os.Exit(0) +} + +func myDebugExit(n *gui.Node) { + log.Println("Entered myDebugExit() on node.Name =", n.Name) + log.Println("Don't actually os.Exit()") +} diff --git a/cmds/gui-example/os.go b/cmds/gui-example/os.go new file mode 100644 index 0000000..ce5db8d --- /dev/null +++ b/cmds/gui-example/os.go @@ -0,0 +1,99 @@ +package main + +import "log" +import "strings" +import "os" +import "os/exec" +import "io/ioutil" +import "errors" +// import "bufio" + +// import "github.com/davecgh/go-spew/spew" + +/* +import "time" +import "runtime" +import "runtime/debug" +import "runtime/pprof" + +import "git.wit.org/wit/gui" +import "git.wit.org/wit/shell" +import "github.com/gobuffalo/packr" +*/ + +func runSimpleCommand(s string) { + cmd := strings.TrimSpace(s) // this is like 'chomp' in perl + cmd = strings.TrimSuffix(cmd, "\n") // this is like 'chomp' in perl + cmdArgs := strings.Fields(cmd) + runLinuxCommand(cmdArgs) +} + +var geom string = "120x30+500+500" + +func xterm(cmd string) { + var tmp []string + var argsXterm = []string{"nohup", "xterm", "-geometry", geom} + tmp = append(argsXterm, "-hold", "-e", cmd) + log.Println("xterm cmd=", cmd) + go runCommand(tmp) +} + +func runCommand(cmdArgs []string) { + log.Println("runCommand() START", cmdArgs) + process := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...) + // process := exec.Command("xterm", "-e", "ping localhost") + log.Println("runCommand() process.Start()") + process.Start() + log.Println("runCommand() process.Wait()") + err := process.Wait() + lookupError(err) + log.Println("runCommand() NEED TO CHECK THE TIME HERE TO SEE IF THIS WORKED") + log.Println("runCommand() OTHERWISE INFORM THE USER") + log.Println("runCommand() END", cmdArgs) +} + +func lookupError(err error) { + var ( + ee *exec.ExitError + pe *os.PathError + ) + + if errors.As(err, &ee) { + log.Println("ran, but non-zero exit code =", ee.ExitCode()) // ran, but non-zero exit code + } else if errors.As(err, &pe) { + log.Printf("os.PathError = %v", pe) // "no such file ...", "permission denied" etc. + } else if err != nil { + log.Printf("something really bad happened general err = %v", err) // something really bad happened! + if exitError, ok := err.(*exec.ExitError); ok { + log.Printf("exitError.ExitCode() is %d\n", exitError.ExitCode()) + } + } else { + log.Println("success! // ran without error (exit code zero)") + } +} + +func runLinuxCommand(cmdArgs []string) (string, error) { + process := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...) + + process.Stdin = os.Stdin + process.Stderr = os.Stderr + + stdOut, err := process.StdoutPipe() + if err != nil { + return "", err + } + + if err := process.Start(); err != nil { + return "", err + } + + bytes, err := ioutil.ReadAll(stdOut) + if err != nil { + return "", err + } + err = process.Wait() + lookupError(err) + + log.Println(string(bytes)) + return string(bytes), err +} diff --git a/window-debug.go b/debug-window.go similarity index 53% rename from window-debug.go rename to debug-window.go index 704ef7b..19f276d 100644 --- a/window-debug.go +++ b/debug-window.go @@ -2,6 +2,8 @@ package gui import ( "log" + // "fmt" + "strconv" "github.com/andlabs/ui" _ "github.com/andlabs/ui/winmanifest" @@ -9,22 +11,36 @@ import ( ) var names = make([]string, 100) +var nodeNames = make([]string, 100) -func makeWindowDebug() ui.Control { +func DebugWindow() { + Config.Title = "DebugWindow()" + node := NewWindow() + node.DebugTab("WIT GUI Debug Tab") +} + +// TODO: remove this crap +// What does this actually do? +// It populates the nodeNames in a map. No, not a map, an array. +// What is the difference again? (other than one being in order and a predefined length) +func addNodeName(c *ui.Combobox, s string, id string) { + c.Append(s) + nodeNames[y] = id + y = y + 1 +} + +func makeWindowDebug() *ui.Box { hbox := ui.NewHorizontalBox() hbox.SetPadded(true) ///////////////////////////////////////////////////// - vbox := addGroup(hbox, "Numbers") - pbar := ui.NewProgressBar() - vbox.Append(pbar, false) - - ///////////////////////////////////////////////////// - vbox = addGroup(hbox, "Window") + vbox := addGroup(hbox, "range Data.WindowMap") cbox := ui.NewCombobox() for name, _ := range Data.WindowMap { - log.Println("range Data.WindowMap() name =", name) + if (Config.Debug) { + log.Println("range Data.WindowMap() name =", name) + } addName(cbox, name) } cbox.SetSelected(0) @@ -122,6 +138,11 @@ func makeWindowDebug() ui.Control { ///////////////////////////////////////////////////// vbox = addGroup(hbox, "Global Debug") + dump3 := addButton(vbox, "Dump Windows") + dump3.OnClicked(func(*ui.Button) { + DumpWindows() + }) + dump2 := addButton(vbox, "Dump Boxes") dump2.OnClicked(func(*ui.Button) { DumpBoxes() @@ -132,11 +153,112 @@ func makeWindowDebug() ui.Control { DumpMap() }) + ///////////////////////////////////////////////////// + nodeBox := addGroup(hbox, "Windows:") + nodeCombo := ui.NewCombobox() + + for name, node := range Data.NodeMap { + if (Config.Debug) { + log.Println("range Data.NodeMap() name =", name) + } + tmp := node.id + " (" + name + ")" + addNodeName(nodeCombo, tmp, node.id) + } + nodeCombo.SetSelected(0) + + nodeBox.Append(nodeCombo, false) + + nodeCombo.OnSelected(func(*ui.Combobox) { + y := nodeCombo.Selected() + log.Println("y =", y) + log.Println("nodeNames[y] =", nodeNames[y]) + node := Data.findId(nodeNames[y]) + if (node != nil) { + node.Dump() + } + }) + + ///////////////////////////////////////////////////// + vbox = addGroup(hbox, "Node Debug") + + n1 := addButton(vbox, "Data.DumpNodeMap()") + n1.OnClicked(func(*ui.Button) { + Data.DumpNodeMap() + }) + + n1 = addButton(vbox, "Data.ListChildren(false)") + n1.OnClicked(func(*ui.Button) { + Data.ListChildren(false) + }) + + n1 = addButton(vbox, "Data.ListChildren(true)") + n1.OnClicked(func(*ui.Button) { + Data.ListChildren(true) + }) + + n1 = addButton(vbox, "Node.Dump()") + n1.OnClicked(func(*ui.Button) { + y := nodeCombo.Selected() + log.Println("y =", y) + log.Println("nodeNames[y] =", nodeNames[y]) + node := Data.findId(nodeNames[y]) + if (node != nil) { + node.Dump() + } + }) + + n1 = addButton(vbox, "Node.ListChildren(false)") + n1.OnClicked(func(*ui.Button) { + y := nodeCombo.Selected() + log.Println("y =", y) + log.Println("nodeNames[y] =", nodeNames[y]) + node := Data.findId(nodeNames[y]) + if (node != nil) { + node.ListChildren(false) + } + }) + + n1 = addButton(vbox, "Node.ListChildren(true)") + n1.OnClicked(func(*ui.Button) { + y := nodeCombo.Selected() + log.Println("y =", y) + log.Println("nodeNames[y] =", nodeNames[y]) + node := Data.findId(nodeNames[y]) + if (node != nil) { + node.ListChildren(true) + } + }) + + n1 = addButton(vbox, "Node.AddDebugTab") + n1.OnClicked(func(*ui.Button) { + y := nodeCombo.Selected() + log.Println("y =", y) + log.Println("nodeNames[y] =", nodeNames[y]) + node := Data.findId(nodeNames[y]) + if (node != nil) { + node.DebugTab("added this DebugTab") + } + }) + + n1 = addButton(vbox, "Node.DemoAndlabsUiTab") + n1.OnClicked(func(*ui.Button) { + y := nodeCombo.Selected() + log.Println("y =", y) + log.Println("nodeNames[y] =", nodeNames[y]) + node := Data.findId(nodeNames[y]) + if (node != nil) { + node.DemoAndlabsUiTab("ran gui.AddDemoAndlabsUiTab() " + strconv.Itoa(Config.counter)) + } + }) + return hbox } +// TODO: remove this crap var x int = 0 +var y int = 0 +// TODO: remove this crap func addName(c *ui.Combobox, s string) { c.Append(s) names[x] = s @@ -155,33 +277,11 @@ func addGroup(b *ui.Box, name string) *ui.Box { return vbox } -func FindWindow(s string) *GuiWindow { - for name, window := range Data.WindowMap { - if name == s { - return window - } - } - log.Printf("COULD NOT FIND WINDOW", s) - return nil -} - -func FindBox(s string) *GuiBox { - for name, window := range Data.WindowMap { - if name != s { - continue - } - for name, abox := range window.BoxMap { - log.Printf("gui.DumpBoxes() \tBOX mapname=%-12s abox.Name=%-12s", name, abox.Name) - return abox - } - log.Println("gui.FindBox() NEED TO INIT WINDOW name =", name) - } - log.Println("gui.FindBox() COULD NOT FIND BOX", s) - return nil -} - func dumpBox(s string) { - for name, window := range Data.WindowMap { + var name string + var window *GuiWindow + + for name, window = range Data.WindowMap { if name != s { continue } @@ -198,6 +298,7 @@ func dumpBox(s string) { log.Println("gui.dumpBox() BoxMap START") for name, abox := range window.BoxMap { log.Printf("gui.DumpBoxes() \tBOX mapname=%-12s abox.Name=%-12s", name, abox.Name) + abox.Dump() if name == "MAINBOX" { if Config.Debug { scs := spew.ConfigState{MaxDepth: 1} @@ -228,3 +329,27 @@ func addButton(box *ui.Box, name string) *ui.Button { box.Append(button, false) return button } + +func (n *Node) DebugTab(title string) { + newNode := n.AddTab(title, makeWindowDebug()) + if (Config.DebugNode) { + newNode.Dump() + } + tabSetMargined(newNode.uiTab) +} + +// This sets _all_ the tabs to Margin = true +// +// TODO: do proper tab tracking (will be complicated). low priority +func tabSetMargined(tab *ui.Tab) { + if (Config.DebugTabs) { + log.Println("tabSetMargined() IGNORE THIS") + } + c := tab.NumPages() + for i := 0; i < c; i++ { + if (Config.DebugTabs) { + log.Println("tabSetMargined() i =", i) + } + tab.SetMargined(i, true) + } +} diff --git a/debug.go b/debug.go index 8c1e947..0b0baa4 100644 --- a/debug.go +++ b/debug.go @@ -8,14 +8,13 @@ import ( "github.com/davecgh/go-spew/spew" ) -// import "reflect" - -// import "github.com/andlabs/ui" -// import _ "github.com/andlabs/ui/winmanifest" - -// -// Dump out debugging information every 4 seconds +// WatchGUI() opens a goroutine // +// From that goroutine, it dumps out debugging information every 4 seconds +/* + TODO: add configuration triggers on what to dump out + TODO: allow this to be sent to /var/log, syslogd, systemd's journalctl, etc +*/ func WatchGUI() { count := 0 @@ -32,6 +31,12 @@ func WatchGUI() { } } +func DumpWindows() { + for name, _ := range Data.WindowMap { + log.Println("gui.DumpWindows() window =", name) + } +} + func DumpMap() { for name, window := range Data.WindowMap { log.Println("gui.DumpBoxes() MAP: ", name) @@ -106,3 +111,50 @@ func addTableTab() { log.Println("Sleep for 1 second, then try to add new tabs") time.Sleep(1 * time.Second) } + +func (dn *GuiData) DumpNodeMap() { + log.Println("DebugDataNodeMap():") + for name, node := range dn.NodeMap { + log.Println("\tNode =", node.id, node.Width, node.Height, name) + if (node.children == nil) { + log.Println("\t\tNo children") + } else { + log.Println("\t\tHas children:", node.children) + } + // node.SetName("yahoo") + // log.Println("\tData.NodeMap node =", node) + } +} + +/* +func DebugDataNodeChildren() { + if Data.NodeMap == nil { + log.Println("DebugDataNodeChildren() NodeMap == nil") + return + } + log.Println("DebugDataNodeChildren():") + for name, node := range Data.NodeMap { + log.Println("\tNode name =", node.Width, node.Height, name) + if (node.children == nil) { + log.Println("\t\tNo children") + break + } + log.Println("\t\tHas children:", node.children) + } +} +*/ + +func (dn *GuiData) ListChildren(dump bool) { + if Data.NodeMap == nil { + log.Println("gui.Data.ListChildren() Data.NodeMap == nil") + return + } + log.Println("gui.Data.ListChildren() Data.NodeMap:") + for name, node := range Data.NodeMap { + log.Println("\tgui.Data.ListChildren() node =", node.id, node.Width, node.Height, name) + if (dump == true) { + node.Dump() + } + node.ListChildren(dump) + } +} diff --git a/window-template.go b/demo-window-andlabs-ui.go similarity index 80% rename from window-template.go rename to demo-window-andlabs-ui.go index c3e30ef..e11ffe9 100644 --- a/window-template.go +++ b/demo-window-andlabs-ui.go @@ -4,7 +4,19 @@ import "log" import "github.com/andlabs/ui" import _ "github.com/andlabs/ui/winmanifest" -func makeWindowTemplate() ui.Control { +// This will create a tab in a window using direct +// calls to andlabs/ui. This can be used to bypass +// the obvuscation added in this package if it is desired +// or needed. +func (n *Node) DemoAndlabsUiTab(title string) { + newNode := n.AddTab(title, makeAndlabsUiTab()) + if (Config.DebugNode) { + newNode.Dump() + } + tabSetMargined(newNode.uiTab) +} + +func makeAndlabsUiTab() *ui.Box { hbox := ui.NewHorizontalBox() hbox.SetPadded(true) diff --git a/demo-window.go b/demo-window.go new file mode 100644 index 0000000..16566c4 --- /dev/null +++ b/demo-window.go @@ -0,0 +1,28 @@ +package gui + +// import "log" +import "github.com/andlabs/ui" +import _ "github.com/andlabs/ui/winmanifest" + +var mybox *ui.Box + +func (n *Node) AddGroup(title string) *Node { + if (n == nil) { + return nil + } + hbox := n.uiBox + if (hbox == nil) { + return n + } + group := ui.NewGroup(title) + group.SetMargined(true) + hbox.Append(group, true) + + vbox := ui.NewVerticalBox() + vbox.SetPadded(true) + group.SetChild(vbox) + + newNode := n.AddNode(title) + newNode.uiBox = vbox + return newNode +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..2b324c9 --- /dev/null +++ b/doc.go @@ -0,0 +1,72 @@ +/* +Package wit/gui implements a abstraction layer for Go visual elements in +a cross platform way. Right now, this abstraction is built on top of +the GUI toolkit 'andlabs/ui' which does the cross platform support. + +A quick overview of the features, some general design guidelines +and principles for how this package should generally work: + + * GUI elements are stored in a tree of nodes + * When in doubt, it's ok to guess. We will return something close. + * It tries to make your code simple + +Quick Start + +This section demonstrates how to quickly get started with spew. See the +sections below for further details on formatting and configuration options. + +To dump a variable with full newlines, indentation, type, and pointer +information use Dump, Fdump, or Sdump: + + package main + + import ( + "git.wit.org/wit/gui" + ) + + func main() { + gui.Main(initGUI) + } + + // This initializes the first window + func initGUI() { + gui.Config.Title = "WIT GUI Window 1" + gui.Config.Width = 640 + gui.Config.Height = 480 + node1 := gui.NewWindow() + addDemoTab(node1, "A Simple Tab Demo") + } + + func addDemoTab(n *gui.Node, title string) { + newNode := n.AddTab(title, nil) + + groupNode1 := newNode.AddGroup("group 1") + groupNode1.AddComboBox("demoCombo2", "more 1", "more 2", "more 3") + } + +Configuration Options + +Configuration of the GUI is handled by fields in the ConfigType type. For +convenience, all of the top-level functions use a global state available +via the gui.Config global. + +The following configuration options are available: + * Width + When creating a new window, this is the width + + * Height + When creating a new window, this is the height + + * Debug + When 'true' log more output + +GUI Usage + +Errors + +Since it is possible for custom Stringer/error interfaces to panic, spew +detects them and handles them internally by printing the panic information +inline with the output. Since spew is intended to provide deep pretty printing +capabilities on structures, it intentionally does not return any errors. +*/ +package gui diff --git a/entry.go b/entry.go index d7f31c9..c10b7d0 100644 --- a/entry.go +++ b/entry.go @@ -13,26 +13,46 @@ func NewLabel(box *GuiBox, text string) { box.Append(ui.NewLabel(text), false) } -func GetText(box *GuiBox, name string) string { - if (box == nil) { - log.Println("gui.GetText() ERROR box == nil") - return "" - } - if (box.Window.EntryMap == nil) { +func (n *Node) NewLabel(text string) *Node { + // make new node here + // n.Append(ui.NewLabel(text), false) + newNode := makeNode(n, text, 333, 334) + newNode.Dump() + // panic("node.NewLabel()") + + n.Append(newNode) + return newNode +} + +func (b *GuiBox) GetText(name string) string { + if (b.Window.EntryMap == nil) { log.Println("gui.GetText() ERROR b.Box.Window.EntryMap == nil") return "" } - spew.Dump(box.Window.EntryMap) - if (box.Window.EntryMap[name] == nil) { + spew.Dump(b.Window.EntryMap) + if (b.Window.EntryMap[name] == nil) { log.Println("gui.GetText() ERROR box.Window.EntryMap[", name, "] == nil ") return "" } - e := box.Window.EntryMap[name] + e := b.Window.EntryMap[name] log.Println("gui.GetText() box.Window.EntryMap[", name, "] = ", e.UiEntry.Text()) log.Println("gui.GetText() END") return e.UiEntry.Text() } +func (n *Node) SetText(value string) error { + log.Println("gui.SetText() value =", value) + if (n.uiText != nil) { + n.uiText.SetText(value) + return nil + } + if (n.uiButton != nil) { + n.uiButton.SetText(value) + return nil + } + return nil +} + func SetText(box *GuiBox, name string, value string) error { if (box == nil) { return fmt.Errorf("gui.SetText() ERROR box == nil") @@ -42,7 +62,7 @@ func SetText(box *GuiBox, name string, value string) error { } spew.Dump(box.Window.EntryMap) if (box.Window.EntryMap[name] == nil) { - return fmt.Errorf("gui.SetText() ERROR box.Window.EntryMap[", name, "] == nil ") + return fmt.Errorf("gui.SetText() ERROR box.Window.EntryMap[" + name + "] == nil ") } e := box.Window.EntryMap[name] log.Println("gui.SetText() box.Window.EntryMap[", name, "] = ", e.UiEntry.Text()) diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..f65cad1 --- /dev/null +++ b/example_test.go @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package gui_test + +import ( + "git.wit.org/wit/gui" +) + +// This example demonstrates how to create a NewWindow() +// +// Interacting with a GUI in a cross platform fashion adds some +// unusual problems. To obvuscate those, andlabs/ui starts a +// goroutine that interacts with the native gui toolkits +// on the Linux, MacOS, Windows, etc. +// +// Because of this oddity, to initialize a new window, the +// function is not passed any arguements and instead passes +// the information via the Config type. +// +func ExampleNewWindow() { + // Define the name and size + gui.Config.Title = "WIT GUI Window 1" + gui.Config.Width = 640 + gui.Config.Height = 480 + + // Create the Window + gui.NewWindow() + + // Output: + // You get a window +} diff --git a/find.go b/find.go new file mode 100644 index 0000000..e046161 --- /dev/null +++ b/find.go @@ -0,0 +1,156 @@ +package gui + +import ( + "log" + "os" + + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +// "github.com/davecgh/go-spew/spew" +) + +func (n *Node) FindTab() *ui.Tab { + return n.uiTab +} + +func (n *Node) FindControl() *ui.Control { + return n.uiControl +} + +func (n *Node) FindBox() *GuiBox { + if (n.box != nil) { + return n.box + } + if (n.parent != nil) { + p := n.parent + return p.box + } + return n.box +} + +func (n *Node) FindWindowBox() *GuiBox { + if (n.box == nil) { + panic("SERIOUS ERROR n.box == nil in FindWindowBox()") + } + return n.box +} + +func (w *GuiWindow) FindNode() *Node { + return w.node +} + +func (b *GuiBox) FindNode() *Node { + log.Println("gui.FindNode() on GuiBox") + if b.node != nil { + return b.node + } + Data.ListChildren(true) + b.Dump() + log.Println("gui.FindNode() on GuiBox is nil") + os.Exit(-1) + return nil +} + +func FindWindow(s string) *GuiWindow { + for name, window := range Data.WindowMap { + if name == s { + return window + } + } + log.Printf("COULD NOT FIND WINDOW " + s) + return nil +} + +func FindBox(s string) *GuiBox { + for name, window := range Data.WindowMap { + if name != s { + continue + } + for name, abox := range window.BoxMap { + log.Printf("gui.DumpBoxes() \tBOX mapname=%-12s abox.Name=%-12s", name, abox.Name) + return abox + } + log.Println("gui.FindBox() NEED TO INIT WINDOW name =", name) + } + log.Println("gui.FindBox() COULD NOT FIND BOX", s) + return nil +} + +func FindNode(name string) *Node { + if Data.NodeMap == nil { + log.Println("gui.FindNode() gui.Data.NodeMap == nil") + return nil + } + log.Println("gui.FindNode() searching Data.NodeMap:") + for id, node := range Data.NodeMap { + log.Println("\tData.NodeMap name =", node.Width, node.Height, id) + node.Dump() + if (name == node.Name) { + return node + } + newNode := findByName(node, name) + if (newNode != nil) { + return newNode + } + log.Println("gui.FindNode() could not find node name =", name) + os.Exit(-1) + } + log.Println("gui.FindNode() could not find node name =", name) + return nil +} + +func (dn *GuiData) findId(id string) *Node { + if Data.NodeMap == nil { + log.Println("gui.Data.findId() map == nil") + return nil + } + // log.Println("Dumping Data.NodeMap:") + for name, node := range Data.NodeMap { + // log.Println("\tData.NodeMap name =", node.id, node.Width, node.Height, name) + if (id == node.id) { + log.Println("\tgui.Data.findId() found node =", node.id, node.Width, node.Height, name) + return node + } + // TODO: fix // Oct 9 + // node.findId(id) + } + return nil +} + +func findByIdDFS(node *Node, id string) *Node { + log.Println("findByIdDFS()", id, node) + node.Dump() + if node.id == id { + log.Println("Found node id =", id, node) + return node + } + + if len(node.children) > 0 { + for _, child := range node.children { + newNode := findByIdDFS(child, id) + if (newNode != nil) { + return newNode + } + } + } + return nil +} + +func findByName(node *Node, name string) *Node { + log.Println("findByName()", name, node) + node.Dump() + if node.Name == name { + log.Println("findByName() Found node name =", name, node) + return node + } + + if len(node.children) > 0 { + for _, child := range node.children { + newNode := findByName(child, name) + if (newNode != nil) { + return newNode + } + } + } + return nil +} diff --git a/gui-example/main.go b/gui-example/main.go deleted file mode 100644 index 1ff286d..0000000 --- a/gui-example/main.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "log" - "os" - "time" - - "git.wit.org/wit/gui" -) - -func customExit(gw *gui.GuiWindow) { - log.Println("Should Exit Here") - os.Exit(0) -} - -func main() { - log.Println("starting my Control Panel") - - gui.Config.Width = 800 - gui.Config.Height = 300 - gui.Config.Exit = customExit - - go gui.Main(initGUI) - - watchGUI() -} - -func initGUI() { - gui.NewWindow("jcarr start", 640, 480) -} - -func watchGUI() { - var i = 1 - for { - log.Println("Waiting for customExit()", i) - i += 1 - time.Sleep(time.Second) - if i == 3 { - log.Println("Sending ExampleWindow to gui.Queue()") - gui.Queue(gui.DebugWindow) - } - } -} diff --git a/gui.go b/gui.go index 97e3c24..4174133 100644 --- a/gui.go +++ b/gui.go @@ -18,6 +18,14 @@ func init() { Data.buttonMap = make(map[*ui.Button]*GuiButton) Data.WindowMap = make(map[string]*GuiWindow) + Data.NodeMap = make(map[string]*Node) + + Data.NodeSlice = make([]*Node, 0) + + Config.counter = 0 + Config.prefix = "wit" + Config.DebugNode = false + Config.DebugTabs = false } func GuiInit() { diff --git a/main.go b/main.go index 09d105f..70e0e65 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ func Main(f func()) { ui.Main(f) } -// Other goroutines must use this +// Other goroutines must use this to access the GUI // // You can not acess / process the GUI thread directly from // other goroutines. This is due to the nature of how @@ -23,28 +23,12 @@ func Queue(f func()) { ui.QueueMain(f) } +/* func ExampleWindow() { log.Println("START gui.ExampleWindow()") - title := "Test Window" - box := InitWindow(nil, title, 0) - window := box.Window - log.Println("box =", box) - log.Println("window =", window) - box.AddDebugTab("jcarr Debug") - - window.UiWindow.Show() -} - -func DebugWindow() { - log.Println("START gui.ExampleWindow()") - - title := "Debug Window" - box := InitWindow(nil, title, 0) - window := box.Window - log.Println("box =", box) - log.Println("window =", window) - box.AddDebugTab("jcarr Debug") - - window.UiWindow.Show() + Config.Title = "ExampleWindow" + node := NewWindow() + node.AddDebugTab("jcarr Debug") } +*/ diff --git a/new-structs.go b/new-structs.go index 0fa326a..4f9989e 100644 --- a/new-structs.go +++ b/new-structs.go @@ -2,12 +2,42 @@ package gui import ( "log" + "fmt" +// "reflect" + + // "github.com/davecgh/go-spew/spew" "github.com/andlabs/ui" _ "github.com/andlabs/ui/winmanifest" ) +type Element int + // https://ieftimov.com/post/golang-datastructures-trees/ +const ( + Unknown Element = iota + Window + Tab + Box + Label + Combo +) + +func (s Element) String() string { + switch s { + case Window: + return "window" + case Tab: + return "tab" + case Box: + return "box" + case Label: + return "label" + case Combo: + return "combo" + } + return "unknown" +} type Node struct { id string @@ -15,32 +45,227 @@ type Node struct { Width int Height int - uiType *ui.Control + parent *Node children []*Node + + window *GuiWindow + box *GuiBox + custom func(*Node) + + uiControl *ui.Control + uiButton *ui.Button + uiWindow *ui.Window + uiTab *ui.Tab + uiBox *ui.Box + uiText *ui.EditableCombobox } -func (n Node) SetName(name string) { +func (n *Node) Parent() *Node { + return n.parent +} + +func (n *Node) Window() *Node { + return n.parent +} + +func (n *Node) Dump() { + log.Println("gui.Node.Dump() id = ", n.id) + log.Println("gui.Node.Dump() Name = ", n.Name) + log.Println("gui.Node.Dump() Width = ", n.Width) + log.Println("gui.Node.Dump() Height = ", n.Height) + + if (n.parent == nil) { + log.Println("gui.Node.Dump() parent = nil") + } else { + log.Println("gui.Node.Dump() parent = ", n.parent.id) + } + log.Println("gui.Node.Dump() children = ", n.children) + + log.Println("gui.Node.Dump() window = ", n.window) + log.Println("gui.Node.Dump() box = ", n.box) + + log.Println("gui.Node.Dump() uiWindow = ", n.uiWindow) + log.Println("gui.Node.Dump() uiTab = ", n.uiTab) + log.Println("gui.Node.Dump() uiBox = ", n.uiBox) + log.Println("gui.Node.Dump() uiControl = ", n.uiControl) + log.Println("gui.Node.Dump() uiButton = ", n.uiButton) + if (n.id == "") { + panic("gui.Node.Dump() id == nil") + } +} + + +func (n *Node) SetBox(box *GuiBox) { + n.box = box +} + +func (n *Node) SetName(name string) { // n.uiType.SetName(name) - log.Println("n.uiType =", n.uiType) + if (n.uiWindow != nil) { + log.Println("node is a window. setting title =", name) + n.uiWindow.SetTitle(name) + return + } + log.Println("*ui.Control =", n.uiControl) return } -func (n Node) Append(child Node) { +func (n *Node) Append(child *Node) { // if (n.UiBox == nil) { // return // } - // n.uiType.Append(child, x) + n.children = append(n.children, child) + if (Config.Debug) { + log.Println("child node:") + child.Dump() + log.Println("parent node:") + n.Dump() + } + // time.Sleep(3 * time.Second) } -func findByIdDFS(node *Node, id string) *Node { - if node.id == id { - return node +func (n *Node) List() { + findByIdDFS(n, "test") +} + +var listChildrenParent *Node +var listChildrenDepth int = 0 + +func indentPrintln(depth int, format string, a ...interface{}) { + var tabs string + for i := 0; i < depth; i++ { + tabs = tabs + "\t" } - if len(node.children) > 0 { - for _, child := range node.children { - findByIdDFS(child, id) + // newFormat := tabs + strconv.Itoa(depth) + " " + format + newFormat := tabs + format + log.Println(newFormat, a) +} + +func (n *Node) ListChildren(dump bool) { + indentPrintln(listChildrenDepth, "\t", n.id, n.Width, n.Height, n.Name) + + if (dump == true) { + n.Dump() + } + if len(n.children) == 0 { + if (n.parent == nil) { + } else { + if (Config.DebugNode) { + log.Println("\t\t\tparent =",n.parent.id) + } + if (listChildrenParent != nil) { + if (Config.DebugNode) { + log.Println("\t\t\tlistChildrenParent =",listChildrenParent.id) + } + if (listChildrenParent.id != n.parent.id) { + log.Println("parent.child does not match child.parent") + panic("parent.child does not match child.parent") + } + } } + if (Config.DebugNode) { + log.Println("\t\t", n.id, "has no children") + } + return } - return nil + for _, child := range n.children { + // log.Println("\t\t", child.id, child.Width, child.Height, child.Name) + if (child.parent != nil) { + if (Config.DebugNode) { + log.Println("\t\t\tparent =",child.parent.id) + } + } else { + log.Println("\t\t\tno parent") + panic("no parent") + } + if (dump == true) { + child.Dump() + } + if (Config.DebugNode) { + if (child.children == nil) { + log.Println("\t\t", child.id, "has no children") + } else { + log.Println("\t\t\tHas children:", child.children) + } + } + listChildrenParent = n + listChildrenDepth += 1 + child.ListChildren(dump) + listChildrenDepth -= 1 + } + return +} + +// The parent Node needs to be the raw Window +// The 'stuff' Node needs to be the contents of the tab +// +// This function should make a new node with the parent and +// the 'stuff' Node as a child +func (n *Node) AddTabNode(title string, b *GuiBox) *Node { + var newNode *Node + parent := n + + newNode = parent.makeNode(title, 444, 400 + Config.counter) + newNode.uiTab = parent.uiTab + newNode.box = b + + if (Config.DebugNode) { + fmt.Println("") + log.Println("parent:") + parent.Dump() + + fmt.Println("") + log.Println("newNode:") + newNode.Dump() + } + + if (newNode.uiTab == nil) { + log.Println("wit/gui/ AddTabNode() Something went wrong tab == nil") + // TODO: try to find the tab or window and make them if need be + return newNode + } + newNode.uiTab.Append(title, b.UiBox) + + return newNode +} + +func (n *Node) AddTab(title string, uiC *ui.Box) *Node { + parent := n + log.Println("gui.Node.AddTab() START name =", title) + if parent.uiWindow == nil { + parent.Dump() + log.Println("gui.Node.AddTab() ERROR ui.Window == nil") + return nil + } + if parent.box == nil { + parent.Dump() + panic("gui.AddTab() ERROR box == nil") + } + if parent.uiTab == nil { + inittab := ui.NewTab() // no, not that 'inittab' + parent.uiWindow.SetChild(inittab) + parent.uiWindow.SetMargined(true) + parent.uiTab = inittab + + // parent.Dump() + // panic("gui.AddTab() ERROR uiTab == nil") + } + + tab := parent.uiTab + parent.uiWindow.SetMargined(true) + + if (uiC == nil) { + hbox := ui.NewHorizontalBox() + hbox.SetPadded(true) + uiC = hbox + } + tab.Append(title, uiC) + + newNode := parent.makeNode(title, 555, 600 + Config.counter) + newNode.uiTab = tab + newNode.uiBox = uiC + // panic("gui.AddTab() after makeNode()") + tabSetMargined(newNode.uiTab) + return newNode } diff --git a/structs.go b/structs.go index ad3307b..c15f4b7 100644 --- a/structs.go +++ b/structs.go @@ -19,11 +19,20 @@ var Data GuiData var Config GuiConfig type GuiConfig struct { + Title string Width int Height int - Debug bool - DebugTable bool - Exit func(*GuiWindow) + Exit func(*Node) + + Debug bool + DebugNode bool + DebugTabs bool + DebugTable bool + DebugWindow bool + + depth int + counter int // used to make unique ID's + prefix string } type GuiData struct { @@ -35,7 +44,10 @@ type GuiData struct { AllEntries []*GuiEntry WindowMap map[string]*GuiWindow - // Windows []*GuiWindow + // Store access to everything via binary tree's + NodeMap map[string]*Node + NodeArray []*Node + NodeSlice []*Node // A map of all buttons everywhere on all // windows, all tabs, across all goroutines @@ -45,7 +57,6 @@ type GuiData struct { // andlabs/ui & andlabs/libui work AllButtons []*GuiButton buttonMap map[*ui.Button]*GuiButton - Nodes *Node } type GuiTab struct { @@ -87,11 +98,20 @@ type GuiWindow struct { EntryMap map[string]*GuiEntry Area *GuiArea + node *Node + // andlabs/ui abstraction mapping UiWindow *ui.Window UiTab *ui.Tab // if this != nil, the window is 'tabbed' } +func (w *GuiWindow) Dump() { + log.Println("gui.GuiWindow.Dump() Name = ", w.Name) + log.Println("gui.GuiWindow.Dump() node = ", w.node) + log.Println("gui.GuiWindow.Dump() Width = ", w.Width) + log.Println("gui.GuiWindow.Dump() Height = ", w.Height) +} + // GuiBox is any type of ui.Hbox or ui.Vbox // There can be lots of these for each GuiWindow type GuiBox struct { @@ -99,121 +119,62 @@ type GuiBox struct { Axis int // does it add items to the X or Y axis Window *GuiWindow // the parent Window + node *Node + // andlabs/ui abstraction mapping UiBox *ui.Box } -func (s GuiBox) SetTitle(title string) { +func (b *GuiBox) Dump() { + log.Println("gui.GuiBox.Dump() Name = ", b.Name) + log.Println("gui.GuiBox.Dump() Axis = ", b.Axis) + log.Println("gui.GuiBox.Dump() GuiWindow = ", b.Window) + log.Println("gui.GuiBox.Dump() node = ", b.node) + log.Println("gui.GuiBox.Dump() UiBox = ", b.UiBox) +} + +func (b *GuiBox) SetTitle(title string) { log.Println("DID IT!", title) - if s.Window == nil { + if b.Window == nil { return } - if s.Window.UiWindow == nil { + if b.Window.UiWindow == nil { return } - s.Window.UiWindow.SetTitle(title) + b.Window.UiWindow.SetTitle(title) return } -func (s GuiBox) Append(child ui.Control, x bool) { - if s.UiBox == nil { +func (w *GuiWindow) SetNode(n *Node) { + if (w.node != nil) { + w.Dump() + panic("gui.SetNode() Error not nil") + } + w.node = n + if (w.node == nil) { + w.Dump() + panic("gui.SetNode() node == nil") + } +} + +func (b *GuiBox) SetNode(n *Node) { + if (b.node != nil) { + b.Dump() + panic("gui.SetNode() Error not nil") + } + b.node = n + if (b.node == nil) { + b.Dump() + panic("gui.SetNode() node == nil") + } +} + +func (b *GuiBox) Append(child ui.Control, x bool) { + if b.UiBox == nil { + panic("GuiBox.Append() can't work. UiBox == nil") return } - s.UiBox.Append(child, x) -} - -/* -func (w GuiWindow) InitWindow(title string) *GuiBox { - if w.UiWindow == nil { - log.Println("gui.InitBox() THIS SHOULD NEVER HAPPEN. Window doesn't exist", w) - return nil - } - tab := ui.NewTab() - w.UiWindow.SetChild(tab) - w.UiWindow.SetMargined(true) - - tab.Append(title, InitBlankWindow()) - tab.SetMargined(0, true) - - w.UiTab = tab - return nil -} -*/ - -func (s GuiBox) InitTab(title string, custom func() ui.Control) *ui.Tab { - if s.Window == nil { - return nil - } - if s.Window.UiWindow == nil { - return nil - } - - window := s.Window.UiWindow - tab := ui.NewTab() - window.SetChild(tab) - window.SetMargined(true) - - tab.Append(title, custom()) - tab.SetMargined(0, true) - // tab.SetMargined(1, true) - - s.Window.UiTab = tab - return tab -} - -func (s GuiBox) AddTab(title string, custom ui.Control) *ui.Tab { - if s.Window == nil { - return nil - } - if s.Window.UiTab == nil { - return nil - } - - tab := s.Window.UiTab - - tab.Append(title, custom) - return tab -} - -func (s GuiBox) AddTab2(title string, custom ui.Control) *ui.Tab { - if s.Window == nil { - return nil - } - if s.Window.UiTab == nil { - return nil - } - - tab := s.Window.UiTab - tab.Append(title, custom) - return tab -} - -func (s GuiBox) AddBoxTab(title string) *GuiBox { - uiTab := s.AddTab2(title, InitBlankWindow()) - tabSetMargined(uiTab) - - var box *GuiBox - box = HardBox(s.Window, Xaxis, "jcarrAddBoxTab") - box.Window.UiTab = uiTab - return box -} - -func (s GuiBox) AddDemoTab(title string) { - uiTab := s.AddTab(title, makeWindowTemplate()) - tabSetMargined(uiTab) -} - -func (s GuiBox) AddDebugTab(title string) { - uiTab := s.AddTab(title, makeWindowDebug()) - tabSetMargined(uiTab) -} - -func tabSetMargined(tab *ui.Tab) { - c := tab.NumPages() - for i := 0; i < c; i++ { - log.Println("tabSetMargined() i =", i) - // tab.SetMargined(i, true) - } + b.UiBox.Append(child, x) } // Note: every mouse click is handled diff --git a/table.go b/table.go index 87e69b2..92ae871 100644 --- a/table.go +++ b/table.go @@ -98,12 +98,12 @@ func InitColumns(mh *TableData, parts []TableColumnData) { } func AddTableTab(gw *GuiWindow, name string, rowcount int, parts []TableColumnData) *TableData { - box := InitWindow(gw, name, Yaxis) - - return AddTableBox(box, name, rowcount, parts) + node := NewWindow() + b := node.box + return b.AddTableBox(name, rowcount, parts) } -func AddTableBox(box *GuiBox, name string, rowcount int, parts []TableColumnData) *TableData { +func (b *GuiBox) AddTableBox(name string, rowcount int, parts []TableColumnData) *TableData { mh := new(TableData) mh.RowCount = rowcount @@ -142,9 +142,9 @@ func AddTableBox(box *GuiBox, name string, rowcount int, parts []TableColumnData // is this needed? // gw.BoxMap[name] = box - mh.Box = box + mh.Box = b - box.UiBox.Append(table, true) + b.UiBox.Append(table, true) return mh } diff --git a/tableCallbacks.go b/tableCallbacks.go index 966f173..6eefd8d 100644 --- a/tableCallbacks.go +++ b/tableCallbacks.go @@ -30,7 +30,8 @@ func (mh *TableData) ColumnTypes(m *ui.TableModel) []ui.TableValue { } // TODO: Figure out why this is being called 1000 times a second (10 times for each row & column) -// Nevermind this TODO. Who gives a shit. This is a really smart way to treat the OS toolkits +// +// Nevermind that TODO. Who gives a shit. This is a really smart way to treat the OS toolkits func (mh *TableData) CellValue(m *ui.TableModel, row, column int) ui.TableValue { if (Config.DebugTable) { log.Println("CellValue() row, column =", row, column) diff --git a/window.go b/window.go index 2b615b5..403638e 100644 --- a/window.go +++ b/window.go @@ -2,40 +2,13 @@ package gui import ( "log" + "fmt" "strconv" - "time" "github.com/andlabs/ui" - - // import "regexp" - _ "github.com/andlabs/ui/winmanifest" ) -func initUI(name string, callback func(*GuiBox) *GuiBox) { - ui.Main(func() { - log.Println("gui.initUI() inside ui.Main()") - - box := InitWindow(nil, "StartNewWindow"+name, 0) - box = callback(box) - window := box.Window - log.Println("StartNewWindow() box =", box) - - window.UiWindow.Show() - }) -} - -func StartNewWindow(bg bool, name string, axis int, callback func(*GuiBox) *GuiBox) { - log.Println("StartNewWindow() ui.Main() Create a new window") - - if bg { - go initUI(name, callback) - time.Sleep(500 * time.Millisecond) // this might make it more stable on windows? - } else { - initUI(name, callback) - } -} - func MessageWindow(gw *GuiWindow, msg1 string, msg2 string) { ui.MsgBox(gw.UiWindow, msg1, msg2) } @@ -44,71 +17,6 @@ func ErrorWindow(gw *GuiWindow, msg1 string, msg2 string) { ui.MsgBoxError(gw.UiWindow, msg1, msg2) } -// -// This creates a new 'window' (which is just a tab in the window) -// This is this way because on Linux you can have more than one -// actual window but that does not appear to work on the MacOS or Windows -// -func InitWindow(gw *GuiWindow, name string, axis int) *GuiBox { - log.Println("InitGuiWindow() START") - - var box *GuiBox - if gw == nil { - box = mapWindow(nil, name, Config.Height, Config.Width) - } else { - box = mapWindow(gw.UiWindow, name, Config.Height, Config.Width) - } - - // box.Window = &newGuiWindow - newGuiWindow := box.Window - - // This is the first window. One must create it here - if gw == nil { - log.Println("initWindow() ADDING ui.NewWindow()") - newGuiWindow.UiWindow = ui.NewWindow(name, int(newGuiWindow.Height), int(newGuiWindow.Width), true) - newGuiWindow.UiWindow.SetBorderless(false) - - // newGuiWindow.UiWindow.SetTitle("test") - newGuiWindow.UiWindow.OnClosing(func(*ui.Window) bool { - log.Println("initTabWindow() OnClosing() THIS WINDOW IS CLOSING newGuiWindow=", newGuiWindow) - // newGuiWindow.UiWindow.Destroy() - if Config.Exit == nil { - ui.Quit() - } else { - // allow a custom exit function - Config.Exit(newGuiWindow) - } - return true - }) - - newGuiWindow.UiTab = ui.NewTab() - newGuiWindow.UiWindow.SetChild(newGuiWindow.UiTab) - newGuiWindow.UiWindow.SetMargined(true) - tmp := 0 - newGuiWindow.TabNumber = &tmp - } else { - newGuiWindow.UiWindow = gw.UiWindow - newGuiWindow.UiTab = gw.UiTab - } - - newGuiWindow.BoxMap = make(map[string]*GuiBox) - newGuiWindow.EntryMap = make(map[string]*GuiEntry) - // Data.Windows = append(Data.Windows, &newGuiWindow) - - if newGuiWindow.UiTab == nil { - tabnum := 0 - newGuiWindow.TabNumber = &tabnum - } else { - tabnum := newGuiWindow.UiTab.NumPages() - newGuiWindow.TabNumber = &tabnum - } - - Data.WindowMap[newGuiWindow.Name] = newGuiWindow - - log.Println("InitGuiWindow() END *GuiWindow =", newGuiWindow) - return box -} - func DeleteWindow(name string) { log.Println("gui.DeleteWindow() START name =", name) window := Data.WindowMap[name] @@ -144,57 +52,102 @@ func DeleteWindow(name string) { } } -func CreateWindow(title string, tabname string, x int, y int, custom func() ui.Control) *GuiBox { - box := CreateBlankWindow(title, x, y) - box.InitTab(title, custom) - return box +func makeNode(parent *Node, title string, x int, y int) *Node { + var node Node + node.Name = title + node.Width = x + node.Height = y + + id := Config.prefix + strconv.Itoa(Config.counter) + Config.counter += 1 + node.id = id + + // panic("gui.makeNode() START") + if (parent == nil) { + if (Data.NodeMap[title] != nil) { + log.Println("Duplicate window name =", title) + // TODO: just change the 'title' to something unique + panic(fmt.Sprintf("Duplicate window name = %s\n", title)) + return nil + } + // panic("gui.makeNode() before NodeMap()") + Data.NodeMap[title] = &node + Data.NodeArray = append(Data.NodeArray, &node) + Data.NodeSlice = append(Data.NodeSlice, &node) + // panic("gui.makeNode() after NodeMap()") + return &node + } else { + // panic("gui.makeNode() before Append()") + parent.Append(&node) + // panic("gui.makeNode() after Append()") + } + node.parent = parent + return &node } -func CreateBlankWindow(title string, x int, y int) *GuiBox { - box := mapWindow(nil, title, x, y) - log.Println("gui.CreateBlankWindow() title = box.Name =", box.Name) +func (parent *Node) makeNode(title string, x int, y int) *Node { + var node Node + node.Name = title + node.Width = x + node.Height = y - window := ui.NewWindow(box.Name, x, y, false) - window.SetBorderless(false) - window.OnClosing(func(*ui.Window) bool { - log.Println("createWindow().OnClosing()", box.Name) + id := Config.prefix + strconv.Itoa(Config.counter) + Config.counter += 1 + node.id = id + + parent.Append(&node) + node.parent = parent + return &node +} + +func (n *Node) AddNode(title string) *Node { + var node Node + node.Name = title + node.Width = n.Width + node.Height = n.Height + + id := Config.prefix + strconv.Itoa(Config.counter) + Config.counter += 1 + node.id = id + + n.Append(&node) + node.parent = n + return &node +} + +func (n *Node) uiNewWindow(title string, x int, y int) { + w := ui.NewWindow(title, x, y, false) + w.SetBorderless(false) + f := Config.Exit + w.OnClosing(func(*ui.Window) bool { + if (Config.Debug) { + log.Println("ui.Window().OnClosing()") + } + if (f != nil) { + f(n) + } return true }) - ui.OnShouldQuit(func() bool { - log.Println("createWindow().Destroy()", box.Name) - window.Destroy() - return true - }) - - window.SetMargined(true) - window.Show() - - box.Window.UiWindow = window - return box + w.SetMargined(true) + w.Show() + n.uiWindow = w + // w.node = &node + return } -func InitBlankWindow() ui.Control { - hbox := ui.NewHorizontalBox() - hbox.SetPadded(true) - - return hbox -} - -var master = 0 - -func mapWindow(window *ui.Window, title string, x int, y int) *GuiBox { +func mapWindow(parent *Node, window *ui.Window, title string, x int, y int) *Node { log.Println("gui.WindowMap START title =", title) if Data.WindowMap[title] != nil { log.Println("Data.WindowMap[title] already exists title =", title) - master = master + 1 - title = title + " jcarr " + strconv.Itoa(master) + title = title + Config.prefix + strconv.Itoa(Config.counter) + Config.counter += 1 } if Data.WindowMap[title] != nil { log.Println("Data.WindowMap[title] already exists title =", title) panic("Data.WindowMap[newGuiWindow.Name] already exists") return nil } - log.Println("gui.WindowMap START title =", title) + var newGuiWindow GuiWindow newGuiWindow.Width = x newGuiWindow.Height = y @@ -210,30 +163,47 @@ func mapWindow(window *ui.Window, title string, x int, y int) *GuiBox { box.Window = &newGuiWindow box.Name = title + node := makeNode(parent, title, x, y) + node.box = &box + node.uiWindow = window + box.node = node + newGuiWindow.BoxMap["jcarrInitTest"] = &box - return &box + return node } -func NewWindow(title string, x int, y int) *GuiBox { - box := mapWindow(nil, title, x, y) +// This routine creates a blank window with a Title and size (W x H) +// +// This routine can not have any arguements due to the nature of how +// it can be passed via the 'andlabs/ui' queue which, because it is +// cross platform, must pass UI changes into the OS threads (that is +// my guess). +func NewWindow() *Node { + title := Config.Title + w := Config.Width + h := Config.Height + + var n *Node + n = mapWindow(nil, nil, title, w, h) + box := n.box log.Println("gui.NewWindow() title = box.Name =", box.Name) - window := ui.NewWindow(box.Name, x, y, false) - window.SetBorderless(false) - window.OnClosing(func(*ui.Window) bool { - log.Println("createWindow().OnClosing()", box.Name) - return true - }) - ui.OnShouldQuit(func() bool { - log.Println("createWindow().Destroy()", box.Name) - window.Destroy() - return true - }) + n.uiNewWindow(box.Name, w, h) + window := n.uiWindow - window.SetMargined(true) - window.Show() + f := Config.Exit + ui.OnShouldQuit(func() bool { + log.Println("createWindow().Destroy() on node.Name =", n.Name) + if (f != nil) { + f(n) + } + return true + }) box.Window.UiWindow = window - return box + if(n.uiWindow == nil) { + panic("node.uiWindow == nil. This should never happen") + } + return n }