diff --git a/.gitignore b/.gitignore index 0a7f6ab..03137f9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ cmds/console-ui-helloworld/console-ui-helloworld cmds/debug/debug cmds/helloworld/helloworld cmds/textbox/textbox +cmds/cloudflare/cloudflare cmds/*/helloconsole # temporary files when building debian packages diff --git a/Makefile b/Makefile index 2949aae..8e73c22 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ all: README.md @echo make clean make plugins + make cmds-buttonplugin build-dep: apt install -f libgtk-3-dev @@ -82,11 +83,11 @@ clean: plugins: plugins-gocui plugins-andlabs plugins-gocui: - GO111MODULE="off" go build -v -x -C toolkit/gocui -buildmode=plugin -o ../gocui.so - GO111MODULE="off" go build -v -x -C toolkit/nocui -buildmode=plugin -o ../nocui.so + GO111MODULE="off" go build -C toolkit/gocui -v -buildmode=plugin -o ../gocui.so + GO111MODULE="off" go build -C toolkit/nocui -v -buildmode=plugin -o ../nocui.so plugins-andlabs: - GO111MODULE="off" go build -v -x -C toolkit/andlabs -buildmode=plugin -o ../andlabs.so + GO111MODULE="off" go build -C toolkit/andlabs -v -buildmode=plugin -o ../andlabs.so objdump: objdump -t toolkit/andlabs.so |less diff --git a/README-goreadme.md b/README-goreadme.md index 63698b8..454cfff 100644 --- a/README-goreadme.md +++ b/README-goreadme.md @@ -139,7 +139,7 @@ Creates a window helpful for debugging this package `func ShowDebugValues()` -### func [StandardExit](/main.go#L149) +### func [StandardExit](/main.go#L153) `func StandardExit()` @@ -158,13 +158,19 @@ This goroutine can be used like a watchdog timer ## Types -### type [GuiArgs](/structs.go#L27) +### type [GuiArgs](/structs.go#L29) `type GuiArgs struct { ... }` This struct can be used with the go-arg package -### type [Node](/structs.go#L57) +#### Variables + +```golang +var GuiArg GuiArgs +``` + +### type [Node](/structs.go#L59) `type Node struct { ... }` diff --git a/cmds/buttonplugin/Makefile b/cmds/buttonplugin/Makefile index 1903a67..74486e5 100644 --- a/cmds/buttonplugin/Makefile +++ b/cmds/buttonplugin/Makefile @@ -7,7 +7,7 @@ # run: build - ./buttonplugin --gui-toolkit gocui >/tmp/witgui.log.stderr 2>&1 + ./buttonplugin --gui gocui >/tmp/witgui.log.stderr 2>&1 build-release: go get -v -u -x . diff --git a/cmds/buttonplugin/log.go b/cmds/buttonplugin/log.go index 13238f4..7d65d05 100644 --- a/cmds/buttonplugin/log.go +++ b/cmds/buttonplugin/log.go @@ -5,6 +5,7 @@ import ( "fmt" arg "github.com/alexflint/go-arg" "git.wit.org/wit/gui" + log "git.wit.org/wit/gui/log" ) @@ -14,6 +15,7 @@ var args struct { User string `arg:"env:USER"` Demo bool `help:"run a demo"` gui.GuiArgs + log.LogArgs } /* @@ -26,6 +28,11 @@ func init() { arg.MustParse(&args) fmt.Println(args.Foo, args.Bar, args.User) + if (args.Gui != "") { + gui.GuiArg.Gui = args.Gui + } + log.Log(true, "INIT() args.GuiArg.Gui =", gui.GuiArg.Gui) + /* log.Println() log.Println("STDOUT is now at /tmp/guilogfile") diff --git a/cmds/buttonplugin/main.go b/cmds/buttonplugin/main.go index 1fd8b5f..3200e9e 100644 --- a/cmds/buttonplugin/main.go +++ b/cmds/buttonplugin/main.go @@ -20,14 +20,7 @@ func main() { // This will turn on all debugging // gui.SetDebug(true) - // myGui = gui.New().LoadToolkit("gocui") - // myGui = gui.New().LoadToolkit("andlabs") - // myGui = gui.New().Default() - if (args.GuiToolkit == nil) { - myGui = gui.New().Default() - } else { - myGui = gui.New().LoadToolkit(args.GuiToolkit[0]) - } + myGui = gui.New().Default() buttonWindow() // This is just a optional goroutine to watch that things are alive diff --git a/cmds/cloudflare/Makefile b/cmds/cloudflare/Makefile new file mode 100644 index 0000000..bcd88c6 --- /dev/null +++ b/cmds/cloudflare/Makefile @@ -0,0 +1,18 @@ +run: build + ./cloudflare + +build-release: + go get -v -u -x . + go build + ./cloudflare + +build: + GO111MODULE="off" go get -v -x . + GO111MODULE="off" go build + +update: + GO111MODULE="off" go get -v -u -x . + +log: + reset + tail -f /tmp/witgui.* /tmp/guilogfile diff --git a/cmds/cloudflare/argv.go b/cmds/cloudflare/argv.go new file mode 100644 index 0000000..38579c7 --- /dev/null +++ b/cmds/cloudflare/argv.go @@ -0,0 +1,30 @@ +// This creates a simple hello world window +package main + +import ( + "fmt" + arg "github.com/alexflint/go-arg" + "git.wit.org/wit/gui" + log "git.wit.org/wit/gui/log" +) + + +var args struct { + Foo string + Bar bool + User string `arg:"env:USER"` + Demo bool `help:"run a demo"` + gui.GuiArgs + log.LogArgs +} + +func init() { + arg.MustParse(&args) + fmt.Println(args.Foo, args.Bar, args.User) + + if (args.Gui != "") { + gui.GuiArg.Gui = args.Gui + } + log.Log(true, "INIT() args.GuiArg.Gui =", gui.GuiArg.Gui) + +} diff --git a/cmds/cloudflare/dns.go b/cmds/cloudflare/dns.go new file mode 100644 index 0000000..6626843 --- /dev/null +++ b/cmds/cloudflare/dns.go @@ -0,0 +1,115 @@ +// This is a simple example +package main + +import ( + "os" + "log" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strconv" +) + +// Define a struct to match the JSON structure of the response. +// This structure should be adjusted based on the actual format of the response. +type DNSRecords struct { + Result []struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Content string `json:"content"` + Proxied bool `json:"proxied"` + Proxiable bool `json:"proxiable"` + TTL int `json:"ttl"` + } `json:"result"` +} + +// var domain string = "wit.org" +// var os.Getenv("CLOUDFLARE_DOMAIN") + +func loadDNS(hostname string) { + log.Println("adding DNS record") + + // more2.NewButton(name, func () { + // log.Println(name, "ip =", ip) + // }) + + newt := mainWindow.NewTab(hostname) + newg := newt.NewGroup("more") + more2 := newg.NewGrid("gridnuts", 5, gridH) + + records := getRecords() + for _, record := range records.Result { + more2.NewLabel(record.Type) + more2.NewLabel(record.Name) + if (record.Proxied) { + more2.NewLabel("Proxied") + } else { + more2.NewLabel("DNS") + } + var ttl, short string + if (record.TTL == 1) { + ttl = "Auto" + } else { + ttl = strconv.Itoa(record.TTL) + } + more2.NewLabel(ttl) + // short = fmt.Sprintf("%80s", record.Content) + short = record.Content + if len(short) > 40 { + short = short[:40] // Slice the first 20 characters + } + more2.NewLabel(short) + + fmt.Printf("ID: %s, Type: %s, Name: %s, short Content: %s\n", record.ID, record.Type, record.Name, short) + fmt.Printf("\tproxied: %b, %b, string TTL: %i\n", record.Proxied, record.Proxiable, ttl) + } +} + + +func getRecords() *DNSRecords { + var url string = os.Getenv("CLOUDFLARE_URL") + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Println(err) + return nil + } + + var authKey string = os.Getenv("CLOUDFLARE_AUTHKEY") + var email string = os.Getenv("CLOUDFLARE_EMAIL") + + // Set headers + req.Header.Set("X-Auth-Key", authKey) + req.Header.Set("X-Auth-Email", email) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + fmt.Println(err) + return nil + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println(err) + return nil + } + + var records DNSRecords + if err := json.Unmarshal(body, &records); err != nil { + fmt.Println(err) + return nil + } + + // Process the records as needed + /* + for _, record := range records.Result { + fmt.Printf("ID: %s, Type: %s, Name: %s, Content: %s\n", record.ID, record.Type, record.Name, record.Content) + fmt.Printf("\tproxied: %b, %b, TTL: %i\n", record.Proxied, record.Proxiable, record.TTL) + } + */ + + return &records +} diff --git a/cmds/cloudflare/main.go b/cmds/cloudflare/main.go new file mode 100644 index 0000000..75ba448 --- /dev/null +++ b/cmds/cloudflare/main.go @@ -0,0 +1,122 @@ +// This is a simple example +package main + +import ( + "os" + "fmt" + "log" + "strconv" + "git.wit.org/wit/gui" +) + +var title string = "Cloudflare DNS Control Panel" +var outfile string = "/tmp/guilogfile" +var myGui *gui.Node + +var buttonCounter int = 5 +var gridW int = 5 +var gridH int = 3 + +var mainWindow, more, more2 *gui.Node + +func main() { + myGui = gui.New().Default() + buttonWindow() + + // This is just a optional goroutine to watch that things are alive + gui.Watchdog() + gui.StandardExit() +} + +// This creates a window +func buttonWindow() { + var t, g *gui.Node + + log.Println("buttonWindow() START") + + mainWindow = myGui.NewWindow(title).SetText(title) + t = mainWindow.NewTab("Cloudflare") + g = t.NewGroup("buttons") + g1 := t.NewGroup("buttonGroup 2") + + more = g1.NewGroup("more") + showCloudflareCredentials(more) + + g1.NewButton("hello", func () { + log.Println("world") + }) + more2 = g1.NewGrid("gridnuts", gridW, gridH) + + var domain string = os.Getenv("CLOUDFLARE_DOMAIN") + if (domain == "") { + domain = "example.org" + } + + g.NewButton("Load " + domain + " DNS", func () { + loadDNS(domain) + }) + + g.NewButton("Load 'gocui'", func () { + // this set the xterm and mate-terminal window title. maybe works generally? + fmt.Println("\033]0;" + title + "blah \007") + myGui.LoadToolkit("gocui") + }) + + g.NewButton("Load 'andlabs'", func () { + myGui.LoadToolkit("andlabs") + }) + + g.NewButton("NewButton(more)", func () { + name := "foobar " + strconv.Itoa(buttonCounter) + log.Println("NewButton(more) Adding button", name) + buttonCounter += 1 + more.NewButton(name, func () { + log.Println("Got all the way to main() name =", name) + }) + }) + + g.NewButton("NewButton(more2)", func () { + name := "foobar " + strconv.Itoa(buttonCounter) + log.Println("NewButton(more2) Adding button", name) + buttonCounter += 1 + more2.NewButton(name, func () { + log.Println("Got all the way to main() name =", name) + }) + }) + + g.NewButton("NewButton(more2 d)", func () { + name := "d" + strconv.Itoa(buttonCounter) + log.Println("NewButton(more2 d) Adding button", name) + buttonCounter += 1 + more2.NewButton(name, func () { + log.Println("Got all the way to main() name =", name) + }) + }) + + g.NewButton("NewGroup()", func () { + name := "neat " + strconv.Itoa(buttonCounter) + log.Println("NewGroup() Adding button", name) + buttonCounter += 1 + more.NewGroup(name) + }) + + g.NewButton("gui.DebugWindow()", func () { + gui.DebugWindow() + }) +} + +func showCloudflareCredentials(box *gui.Node) { + grid := box.NewGrid("credsGrid", 2, 4) // width = 2 + + grid.NewLabel("Domain") + grid.NewLabel(os.Getenv("CLOUDFLARE_DOMAIN")) + + grid.NewLabel("Auth Key") + grid.NewLabel(os.Getenv("CLOUDFLARE_AUTHKEY")) + + grid.NewLabel("Email") + grid.NewLabel(os.Getenv("CLOUDFLARE_EMAIL")) + + grid.NewLabel("URL") + grid.NewLabel(os.Getenv("CLOUDFLARE_URL")) +} diff --git a/log/structs.go b/log/structs.go new file mode 100644 index 0000000..b5d849c --- /dev/null +++ b/log/structs.go @@ -0,0 +1,13 @@ +package witlog + +import ( +) + +// +// Attempt to switch logging to syslog on linux +// + +// This struct can be used with the go-arg package +type LogArgs struct { + Log []string `arg:"--log" help:"Where to log [syslog,stdout]"` +} diff --git a/main.go b/main.go index 17b69ba..f121cec 100644 --- a/main.go +++ b/main.go @@ -123,6 +123,10 @@ func New() *Node { // try to load andlabs, if that doesn't work, fall back to the console func (n *Node) Default() *Node { + if (GuiArg.Gui != "") { + log(logError, "New.Default() try toolkit =", GuiArg.Gui) + return n.LoadToolkit(GuiArg.Gui) + } // if DISPLAY isn't set, return since gtk can't load // TODO: figure out how to check what to do in macos and mswindows if (os.Getenv("DISPLAY") == "") { diff --git a/plugin.go b/plugin.go index d9f2bdd..42dc7cc 100644 --- a/plugin.go +++ b/plugin.go @@ -119,8 +119,15 @@ func searchPaths(name string) *aplug { filename = "plugins/" + name + ".so" pfile, err = me.resFS.ReadFile(filename) if (err == nil) { + filename = "/tmp/" + name + ".so" log(logError, "write out file here", name, filename, len(pfile)) - exit() + f, _ := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0600) + f.Write(pfile) + f.Close() + p := initToolkit(name, filename) + if (p != nil) { + return p + } } else { log(logError, filename, "was not embedded. Error:", err) } diff --git a/structs.go b/structs.go index 53572f2..b7a38a2 100644 --- a/structs.go +++ b/structs.go @@ -23,9 +23,11 @@ import ( var me guiConfig +var GuiArg GuiArgs + // This struct can be used with the go-arg package type GuiArgs struct { - GuiToolkit []string `arg:"--gui-toolkit" help:"The order to attempt loading plugins [gocui,andlabs,gtk,qt]"` + Gui string `arg:"--gui" help:"Use this gui toolkit [andlabs,gocui,nocui]"` GuiDebug bool `arg:"--gui-debug" help:"open the GUI debugger"` GuiVerbose bool `arg:"--gui-verbose" help:"enable all logging"` } diff --git a/toolkit/gocui/add.go b/toolkit/gocui/add.go index 3020c83..97d65d3 100644 --- a/toolkit/gocui/add.go +++ b/toolkit/gocui/add.go @@ -1,88 +1,67 @@ package main import ( -// "github.com/awesome-gocui/gocui" "git.wit.org/wit/gui/toolkit" ) -// TODO: make these defaults in config struct definition -var fakeStartWidth int = me.DevelOffsetW +var fakeStartWidth int = me.FakeW var fakeStartHeight int = me.TabH + me.FramePadH -func (w *cuiWidget) setFake() { +// setup fake labels for non-visible things off screen +func (n *node) setFake() { + w := n.tk w.isFake = true - t := len(w.name) - // setup fake labels for non-visable things off screen - w.gocuiSize.w0 = fakeStartWidth - w.gocuiSize.h0 = fakeStartHeight - w.gocuiSize.w1 = w.gocuiSize.w0 + t + me.PadW - w.gocuiSize.h1 = w.gocuiSize.h0 + me.DefaultHeight + me.PadH + n.gocuiSetWH(fakeStartWidth, fakeStartHeight) - w.realWidth = w.gocuiSize.Width() + me.FramePadW - w.realHeight = w.gocuiSize.Height() + me.FramePadH - - fakeStartHeight += w.realHeight + fakeStartHeight += w.gocuiSize.Height() // TODO: use the actual max hight of the terminal window if (fakeStartHeight > 24) { - fakeStartHeight = me.TabH + me.FramePadH - fakeStartWidth += me.DevelOffsetW + fakeStartHeight = me.TabH + fakeStartWidth += me.FakeW } if (logInfo) { - w.showView() + n.showView() } } // set the widget start width & height -func (w *cuiWidget) addWidget() { - log(logInfo, "setStartWH() w.id =", w.id, "w.name", w.name) - switch w.widgetType { +func (n *node) addWidget() { + nw := n.tk + log(logInfo, "setStartWH() w.id =", n.WidgetId, "n.name", n.Name) + switch n.WidgetType { case toolkit.Root: - log(logInfo, "setStartWH() rootNode w.id =", w.id, "w.name", w.name) - w.setFake() + log(logInfo, "setStartWH() rootNode w.id =", n.WidgetId, "w.name", n.Name) + n.setFake() return case toolkit.Flag: - w.setFake() + n.setFake() return case toolkit.Window: - me.current = w - updateCurrentTabs() - setCurrentWindow(w) + nw.frame = false + redoWindows(0,0) return case toolkit.Tab: - // if this is the first tab, set it to the current one and stay here - if (me.current != nil) { - // there is already a current tab. just redraw the tabs - updateCurrentTabs() - return - } - setCurrentTab(w) return case toolkit.Box: - w.isFake = true - w.setFake() - w.startW = w.parent.startW - w.startH = w.parent.startH + nw.isFake = true + n.setFake() return case toolkit.Grid: - w.isFake = true - w.setFake() - w.startW = w.parent.startW - w.startH = w.parent.startH + nw.isFake = true + n.setFake() return case toolkit.Group: - w.startW = w.parent.startW + 4 - w.startH = w.parent.startH + me.DefaultHeight + me.FramePadH - - t := len(w.text) - w.gocuiSize.w1 = w.gocuiSize.w0 + t + me.FramePadW - w.gocuiSize.h1 = w.gocuiSize.h0 + me.DefaultHeight + me.FramePadH + nw.frame = false + return + case toolkit.Label: + nw.frame = false return default: - w.startW = w.parent.startW - w.startH = w.parent.startH - if w.IsCurrent() { - w.updateCurrent() + /* + if n.IsCurrent() { + n.updateCurrent() } + */ } - w.showWidgetPlacement(logInfo, "addWidget()") + n.showWidgetPlacement(logInfo, "addWidget()") } diff --git a/toolkit/gocui/checkbox.go b/toolkit/gocui/checkbox.go index 5568ee3..a1fe27c 100644 --- a/toolkit/gocui/checkbox.go +++ b/toolkit/gocui/checkbox.go @@ -5,28 +5,29 @@ import ( "git.wit.org/wit/gui/toolkit" ) -func (w *cuiWidget) setCheckbox(b bool) { - if (w.widgetType != toolkit.Checkbox) { +func (n *node) setCheckbox(b bool) { + w := n.tk + if (n.WidgetType != toolkit.Checkbox) { return } if (b) { - w.b = b - w.text = "X " + w.name + n.B = b + n.Text = "X " + n.Name } else { - w.b = b - w.text = " " + w.name + n.B = b + n.Text = " " + n.Name } - t := len(w.text) + 1 + t := len(n.Text) + 1 w.gocuiSize.w1 = w.gocuiSize.w0 + t - w.realWidth = w.gocuiSize.Width() + me.PadW - w.realHeight = w.gocuiSize.Height() + me.PadH +// w.realWidth = w.gocuiSize.Width() + me.PadW +// w.realHeight = w.gocuiSize.Height() + me.PadH - if w.frame { - w.realWidth += me.FramePadW - w.realHeight += me.FramePadH - } +// if w.frame { +// w.realWidth += me.FramePadW +// w.realHeight += me.FramePadH +// } - w.deleteView() - w.showView() + n.deleteView() + n.showView() } diff --git a/toolkit/gocui/click.go b/toolkit/gocui/click.go index 8802674..380b0a3 100644 --- a/toolkit/gocui/click.go +++ b/toolkit/gocui/click.go @@ -1,278 +1,275 @@ package main import ( - // "fmt" - // "errors" - "strconv" - "strings" - "github.com/awesome-gocui/gocui" "git.wit.org/wit/gui/toolkit" ) // set isCurrent = false everywhere -func UnsetCurrent(w *cuiWidget) { +func UnsetCurrent(n *node) { + w := n.tk w.isCurrent = false - for _, child := range w.children { + for _, child := range n.children { UnsetCurrent(child) } } -func updateCurrentTabs() { - me.rootNode.nextW = 0 - me.rootNode.nextH = 0 - me.rootNode.redoTabs(true) -} - // when adding a new widget, this will update the display // of the current widgets if that widget is supposed // to be in current display -func (w *cuiWidget) updateCurrent() { - if w.widgetType == toolkit.Tab { - if w.IsCurrent() { - setCurrentTab(w) +func (n *node) updateCurrent() { + log("updateCurrent()", n.Name) + if n.WidgetType == toolkit.Tab { + if n.IsCurrent() { + setCurrentTab(n) } return } - if w.widgetType == toolkit.Window { - if w.IsCurrent() { - setCurrentWindow(w) + if n.WidgetType == toolkit.Window { + if n.IsCurrent() { + // setCurrentWindow(n) } return } - if w.widgetType == toolkit.Root { + if n.WidgetType == toolkit.Root { return } - w.parent.updateCurrent() + n.parent.updateCurrent() } // shows the widgets in a window -func setCurrentWindow(w *cuiWidget) { - if w.widgetType != toolkit.Window { +func setCurrentWindow(n *node) { + if n.IsCurrent() { + return + } + w := n.tk + if n.WidgetType != toolkit.Window { return } UnsetCurrent(me.rootNode) - me.rootNode.hideWidgets() - // THIS IS THE BEGINING OF THE LAYOUT - me.rootNode.nextW = 0 - me.rootNode.nextH = 0 - - w.isCurrent = true - if w.hasTabs { + if n.hasTabs { // set isCurrent = true on the first tab - for _, child := range w.children { - child.isCurrent = true + for _, child := range n.children { + child.tk.isCurrent = true break } + } else { + w.isCurrent = true } - me.rootNode.redoTabs(true) - - w.placeWidgets() - w.showWidgets() } // shows the widgets in a tab -func setCurrentTab(w *cuiWidget) { - if w.widgetType != toolkit.Tab { +func setCurrentTab(n *node) { + w := n.tk + if n.WidgetType != toolkit.Tab { return } - me.current = w UnsetCurrent(me.rootNode) - me.rootNode.hideWidgets() w.isCurrent = true - w.parent.isCurrent = true - updateCurrentTabs() - w.placeWidgets() - w.showWidgets() + p := n.parent.tk + p.isCurrent = true + log("setCurrent()", n.Name) } -func (w *cuiWidget) doWidgetClick() { - switch w.widgetType { +func (n *node) doWidgetClick() { + switch n.WidgetType { case toolkit.Root: // THIS IS THE BEGINING OF THE LAYOUT - me.rootNode.nextW = 0 - me.rootNode.nextH = 0 - me.rootNode.redoTabs(true) + log("doWidgetClick()", n.Name) + redoWindows(0,0) case toolkit.Flag: // me.rootNode.redoColor(true) me.rootNode.dumpTree(true) case toolkit.Window: - setCurrentWindow(w) - case toolkit.Tab: - setCurrentTab(w) - case toolkit.Group: - w.placeWidgets() - w.toggleTree() - case toolkit.Checkbox: - if (w.b) { - w.setCheckbox(false) - } else { - w.setCheckbox(true) - } - w.doUserEvent() - case toolkit.Grid: me.rootNode.hideWidgets() - w.placeGrid() - w.showWidgets() + n.redoTabs(me.TabW, me.TabH) + if ! n.hasTabs { + setCurrentWindow(n) + n.placeWidgets(me.RawW, me.RawH) + n.showWidgets() + } + case toolkit.Tab: + setCurrentTab(n) + n.placeWidgets(me.RawW, me.RawH) + n.showWidgets() + case toolkit.Group: + // n.placeWidgets(p.tk.startH, newH) + n.toggleTree() + case toolkit.Checkbox: + if (n.B) { + n.setCheckbox(false) + } else { + n.setCheckbox(true) + } + n.doUserEvent() + case toolkit.Grid: + n.placeGrid(n.tk.size.w0, n.tk.size.h0) + n.showWidgets() case toolkit.Box: // w.showWidgetPlacement(logNow, "drawTree()") - if (w.horizontal) { - log("BOX IS HORIZONTAL", w.name) + if (n.horizontal) { + log("BOX IS HORIZONTAL", n.Name) } else { - log("BOX IS VERTICAL", w.name) + log("BOX IS VERTICAL", n.Name) } - w.placeWidgets() - w.toggleTree() + // n.placeWidgets() + n.toggleTree() case toolkit.Button: - w.doUserEvent() + n.doUserEvent() default: } } -// this passes the user event back from the plugin -func (w *cuiWidget) doUserEvent() { - if (me.callback == nil) { - log(logError, "doUserEvent() no callback channel was configured") - return - } - var a toolkit.Action - a.WidgetId = w.id - a.Name = w.name - a.Text = w.text - a.B = w.b - a.ActionType = toolkit.User - me.callback <- a - log(logNow, "END: sent a button click callback()") -} - var toggle bool = true -func (w *cuiWidget) toggleTree() { +func (n *node) toggleTree() { if (toggle) { - w.drawTree(toggle) + n.drawTree(toggle) toggle = false } else { - w.hideWidgets() + n.hideWidgets() toggle = true } } // display the widgets in the binary tree -func (w *cuiWidget) drawTree(draw bool) { +func (n *node) drawTree(draw bool) { + w := n.tk if (w == nil) { return } - w.showWidgetPlacement(logNow, "drawTree()") + n.showWidgetPlacement(logNow, "drawTree()") if (draw) { // w.textResize() - w.showView() + n.showView() } else { - w.deleteView() + n.deleteView() } - for _, child := range w.children { + for _, child := range n.children { child.drawTree(draw) } } func click(g *gocui.Gui, v *gocui.View) error { // var l string - var err error + // var err error - log(logNow, "click() START", v.Name()) + log(logVerbose, "click() START", v.Name()) + // n := me.rootNode.findWidgetName(v.Name()) + n := findUnderMouse() + if (n != nil) { + log(logNow, "click() Found widget =", n.WidgetId, n.Name, ",", n.Text) + n.doWidgetClick() + } else { + log(logNow, "click() could not find node name =", v.Name()) + } + /* i, err := strconv.Atoi(v.Name()) if (err != nil) { - log(logNow, "click() Can't find widget. error =", err) + log(logError, "click() Can't find widget. error =", err) } else { - log(logNow, "click() ok v.Name() =", v.Name()) - w := findWidget(i, me.rootNode) - if (w == nil) { + log(logVerbose, "click() ok v.Name() =", v.Name()) + n := me.rootNode.findWidgetId(i) + if (n == nil) { log(logError, "click() CANT FIND VIEW in binary tree. v.Name =", v.Name()) return nil } - log(logNow, "click() Found widget =", w.id, w.name, ",", w.text) - w.doWidgetClick() + log(logNow, "click() Found widget =", n.WidgetId, n.Name, ",", n.Text) + n.doWidgetClick() return nil } + */ if _, err := g.SetCurrentView(v.Name()); err != nil { return err } - /* - _, cy := v.Cursor() - if l, err = v.Line(cy); err != nil { - l = "" - } - - maxX, maxY := g.Size() - if v, err := g.SetView("msg", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2, 0); err == nil || errors.Is(err, gocui.ErrUnknownView) { - v.Clear() - v.SelBgColor = gocui.ColorCyan - v.SelFgColor = gocui.ColorBlack - fmt.Fprintln(v, l) - } - */ - - // this seems to delete the button(?) - // g.SetViewOnBottom(v.Name()) - log(logNow, "click() END") + log(logVerbose, "click() END") return nil } - -// display the widgets in the binary tree - -func ctrlDown(g *gocui.Gui, v *gocui.View) error { - var found *cuiWidget - var widgets []*cuiWidget - var f func (widget *cuiWidget) - w, h := g.MousePosition() +func findUnderMouse() *node { + var found *node + var widgets []*node + var f func (n *node) + w, h := me.baseGui.MousePosition() // find buttons that are below where the mouse button click - f = func(widget *cuiWidget) { - // if ((widget.logicalSize.w0 < w) && (w < widget.logicalSize.w1)) { - if ((widget.gocuiSize.w0 <= w) && (w <= widget.gocuiSize.w1) && - (widget.gocuiSize.h0 <= h) && (h <= widget.gocuiSize.h1)) { - widgets = append(widgets, widget) - found = widget + f = func(n *node) { + widget := n.tk + // ignore widgets that are not visible + if n.Visible() { + if ((widget.gocuiSize.w0 <= w) && (w <= widget.gocuiSize.w1) && + (widget.gocuiSize.h0 <= h) && (h <= widget.gocuiSize.h1)) { + widgets = append(widgets, n) + found = n + } } - for _, child := range widget.children { + for _, child := range n.children { f(child) } } f(me.rootNode) - var t string - for _, widget := range widgets { - // log(logNow, "ctrlDown() FOUND widget", widget.id, widget.name) - t += widget.cuiName + " " + widget.name + "\n" - widget.showWidgetPlacement(logNow, "ctrlDown() FOUND") + // widgets has everything that matches + // TODO: pop up menu with a list of them + for _, n := range widgets { + //log(logNow, "ctrlDown() FOUND widget", widget.id, widget.name) + n.showWidgetPlacement(logNow, "ctrlDown() FOUND") } - t = strings.TrimSpace(t) + return found +} + +// find the widget under the mouse click +func ctrlDown(g *gocui.Gui, v *gocui.View) error { + var found *node + // var widgets []*node + // var f func (n *node) + found = findUnderMouse() + /* + w, h := g.MousePosition() + + // find buttons that are below where the mouse button click + f = func(n *node) { + widget := n.tk + // ignore widgets that are not visible + if n.Visible() { + if ((widget.gocuiSize.w0 <= w) && (w <= widget.gocuiSize.w1) && + (widget.gocuiSize.h0 <= h) && (h <= widget.gocuiSize.h1)) { + widgets = append(widgets, n) + found = n + } + } + + for _, child := range n.children { + f(child) + } + } + f(me.rootNode) + */ if (me.ctrlDown == nil) { setupCtrlDownWidget() - me.ctrlDown.text = "ctrlDown" // t - me.ctrlDown.cuiName = "ctrlDown" - me.ctrlDown.parent = me.rootNode + me.ctrlDown.Text = found.Name + me.ctrlDown.tk.cuiName = "ctrlDown" + // me.ctrlDown.parent = me.rootNode } + cd := me.ctrlDown.tk if (found == nil) { found = me.rootNode } - // ? TODO: found.setRealSize() - me.ctrlDown.gocuiSize.w0 = found.startW - me.ctrlDown.gocuiSize.h0 = found.startH - me.ctrlDown.gocuiSize.w1 = me.ctrlDown.gocuiSize.w0 + found.realWidth - me.ctrlDown.gocuiSize.h1 = me.ctrlDown.gocuiSize.h0 + found.realHeight - if (me.ctrlDown.v == nil) { - me.ctrlDown.text = found.text - me.ctrlDown.showWidgetPlacement(logNow, "ctrlDown:") - me.ctrlDown.showView() - } else { + me.ctrlDown.Text = found.Name + newR := found.realGocuiSize() + cd.gocuiSize.w0 = newR.w0 + cd.gocuiSize.h0 = newR.h0 + cd.gocuiSize.w1 = newR.w1 + cd.gocuiSize.h1 = newR.h1 + if me.ctrlDown.Visible() { me.ctrlDown.deleteView() + } else { + me.ctrlDown.updateView() } - - log(logNow, "ctrlDown()", w, h) + me.ctrlDown.showWidgetPlacement(logNow, "ctrlDown:") return nil } diff --git a/toolkit/gocui/color.go b/toolkit/gocui/color.go index c1216f0..5dbed05 100644 --- a/toolkit/gocui/color.go +++ b/toolkit/gocui/color.go @@ -8,8 +8,9 @@ import ( // ColorBlack ColorRed ColorGreen ColorYellow ColorBlue ColorMagenta ColorCyan ColorWhite // gocui.GetColor("#FFAA55") // Dark Purple -func (w *cuiWidget) setDefaultWidgetColor() { - log(logInfo, "setDefaultWidgetColor() on", w.widgetType, w.name) +func (n *node) setDefaultWidgetColor() { + w := n.tk + log(logInfo, "setDefaultWidgetColor() on", n.WidgetType, n.Name) v, _ := me.baseGui.View(w.cuiName) if (v == nil) { log(logError, "setDefaultWidgetColor() failed on view == nil") @@ -24,7 +25,7 @@ func (w *cuiWidget) setDefaultWidgetColor() { // v.BgColor = gocui.GetColor("#55AAFF") // super light grey // v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow - switch w.widgetType { + switch n.WidgetType { case toolkit.Root: v.FrameColor = gocui.ColorRed v.BgColor = gocui.GetColor("#B0E0E6") // w3c 'powerder blue' @@ -84,7 +85,8 @@ func (w *cuiWidget) SetColor(c string) { } } -func (w *cuiWidget) setDefaultHighlight() { +func (n *node) setDefaultHighlight() { + w := n.tk if (w.v == nil) { log(logError, "SetColor() failed on view == nil") return @@ -100,16 +102,17 @@ func randColor() gocui.Attribute { return gocui.GetColor(colors[i]) } -func (w *cuiWidget) redoColor(draw bool) { +func (n *node) redoColor(draw bool) { + w := n.tk if (w == nil) { return } sleep(.05) - w.setDefaultHighlight() - // w.setDefaultWidgetColor() + n.setDefaultHighlight() + n.setDefaultWidgetColor() - for _, child := range w.children { + for _, child := range n.children { child.redoColor(draw) } } diff --git a/toolkit/gocui/common.go b/toolkit/gocui/common.go index 325a556..05de64b 100644 --- a/toolkit/gocui/common.go +++ b/toolkit/gocui/common.go @@ -3,118 +3,182 @@ package main import ( "strconv" "git.wit.org/wit/gui/toolkit" -// "github.com/awesome-gocui/gocui" ) -func makeWidget(a *toolkit.Action) *cuiWidget { +func makeWidget(n *node) *cuiWidget { var w *cuiWidget w = new(cuiWidget) + // Set(w, "default") - w.name = a.Name - w.text = a.Text - w.b = a.B - w.i = a.I - w.s = a.S - - w.X = a.X - w.Y = a.Y - - t := len(w.text) - w.gocuiSize.w1 = w.gocuiSize.w0 + t + me.PadW - w.gocuiSize.h1 = w.gocuiSize.h0 + me.DefaultHeight + me.PadH - - w.realWidth = w.gocuiSize.Width() - w.realHeight = w.gocuiSize.Height() - - // set the gocui view.Frame = true by default w.frame = true - if (w.frame) { - w.realHeight += me.FramePadH - w.gocuiSize.height += me.FramePadH - } - w.widgetType = a.WidgetType - w.id = a.WidgetId // set the name used by gocui to the id - w.cuiName = strconv.Itoa(w.id) + w.cuiName = strconv.Itoa(n.WidgetId) - if w.widgetType == toolkit.Root { - log(logInfo, "setupWidget() FOUND ROOT w.id =", w.id, "w.parent", w.parent, "ParentId =", a.ParentId) - w.id = 0 - me.rootNode = w + if n.WidgetType == toolkit.Root { + log(logInfo, "setupWidget() FOUND ROOT w.id =", n.WidgetId) + n.WidgetId = 0 + me.rootNode = n return w } - w.parent = findWidget(a.ParentId, me.rootNode) - log(logInfo, "setupWidget() w.id =", w.id, "w.parent", w.parent, "ParentId =", a.ParentId) - if (w.parent == nil) { - log(logError, "setupWidget() ERROR: PARENT = NIL w.id =", w.id, "w.parent", w.parent, "ParentId =", a.ParentId) - // just use the rootNode (hopefully it's not nil) - w.parent = me.rootNode - // return w - } - - // add this widget as a child for the parent - w.parent.Append(w) - - if (a.WidgetType == toolkit.Box) { - if (a.B) { - w.horizontal = true + if (n.WidgetType == toolkit.Box) { + if (n.B) { + n.horizontal = true } else { - w.horizontal = false + n.horizontal = false } } - if (a.WidgetType == toolkit.Grid) { + + if (n.WidgetType == toolkit.Grid) { w.widths = make(map[int]int) // how tall each row in the grid is w.heights = make(map[int]int) // how wide each column in the grid is } + return w } func setupCtrlDownWidget() { - var w *cuiWidget - w = new(cuiWidget) + a := new(toolkit.Action) + a.Name = "ctrlDown" + a.WidgetType = toolkit.Dialog + a.WidgetId = -1 + a.ParentId = 0 + n := addNode(a) - w.name = "ctrlDown" - - w.widgetType = toolkit.Flag - w.id = -1 - me.ctrlDown = w - // me.rootNode.Append(w) + me.ctrlDown = n } -func (w *cuiWidget) deleteView() { +func (n *node) deleteView() { + w := n.tk if (w.v != nil) { - me.baseGui.DeleteView(w.cuiName) + w.v.Visible = false + return } + // make sure the view isn't really there + me.baseGui.DeleteView(w.cuiName) w.v = nil } -func (n *cuiWidget) Append(child *cuiWidget) { - n.children = append(n.children, child) - // child.parent = n -} - -// find widget by number -func findWidget(i int, w *cuiWidget) (*cuiWidget) { - if (w == nil) { - log(logVerbose, "findWidget() Trying to find i =", i, "currently checking against w.id = nil") +// searches the binary tree for a WidgetId +func (n *node) findWidgetId(id int) *node { + if (n == nil) { return nil } - log(logVerbose, "findWidget() Trying to find i =", i, "currently checking against w.id =", w.id) - if (w.id == i) { - log(logInfo, "findWidget() FOUND w.id ==", i, w.widgetType, w.name) - return w + if n.WidgetId == id { + return n } - for _, child := range w.children { - newW := findWidget(i, child) - log(logVerbose, "findWidget() Trying to find i =", i, "currently checking against child.id =", child.id) - if (newW != nil) { - return newW + for _, child := range n.children { + newN := child.findWidgetId(id) + if (newN != nil) { + return newN } } return nil } +// searches the binary tree for a WidgetId +func (n *node) findWidgetName(name string) *node { + if (n == nil) { + return nil + } + + if n.tk.cuiName == name { + return n + } + + for _, child := range n.children { + newN := child.findWidgetName(name) + if (newN != nil) { + return newN + } + } + return nil +} + +func addNode(a *toolkit.Action) *node { + n := new(node) + n.WidgetType = a.WidgetType + n.WidgetId = a.WidgetId + n.ParentId = a.ParentId + + // copy the data from the action message + n.Name = a.Name + n.Text = a.Text + n.I = a.I + n.S = a.S + n.B = a.B + + n.X = a.X + n.Y = a.Y + + n.W = a.W + n.H = a.H + n.AtW = a.AtW + n.AtH = a.AtH + + // store the internal toolkit information + n.tk = makeWidget(n) + + if (a.WidgetType == toolkit.Root) { + log(logInfo, "addNode() Root") + return n + } + + if (me.rootNode.findWidgetId(a.WidgetId) != nil) { + log(logError, "addNode() WidgetId already exists", a.WidgetId) + return me.rootNode.findWidgetId(a.WidgetId) + } + + // add this new widget on the binary tree + n.parent = me.rootNode.findWidgetId(a.ParentId) + if n.parent != nil { + n.parent.children = append(n.parent.children, n) + //w := n.tk + //w.parent = n.parent.tk + //w.parent.children = append(w.parent.children, w) + } + return n +} + +func (n *node) IsCurrent() bool { + w := n.tk + if (n.WidgetType == toolkit.Tab) { + return w.isCurrent + } + if (n.WidgetType == toolkit.Window) { + return w.isCurrent + } + if (n.WidgetType == toolkit.Root) { + return false + } + return n.parent.IsCurrent() +} + +func (n *node) Visible() bool { + if (n == nil) { + return false + } + if (n.tk == nil) { + return false + } + if (n.tk.v == nil) { + return false + } + return n.tk.v.Visible +} + +func (n *node) SetVisible(b bool) { + if (n == nil) { + return + } + if (n.tk == nil) { + return + } + if (n.tk.v == nil) { + return + } + n.tk.v.Visible = b +} diff --git a/toolkit/gocui/debug.go b/toolkit/gocui/debug.go index e3f3586..3ea31b0 100644 --- a/toolkit/gocui/debug.go +++ b/toolkit/gocui/debug.go @@ -2,37 +2,72 @@ package main import ( "fmt" + "git.wit.org/wit/gui/toolkit" ) -func (w *cuiWidget) dumpTree(draw bool) { +func (n *node) dumpTree(draw bool) { + w := n.tk if (w == nil) { return } - w.showWidgetPlacement(logNow, "Tree:") + n.showWidgetPlacement(logNow, "Tree:") - for _, child := range w.children { + for _, child := range n.children { child.dumpTree(draw) } } -func (w *cuiWidget) showWidgetPlacement(b bool, s string) { - var s1 string - var pId int - if (w == nil) { +func (n *node) showWidgetPlacement(b bool, s string) { + if (n == nil) { log(logError, "WTF w == nil") return } - if (w.parent == nil) { - log(logVerbose, "showWidgetPlacement() parent == nil", w.id, w.cuiName) + w := n.tk + + var s1 string + var pId int + if (n.parent == nil) { + log(logVerbose, "showWidgetPlacement() parent == nil", n.WidgetId, w.cuiName) pId = 0 } else { - pId = w.parent.id + pId = n.parent.WidgetId } - s1 = fmt.Sprintf("(wId,pId)=(%2d,%2d) ", w.id, pId) - s1 += fmt.Sprintf("s/n (%2d,%2d) (%2d,%2d) ", w.startW, w.startH, w.nextW, w.nextH) - s1 += fmt.Sprintf("size (%2d,%2d) ", w.realWidth, w.realHeight) - s1 += fmt.Sprintf("gocui=(%2d,%2d)(%2d,%2d,%2d,%2d)", - w.gocuiSize.Width(), w.gocuiSize.Height(), - w.gocuiSize.w0, w.gocuiSize.h0, w.gocuiSize.w1, w.gocuiSize.h1) - log(b, s1, s, w.widgetType, ",", w.name) // , "text=", w.text) + s1 = fmt.Sprintf("(wId,pId)=(%2d,%2d) ", n.WidgetId, pId) + s1 += fmt.Sprintf("size=(%2d,%2d)(%2d,%2d,%2d,%2d)", + w.size.Width(), w.size.Height(), + w.size.w0, w.size.h0, w.size.w1, w.size.h1) + if n.Visible() { + s1 += fmt.Sprintf("gocui=(%2d,%2d)(%2d,%2d,%2d,%2d)", + w.gocuiSize.Width(), w.gocuiSize.Height(), + w.gocuiSize.w0, w.gocuiSize.h0, w.gocuiSize.w1, w.gocuiSize.h1) + } + if (n.parent != nil) { + if (n.parent.WidgetType == toolkit.Grid) { + s1 += fmt.Sprintf("At(%2d,%2d) ", n.AtW, n.AtH) + } + } + log(b, s1, s, n.WidgetType, ",", n.Name) // , "text=", w.text) +} + +func (n *node) dumpWidget(pad string) { + log(true, "node:", pad, n.WidgetId, "At(", n.AtW, n.AtH, ") ,", n.WidgetType, ",", n.Name) +} + +func (n *node) listWidgets() { + if (n == nil) { + return + } + + var pad string + for i := 0; i < me.depth; i++ { + pad = pad + " " + } + n.dumpWidget(pad) + + for _, child := range n.children { + me.depth += 1 + child.listWidgets() + me.depth -= 1 + } + return } diff --git a/toolkit/gocui/globalDown.go b/toolkit/gocui/globalDown.go deleted file mode 100644 index e9d6b13..0000000 --- a/toolkit/gocui/globalDown.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "errors" - "fmt" - - "github.com/awesome-gocui/gocui" -) - -func globalDown(g *gocui.Gui, v *gocui.View) error { - mx, my := g.MousePosition() - if vx0, vy0, vx1, vy1, err := g.ViewPosition("msg"); err == nil { - if mx >= vx0 && mx <= vx1 && my >= vy0 && my <= vy1 { - return msgDown(g, v) - } - } - globalMouseDown = true - maxX, _ := g.Size() - msg := fmt.Sprintf("Mouse really down at: %d,%d", mx, my) + "foo\n" + "bar\n" - x := mx - len(msg)/2 - if x < 0 { - x = 0 - } else if x+len(msg)+1 > maxX-1 { - x = maxX - 1 - len(msg) - 1 - } - if v, err := g.SetView("globalDown", x, my-1, x+len(msg)+1, my+1, 0); err != nil { - if !errors.Is(err, gocui.ErrUnknownView) { - return err - } - v.WriteString(msg) - } - return nil -} diff --git a/toolkit/gocui/gocui.go b/toolkit/gocui/gocui.go new file mode 100644 index 0000000..85e6ea5 --- /dev/null +++ b/toolkit/gocui/gocui.go @@ -0,0 +1,100 @@ +// Copyright 2014 The gocui Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "errors" + "github.com/awesome-gocui/gocui" +) + +// This initializes the gocui package +// it runs SetManagerFunc which passes every input +// event (keyboard, mouse, etc) to the function "gocuiEvent()" +func gocuiMain() { + g, err := gocui.NewGui(gocui.OutputNormal, true) + if err != nil { + panic(err) + } + defer g.Close() + + me.baseGui = g + + g.Cursor = true + g.Mouse = true + + // this sets the function that is run on every event. For example: + // When you click the mouse, move the mouse, or press a key on the keyboard + // This is equivalent to xev or similar to cat /dev/input on linux + g.SetManagerFunc(gocuiEvent) + + if err := defaultKeybindings(g); err != nil { + panic(err) + } + + if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) { + panic(err) + } +} + +// Thanks to the gocui developers -- your package kicks ass +// This function is called on every event. It is a callback function from the gocui package +// which has an excellent implementation. While gocui handles things like text highlighting +// and the layout of the text areas -- also things like handling SIGWINCH and lots of really +// complicated console handling, it sends events here in a clean way. +// This is equivalent to the linux command xev (apt install x11-utils) +func gocuiEvent(g *gocui.Gui) error { + maxX, maxY := g.Size() + mx, my := g.MousePosition() + log(logVerbose, "handleEvent() START", maxX, maxY, mx, my, msgMouseDown) + if _, err := g.View("msg"); msgMouseDown && err == nil { + moveMsg(g) + } + if widgetView, _ := g.View("msg"); widgetView == nil { + log(logNow, "handleEvent() create output widget now", maxX, maxY, mx, my) + makeOutputWidget(g, "this is a create before a mouse click") + if (me.logStdout != nil) { + // setOutput(me.logStdout) + } + } else { + log(logInfo, "output widget already exists", maxX, maxY, mx, my) + } + mouseMove(g) + log(logVerbose, "handleEvent() END ", maxX, maxY, mx, my, msgMouseDown) + return nil +} + +func dragOutputWindow() { +} + +// turns off the frame on the global window +func setFrame(b bool) { + // TODO: figure out what this might be useful for + // what is this do? I made it just 2 lines for now. Is this useful for something? + v := SetView("global", 15, 5, 80, 8, 10) + if (v == nil) { + log(logError, "setFrame() global failed") + } + v.Frame = b +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} + +func SetView(name string, x0, y0, x1, y1 int, overlaps byte) *gocui.View { + if (me.baseGui == nil) { + log(logError, "SetView() ERROR: me.baseGui == nil") + return nil + } + + v, err := me.baseGui.SetView(name, x0, y0, x1, y1, overlaps) + if err != nil { + if !errors.Is(err, gocui.ErrUnknownView) { + log(logError, "SetView() global failed on name =", name) + } + return nil + } + return v +} diff --git a/toolkit/gocui/help.go b/toolkit/gocui/help.go index 74143a2..66719bb 100644 --- a/toolkit/gocui/help.go +++ b/toolkit/gocui/help.go @@ -12,25 +12,23 @@ import ( "github.com/awesome-gocui/gocui" ) -func addHelp() { - me.baseGui.SetManagerFunc(helplayout) -} - var helpText []string = []string{"KEYBINDINGS", "", "d: show/hide debugging", - "h: hide widgets", - "s: show all widgets", + "s/h: show/hide all widgets", + "L: list all widgets", "q: quit()", "p: panic()", "o: show Stdout", "l: log to /tmp/witgui.log", - "Ctrl-D: Enable Debugging", + "Ctrl-D: Toggle Debugging", + "Ctrl-V: Toggle Verbose Debugging", "Ctrl-C: Exit", "", } -func helplayout(g *gocui.Gui) error { +func helplayout() error { + g := me.baseGui var err error maxX, _ := g.Size() diff --git a/toolkit/gocui/keybindings.go b/toolkit/gocui/keybindings.go index cf948fe..dbe1fe2 100644 --- a/toolkit/gocui/keybindings.go +++ b/toolkit/gocui/keybindings.go @@ -5,6 +5,7 @@ package main import ( + "os" "github.com/awesome-gocui/gocui" "git.wit.org/wit/gui/toolkit" ) @@ -21,7 +22,7 @@ func defaultKeybindings(g *gocui.Gui) error { if err := g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp); err != nil { return err } - if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, globalDown); err != nil { + if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown); err != nil { return err } if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModMouseCtrl, ctrlDown); err != nil { @@ -47,7 +48,7 @@ func addDebugKeys(g *gocui.Gui) { func(g *gocui.Gui, v *gocui.View) error { log(logNow, "gocui.SetKeyBinding() dumpTree() START") // me.rootNode.dumpTree(true) - fakeStartWidth = me.DevelOffsetW + fakeStartWidth = me.FakeW fakeStartHeight = me.TabH + me.FramePadH if (showDebug) { me.rootNode.showFake() @@ -59,6 +60,12 @@ func addDebugKeys(g *gocui.Gui) { return nil }) + // display the help menu + g.SetKeybinding("", '?', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + helplayout() + return nil + }) // hide all widgets g.SetKeybinding("", 'h', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { @@ -73,6 +80,26 @@ func addDebugKeys(g *gocui.Gui) { return nil }) + // list all widgets + g.SetKeybinding("", 'L', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + me.rootNode.listWidgets() + return nil + }) + + // log to output window + g.SetKeybinding("", 'o', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + if me.logStdout.Visible() { + me.logStdout.SetVisible(false) + setOutput(os.Stdout) + } else { + me.logStdout.SetVisible(true) + setOutput(me.logStdout.tk) + } + return nil + }) + // exit g.SetKeybinding("", 'q', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { @@ -86,9 +113,28 @@ func addDebugKeys(g *gocui.Gui) { }) g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { - var a toolkit.Action - a.ActionType = toolkit.EnableDebug - me.callback <- a + if (showDebug) { + var a toolkit.Action + a.B = true + a.ActionType = toolkit.EnableDebug + me.callback <- a + logInfo = true + logVerbose = true + } else { + logInfo = false + logVerbose = false + } + return nil + }) + g.SetKeybinding("", gocui.KeyCtrlV, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + if (logVerbose) { + logInfo = false + logVerbose = false + } else { + logInfo = true + logVerbose = true + } return nil }) diff --git a/toolkit/gocui/main.go b/toolkit/gocui/main.go index 20e85ac..6214fcb 100644 --- a/toolkit/gocui/main.go +++ b/toolkit/gocui/main.go @@ -96,7 +96,7 @@ func main() { ferr, _ := os.OpenFile("/tmp/witgui.err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) os.Stderr = ferr - MouseMain() + gocuiMain() log(true, "MouseMain() closed") standardExit() diff --git a/toolkit/gocui/mouse.go b/toolkit/gocui/mouse.go index 2cba5c6..dbe2c6d 100644 --- a/toolkit/gocui/mouse.go +++ b/toolkit/gocui/mouse.go @@ -6,68 +6,13 @@ package main import ( "errors" - + "fmt" "github.com/awesome-gocui/gocui" ) -func MouseMain() { - g, err := gocui.NewGui(gocui.OutputNormal, true) - if err != nil { - panic(err) - } - defer g.Close() - - me.baseGui = g - - g.Cursor = true - g.Mouse = true - - g.SetManagerFunc(layout) - - if err := defaultKeybindings(g); err != nil { - panic(err) - } - - if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) { - panic(err) - } -} - -func layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - mx, my := g.MousePosition() - if _, err := g.View("msg"); msgMouseDown && err == nil { - moveMsg(g) - } - // TODO: figure out what this might be useful for - // what is this do? I made it just 2 lines for now. Is this useful for something? - if v, err := g.SetView("global", 15, 5, maxX, 8, 10); err != nil { - if !errors.Is(err, gocui.ErrUnknownView) { - log("global failed", maxX, maxY) - return err - } - v.Frame = false - } - helplayout(g) - if widgetView, _ := g.View("msg"); widgetView == nil { - log(logInfo, "create output widget now", maxX, maxY, mx, my) - makeOutputWidget(g, "this is a create before a mouse click") - if (me.logStdout != nil) { - setOutput(me.logStdout) - } - } else { - log(logInfo, "output widget already exists", maxX, maxY, mx, my) - } - updateHighlightedView(g) - log(logInfo, "layout() END", maxX, maxY, mx, my) - return nil -} - -func quit(g *gocui.Gui, v *gocui.View) error { - return gocui.ErrQuit -} - -func updateHighlightedView(g *gocui.Gui) { +// this function uses the mouse position to highlight & unhighlight things +// this is run every time the user moves the mouse over the terminal window +func mouseMove(g *gocui.Gui) { mx, my := g.MousePosition() for _, view := range g.Views() { view.Highlight = false @@ -102,3 +47,28 @@ func mouseUp(g *gocui.Gui, v *gocui.View) error { } return nil } + +func mouseDown(g *gocui.Gui, v *gocui.View) error { + mx, my := g.MousePosition() + if vx0, vy0, vx1, vy1, err := g.ViewPosition("msg"); err == nil { + if mx >= vx0 && mx <= vx1 && my >= vy0 && my <= vy1 { + return msgDown(g, v) + } + } + globalMouseDown = true + maxX, _ := g.Size() + msg := fmt.Sprintf("Mouse really down at: %d,%d", mx, my) + "foo\n" + "bar\n" + x := mx - len(msg)/2 + if x < 0 { + x = 0 + } else if x+len(msg)+1 > maxX-1 { + x = maxX - 1 - len(msg) - 1 + } + if v, err := g.SetView("globalDown", x, my-1, x+len(msg)+1, my+1, 0); err != nil { + if !errors.Is(err, gocui.ErrUnknownView) { + return err + } + v.WriteString(msg) + } + return nil +} diff --git a/toolkit/gocui/place.go b/toolkit/gocui/place.go index 0919308..d02624e 100644 --- a/toolkit/gocui/place.go +++ b/toolkit/gocui/place.go @@ -1,263 +1,196 @@ package main import ( - "fmt" -// "github.com/awesome-gocui/gocui" + "strings" "git.wit.org/wit/gui/toolkit" ) -func (w *cuiWidget) placeBox() { - if (w.widgetType != toolkit.Box) { +func (n *node) placeBox(startW int, startH int) { + if (n.WidgetType != toolkit.Box) { return } - w.startW = w.parent.nextW - w.startH = w.parent.nextH - w.nextW = w.parent.nextW - w.nextH = w.parent.nextH - w.realWidth = 0 - w.realHeight = 0 + n.tk.size.w0 = startW + n.tk.size.h0 = startH + n.showWidgetPlacement(logNow, "boxS()") - var maxW int - var maxH int - for _, child := range w.children { - w.showWidgetPlacement(logNow, "boxS()") - child.placeWidgets() - if (w.horizontal) { - log(logVerbose, "BOX IS HORIZONTAL") + newW := startW + newH := startH + for _, child := range n.children { + child.placeWidgets(newW, newH) + // n.showWidgetPlacement(logNow, "boxS()") + // w,h := child.logicalSize() + newR := child.realGocuiSize() + w := newR.w1 - newR.w0 + h := newR.h1 - newR.h0 + if (n.horizontal) { + log(logNow, "BOX IS HORIZONTAL", n.Name, "newWH()", newW, newH, "child()", w, h, child.Name) // expand based on the child width - w.nextW += child.realWidth - w.realWidth += child.realWidth + newW += w } else { - log(logVerbose, "BOX IS VERTICAL") + log(logNow, "BOX IS VERTICAL ", n.Name, "newWH()", newW, newH, "child()", w, h, child.Name) // expand based on the child height - w.nextH += child.realHeight - w.realHeight += child.realHeight - } - if (maxW < child.realWidth) { - maxW = child.realWidth - } - if (maxH < child.realHeight) { - maxH = child.realHeight + newH += h } } - if (w.horizontal) { - w.realHeight = maxH - } else { - w.realWidth = maxW - } - w.showWidgetPlacement(logNow, "boxE()") + // w,h := n.logicalSize() + newR := n.realGocuiSize() + w := newR.w1 - newR.w0 + h := newR.h1 - newR.h0 + + n.tk.size.w1 = n.tk.size.w0 + w + n.tk.size.h1 = n.tk.size.h0 + h + n.showWidgetPlacement(logNow, "boxE()") } -func (w *cuiWidget) placeWidgets() { - if (w == nil) { +func (n *node) placeWidgets(startW int, startH int) { + if (n == nil) { return } if (me.rootNode == nil) { return } - p := w.parent - if (p == nil) { - log(logInfo, "place()", w.id, "parent == nil") - return - } - switch w.widgetType { + switch n.WidgetType { case toolkit.Window: - for _, child := range w.children { - w.startW = me.RawW - w.startH = me.RawH - w.nextW = me.RawW - w.nextH = me.RawH - w.showWidgetPlacement(logNow, "place()") - child.placeWidgets() - if (w.realWidth < child.realWidth) { - w.realWidth = child.realWidth - } - if (w.realHeight < child.realHeight) { - w.realHeight = child.realHeight - } - w.showWidgetPlacement(logNow, "place()") + for _, child := range n.children { + child.placeWidgets(me.RawW, me.RawH) + return } case toolkit.Tab: - for _, child := range w.children { - w.startW = me.RawW - w.startH = me.RawH - w.nextW = me.RawW - w.nextH = me.RawH - w.showWidgetPlacement(logNow, "place()") - child.placeWidgets() - if (w.realWidth < child.realWidth) { - w.realWidth = child.realWidth - } - if (w.realHeight < child.realHeight) { - w.realHeight = child.realHeight - } - w.showWidgetPlacement(logNow, "place()") + for _, child := range n.children { + child.placeWidgets(me.RawW, me.RawH) + return } case toolkit.Grid: - w.showWidgetPlacement(logNow, "placeGrid() START") - w.placeGrid() - w.showWidgetPlacement(logNow, "placeGrid() END") + n.placeGrid(startW, startH) case toolkit.Box: - w.showWidgetPlacement(logNow, "placeBox() START") - w.placeBox() - w.showWidgetPlacement(logNow, "placeBox() END") + n.placeBox(startW, startH) case toolkit.Group: // move the group to the parent's next location - w.startW = p.nextW - w.startH = p.nextH - w.nextW = p.nextW - w.nextH = p.nextH - w.moveTo(p.nextW, p.nextH) + n.gocuiSetWH(startW, startH) + n.showWidgetPlacement(logNow, "group()") - // initialize the real width to just the group gocui view - w.realWidth = w.gocuiSize.Width() + me.FramePadW - w.realHeight = w.gocuiSize.Height() + me.FramePadH - - // indent the widgets for a group - w.nextW = p.nextW + me.GroupPadW - w.nextH = p.nextH + w.realHeight - w.showWidgetPlacement(logNow, "place()") - - // mow move all the children aka: run place() on them - var maxW int - for _, child := range w.children { - child.showWidgetPlacement(logNow, "place()") - child.placeWidgets() - child.showWidgetPlacement(logNow, "place()") + newW := startW + me.GroupPadW + newH := startH + 3 // normal hight of the group label + // now move all the children aka: run place() on them + for _, child := range n.children { + child.placeWidgets(newW, newH) + // _,h := child.logicalSize() + newR := child.realGocuiSize() + // w := newR.w1 - newR.w0 + h := newR.h1 - newR.h0 // increment straight down - w.nextH += child.realHeight - w.realHeight += child.realHeight - - // track largest width - if (maxW < child.realWidth) { - maxW = child.realWidth - } - + newH += h } - // add real width of largest child - w.realWidth += maxW - w.showWidgetPlacement(logNow, "place()") default: - w.startW = p.nextW - w.startH = p.nextH - w.nextW = p.nextW - w.nextH = p.nextH - newW := w.gocuiSize.Width() - newH := w.gocuiSize.Height() - w.gocuiSize.w0 = w.startW - w.gocuiSize.h0 = w.startH - w.gocuiSize.w1 = w.gocuiSize.w0 + newW - w.gocuiSize.h1 = w.gocuiSize.h0 + newH - - // the realSize should not be smaller than the gocui view (?) - // this might not be a needed check? Maybe there are legit exceptions? - if (w.realWidth < newW) { - w.realWidth = newW - } - if (w.realHeight < newH) { - w.realHeight = newH - } - w.showWidgetPlacement(logNow, "place()") + n.gocuiSetWH(startW, startH) + // n.moveTo(startW, startH) } } -/* -func (w *cuiWidget) setWH() { - w.gocuiSize.w1 = w.gocuiSize.w0 + w.gocuiSize.width - w.gocuiSize.h1 = w.gocuiSize.h0 + w.gocuiSize.height -} -*/ - -func (w *cuiWidget) moveTo(leftW int, topH int) { - if (w.isFake) { +func (n *node) placeGrid(startW int, startH int) { + w := n.tk + w.size.w0 = startW + w.size.h0 = startH + n.showWidgetPlacement(logInfo, "grid0:") + if (n.WidgetType != toolkit.Grid) { return } - newW := w.gocuiSize.Width() - newH := w.gocuiSize.Height() - w.gocuiSize.w0 = leftW - w.gocuiSize.h0 = topH - w.gocuiSize.w1 = w.gocuiSize.w0 + newW - w.gocuiSize.h1 = w.gocuiSize.h0 + newH - w.showWidgetPlacement(logInfo, "moveTo()") -} -func (w *cuiWidget) placeGrid() { - w.showWidgetPlacement(logNow, "grid0:") - if (w.widgetType != toolkit.Grid) { - return - } - w.startW = w.parent.nextW - w.startH = w.parent.nextH - w.nextW = w.parent.nextW - w.nextH = w.parent.nextH - - var wCount int = 0 - var hCount int = 0 - for _, child := range w.children { - // increment for the next child - w.nextW = w.startW + wCount * 20 - w.nextH = w.startH + hCount * 2 + // first compute the max sizes of the rows and columns + for _, child := range n.children { + // childW, childH := child.logicalSize() + newR := child.realGocuiSize() + childW := newR.w1 - newR.w0 + childH := newR.h1 - newR.h0 // set the child's realWidth, and grid offset - child.parentH = hCount - child.parentW = wCount - if (w.widths[wCount] < child.realWidth) { - w.widths[wCount] = child.realWidth + if (w.widths[child.AtW] < childW) { + w.widths[child.AtW] = childW } - if (w.heights[hCount] < child.realHeight) { - w.heights[hCount] = child.realHeight - } - log(logVerbose, "grid1: (w,h count)", wCount, hCount, "(X,Y)", w.X, w.Y, w.name) - child.showWidgetPlacement(logNow, "grid1: " + fmt.Sprintf("next()=(%2d,%2d)", w.nextW, w.nextH)) - - if ((wCount + 1) < w.X) { - wCount += 1 - } else { - wCount = 0 - hCount += 1 + if (w.heights[child.AtH] < childH) { + w.heights[child.AtH] = childH } + // child.showWidgetPlacement(logInfo, "grid: ") + log(logVerbose, "placeGrid:", child.Name, "child()", childW, childH, "At()", child.AtW, child.AtH) } - // reset the size of the whole grid - w.realWidth = 0 - w.realHeight = 0 - for _, val := range w.widths { - w.realWidth += val - } - for _, val := range w.heights { - w.realHeight += val - } + // find the width and height offset of the grid for AtW,AtH + for _, child := range n.children { + child.showWidgetPlacement(logInfo, "grid1:") - for _, child := range w.children { - child.showWidgetPlacement(logVerbose, "grid2:") var totalW, totalH int - for i, val := range w.widths { - if (i < child.parentW) { - log(logVerbose, "grid2: (w, widths[])", i, val) - totalW += w.widths[i] + for i, w := range w.widths { + if (i < child.AtW) { + totalW += w } } for i, h := range w.heights { - if (i < child.parentH) { + if (i < child.AtH) { totalH += h } } // the new corner to move the child to - w.nextW = w.startW + totalW - w.nextH = w.startH + totalH + newW := startW + totalW + newH := startH + totalH - child.placeWidgets() + log(logVerbose, "placeGrid:", child.Name, "new()", newW, newH, "At()", child.AtW, child.AtH) + child.placeWidgets(newW, newH) child.showWidgetPlacement(logInfo, "grid2:") - log(logInfo) } - // w.updateLogicalSizes() - w.showWidgetPlacement(logNow, "grid3:") + n.showWidgetPlacement(logInfo, "grid3:") } -/* -func (w *cuiWidget) setRealSize() { +// computes the real, actual size of all the gocli objects in a widget +func (n *node) realGocuiSize() *rectType { + var f func (n *node, r *rectType) + newR := new(rectType) + // initialize the values to opposite + newR.w0 = 80 + newR.h0 = 24 + if me.baseGui != nil { + maxW, maxH := me.baseGui.Size() + newR.w0 = maxW + newR.h0 = maxH + } + newR.w1 = 0 + newR.h1 = 0 + + // expand the rectangle to the biggest thing displayed + f = func(n *node, r *rectType) { + newR := n.tk.gocuiSize + if ! n.tk.isFake { + if r.w0 > newR.w0 { + r.w0 = newR.w0 + } + if r.h0 > newR.h0 { + r.h0 = newR.h0 + } + if r.w1 < newR.w1 { + r.w1 = newR.w1 + } + if r.h1 < newR.h1 { + r.h1 = newR.h1 + } + } + for _, child := range n.children { + f(child, r) + } + } + f(n, newR) + return newR +} + +func (n *node) textSize() (int, int) { + var width, height int + + for _, s := range strings.Split(n.Text, "\n") { + if (width < len(s)) { + width = len(s) + } + height += 1 + } + return width, height } -*/ diff --git a/toolkit/gocui/plugin.go b/toolkit/gocui/plugin.go index 27d96b9..6d597b4 100644 --- a/toolkit/gocui/plugin.go +++ b/toolkit/gocui/plugin.go @@ -8,12 +8,17 @@ import ( func action(a *toolkit.Action) { log(logInfo, "action() START", a.WidgetId, a.ActionType, a.WidgetType, a.Name) - w := findWidget(a.WidgetId, me.rootNode) + n := me.rootNode.findWidgetId(a.WidgetId) + var w *cuiWidget + if (n != nil) { + w = n.tk + } switch a.ActionType { case toolkit.Add: if (w == nil) { - w = makeWidget(a) - w.addWidget() + n := addNode(a) + // w = n.tk + n.addWidget() } else { // this is done to protect the plugin being 'refreshed' with the // widget binary tree. TODO: find a way to keep them in sync @@ -22,16 +27,25 @@ func action(a *toolkit.Action) { } case toolkit.Show: if (a.B) { - w.showView() + n.showView() } else { - w.hideWidgets() + n.hideWidgets() } case toolkit.Set: - w.Set(a.A) + if a.WidgetType == toolkit.Flag { + log(logNow, "TODO: set flag here", a.ActionType, a.WidgetType, a.Name) + log(logNow, "TODO: n.WidgetType =", n.WidgetType, "n.Name =", a.Name) + } else { + if (a.A == nil) { + log(logError, "TODO: Set here. a == nil", a.ActionType, "WidgetType =", a.WidgetType, "Name =", a.Name) + } else { + n.Set(a.A) + } + } case toolkit.SetText: - w.SetText(a.S) + n.SetText(a.S) case toolkit.AddText: - w.AddText(a.S) + n.AddText(a.S) case toolkit.Move: log(logNow, "attempt to move() =", a.ActionType, a.WidgetType, a.Name) case toolkit.CloseToolkit: @@ -43,42 +57,61 @@ func action(a *toolkit.Action) { log(logInfo, "action() END") } -func (w *cuiWidget) AddText(text string) { - if (w == nil) { +func (n *node) AddText(text string) { + if (n == nil) { log(logNow, "widget is nil") return } - w.vals = append(w.vals, text) - for i, s := range w.vals { - log(logNow, "AddText()", w.name, i, s) + n.vals = append(n.vals, text) + for i, s := range n.vals { + log(logNow, "AddText()", n.Name, i, s) } - w.SetText(text) + n.SetText(text) } -func (w *cuiWidget) SetText(text string) { - if (w == nil) { +func (n *node) SetText(text string) { + if (n == nil) { log(logNow, "widget is nil") return } - w.text = text - w.s = text - w.textResize() - w.deleteView() - w.showView() + n.S = text + n.Text = text + + n.textResize() + n.deleteView() + n.showView() } -func (w *cuiWidget) Set(val any) { +func (n *node) Set(val any) { + // w := n.tk log(logInfo, "Set() value =", val) switch v := val.(type) { case bool: - w.b = val.(bool) - w.setCheckbox(val.(bool)) + n.B = val.(bool) + n.setCheckbox(val.(bool)) case string: - w.SetText(val.(string)) + n.SetText(val.(string)) case int: - w.i = val.(int) + n.I = val.(int) default: log(logError, "Set() unknown type =", val, v) } } + +// this passes the user event back from the plugin +func (n *node) doUserEvent() { + if (me.callback == nil) { + log(logError, "doUserEvent() no callback channel was configured") + return + } + var a toolkit.Action + a.WidgetId = n.WidgetId + a.Name = n.Name + a.Text = n.Text + a.B = n.B + a.ActionType = toolkit.User + log(logInfo, "doUserEvent() START: send a button click callback()", a.WidgetId, a.Name) + me.callback <- a + log(logInfo, "doUserEvent() END: sent a button click callback()", a.WidgetId, a.Name) +} diff --git a/toolkit/gocui/showStdout.go b/toolkit/gocui/showStdout.go index 32fb9f3..22e95d3 100644 --- a/toolkit/gocui/showStdout.go +++ b/toolkit/gocui/showStdout.go @@ -52,14 +52,12 @@ func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View { a.WidgetType = toolkit.Stdout a.WidgetId = -3 a.ParentId = 0 - me.logStdout = makeWidget(a) - me.logStdout.gocuiSize.w0 = maxX - 32 - me.logStdout.gocuiSize.h0 = maxY/2 - me.logStdout.gocuiSize.w1 = me.logStdout.gocuiSize.w0 + outputW - me.logStdout.gocuiSize.h1 = me.logStdout.gocuiSize.h0 + outputH - - me.logStdout.realWidth = me.logStdout.gocuiSize.Width() - me.logStdout.realHeight = me.logStdout.gocuiSize.Height() + n := addNode(a) + me.logStdout = n + me.logStdout.tk.gocuiSize.w0 = maxX - 32 + me.logStdout.tk.gocuiSize.h0 = maxY/2 + me.logStdout.tk.gocuiSize.w1 = me.logStdout.tk.gocuiSize.w0 + outputW + me.logStdout.tk.gocuiSize.h1 = me.logStdout.tk.gocuiSize.h0 + outputH } v, err := g.View("msg") if (v == nil) { @@ -78,19 +76,19 @@ func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View { if (err != nil) { log("create output window failed", err) - // return nil + return nil } if (v == nil) { log("msg == nil. WTF now? err =", err) return nil } else { - me.logStdout.v = v + me.logStdout.tk.v = v } - me.logStdout.v.Clear() - me.logStdout.v.SelBgColor = gocui.ColorCyan - me.logStdout.v.SelFgColor = gocui.ColorBlack + v.Clear() + v.SelBgColor = gocui.ColorCyan + v.SelFgColor = gocui.ColorBlack fmt.Fprintln(v, "figure out how to capture STDOUT to here\n" + stringFromMouseClick) g.SetViewOnBottom("msg") // g.SetViewOnBottom(v.Name()) diff --git a/toolkit/gocui/structs.go b/toolkit/gocui/structs.go index 1fa6692..06a2266 100644 --- a/toolkit/gocui/structs.go +++ b/toolkit/gocui/structs.go @@ -23,11 +23,12 @@ var me config type config struct { baseGui *gocui.Gui // the main gocui handle - rootNode *cuiWidget // the base of the binary tree. it should have id == 0 - ctrlDown *cuiWidget // shown if you click the mouse when the ctrl key is pressed - current *cuiWidget // this is the current tab or window to show - logStdout *cuiWidget // where to show STDOUT - logStdoutV *gocui.View // where to show STDOUT + rootNode *node // the base of the binary tree. it should have id == 0 + + ctrlDown *node // shown if you click the mouse when the ctrl key is pressed +// current *cuiWidget // this is the current tab or window to show + logStdout *node // where to show STDOUT + helpLabel *gocui.View // this is the channel we send user events like // mouse clicks or keyboard events back to the program @@ -36,53 +37,47 @@ type config struct { // this is the channel we get requests to make widgets pluginChan chan toolkit.Action - helpLabel *gocui.View - - DefaultBehavior bool `default:"true"` - - // Buttons, Group, Tabs, Windows, etc are by default assumed to be a single line - // as a default, we make buttons 8 chars wide - DefaultWidth int `default:"8"` - DefaultHeight int `default:"1"` - // When the widget has a frame, like a button, it adds 2 lines runes on each side // so you need 3 char spacing in each direction to not have them overlap // the amount of padding when there is a frame - FramePadW int `default:"4" dense:"0"` + FramePadW int `default:"1" dense:"0"` FramePadH int `default:"1" dense:"0"` PadW int `default:"1" dense:"0"` PadH int `default:"1" dense:"0"` - // additional amount of space to put between window & tab widgets - WindowPadW int `default:"8" dense:"0"` - TabPadW int `default:"4" dense:"0"` - // how far down to start Window or Tab headings WindowW int `default:"8" dense:"0"` WindowH int `default:"-1"` - TabW int `default:"2" dense:"0"` + TabW int `default:"5" dense:"0"` TabH int `default:"1" dense:"0"` + // additional amount of space to put between window & tab widgets + WindowPadW int `default:"8" dense:"0"` + TabPadW int `default:"4" dense:"0"` + // additional amount of space to indent on a group GroupPadW int `default:"6" dense:"2"` // the raw beginning of each window (or tab) - RawW int `default:"7"` - RawH int `default:"3"` + RawW int `default:"1"` + RawH int `default:"5"` // offset for the hidden widgets - DevelOffsetW int `default:"20"` + FakeW int `default:"20"` + padded bool // add space between things like buttons bookshelf bool // do you want things arranged in the box like a bookshelf or a stack? canvas bool // if set to true, the windows are a raw canvas menubar bool // for windows stretchy bool // expand things like buttons to the maximum size - padded bool // add space between things like buttons margin bool // add space around the frames of windows // writeMutex protects locks the write process writeMutex sync.Mutex + + // used for listWidgets() debugging + depth int } // deprecate these @@ -121,23 +116,21 @@ type node struct { AtW int AtH int + vals []string // dropdown menu items + + // horizontal=true means layout widgets like books on a bookshelf + // horizontal=false means layout widgets like books in a stack + horizontal bool `default:false` + + hasTabs bool // does the window have tabs? + // the internal plugin toolkit structure tk *cuiWidget } -// the gocui way -// the logical size of the widget +// this is the gocui way // corner starts at in the upper left corner type rectType struct { - // where the widget should calculate it's existance from - // startW int - // startH int - - // the is a shortcut to access -// width int // this is always w1 - w0 - height int // this is always h1 - h0 - - // this is the gocui way w0, h0, w1, h1 int // left top right bottom } @@ -150,82 +143,27 @@ func (r *rectType) Height() int { } type cuiWidget struct { - id int // widget ID - // parentId int - widgetType toolkit.WidgetType - - name string // a descriptive name of the widget - text string // the current text being displayed + // the gocui package variables + v *gocui.View // this is nil if the widget is not displayed cuiName string // what gocui uses to reference the widget - vals []string // dropdown menu options + // the logical size of the widget + // For example, 40x12 would be the center of a normal terminal + size rectType + + // the actual gocui display view of this widget + // sometimes this isn't visable like with a Box or Grid + gocuiSize rectType isCurrent bool // is this the currently displayed Window or Tab? - hasTabs bool // does the window have tabs? isFake bool // widget types like 'box' are 'false' - // where the widget's real corner is - // should we always compute this? - startW int - startH int - - // where the next child should be placed - nextW int - nextH int - - // the widget size to reserve or things will overlap - realWidth int - realHeight int - - gocuiSize rectType // the display size of this widget - // logicalSize rectType // the logical size. Includes all the child widgets - // used to track the size of grids widths map[int]int // how tall each row in the grid is heights map[int]int // how wide each column in the grid is - // deprecate // where in the parent grid this widget should go - parentW int - parentH int - - // things from toolkit/action - b bool - i int - s string - X int - Y int - width int - height int - - // horizontal=true means layout widgets like books on a bookshelf - // horizontal=false means layout widgets like books in a stack - horizontal bool `default:false` - tainted bool - v *gocui.View frame bool - - parent *cuiWidget - children []*cuiWidget -} - -func (w *cuiWidget) IsCurrent() bool { - if (w.widgetType == toolkit.Tab) { - return w.isCurrent - } - if (w.widgetType == toolkit.Window) { - return w.isCurrent - } - if (w.widgetType == toolkit.Root) { - return false - } - return w.parent.IsCurrent() -} - -func (w *cuiWidget) StartW() { -} - -func (w *cuiWidget) StartH() { } // from the gocui devs: @@ -238,7 +176,7 @@ func (w *cuiWidget) Write(p []byte) (n int, err error) { w.tainted = true me.writeMutex.Lock() defer me.writeMutex.Unlock() - if (me.logStdout.v == nil) { + if (me.logStdout.tk.v == nil) { // optionally write the output to /tmp s := fmt.Sprint(string(p)) s = strings.TrimSuffix(s, "\n") @@ -246,11 +184,11 @@ func (w *cuiWidget) Write(p []byte) (n int, err error) { v, _ := me.baseGui.View("msg") if (v != nil) { // fmt.Fprintln(outf, "found msg") - me.logStdout.v = v + me.logStdout.tk.v = v } } else { // display the output in the gocui window - me.logStdout.v.Clear() + me.logStdout.tk.v.Clear() s := fmt.Sprint(string(p)) s = strings.TrimSuffix(s, "\n") @@ -260,7 +198,7 @@ func (w *cuiWidget) Write(p []byte) (n int, err error) { l := len(outputS) - outputH outputS = outputS[l:] } - fmt.Fprintln(me.logStdout.v, strings.Join(outputS, "\n")) + fmt.Fprintln(me.logStdout.tk.v, strings.Join(outputS, "\n")) } return len(p), nil diff --git a/toolkit/gocui/tab.go b/toolkit/gocui/tab.go index 8c5b11b..60ee6b3 100644 --- a/toolkit/gocui/tab.go +++ b/toolkit/gocui/tab.go @@ -3,136 +3,111 @@ package main // implements widgets 'Window' and 'Tab' import ( + "strings" "git.wit.org/wit/gui/toolkit" -// "github.com/awesome-gocui/gocui" ) -func (w *cuiWidget) hideWidgets() { - w.isCurrent = false - switch w.widgetType { - case toolkit.Root: - case toolkit.Flag: - case toolkit.Window: - // case toolkit.Tab: - case toolkit.Box: - case toolkit.Grid: - default: - w.deleteView() - } - for _, child := range w.children { - child.hideWidgets() +func (w *cuiWidget) Width() int { + if w.frame { + return w.gocuiSize.w1 - w.gocuiSize.w0 } + return w.gocuiSize.w1 - w.gocuiSize.w0 - 1 } -func (w *cuiWidget) hideFake() { - if (w.isFake) { - w.deleteView() - } - for _, child := range w.children { - child.hideFake() +func (w *cuiWidget) Height() int { + if w.frame { + return w.gocuiSize.h1 - w.gocuiSize.h0 } + return w.gocuiSize.h1 - w.gocuiSize.h0 - 1 } -func (w *cuiWidget) showFake() { - if (w.isFake) { - w.setFake() - w.showWidgetPlacement(logNow, "showFake:") - w.showView() - } - for _, child := range w.children { - child.showFake() - } -} +func (n *node) gocuiSetWH(sizeW, sizeH int) { + w := len(n.Text) + lines := strings.Split(n.Text, "\n") + h := len(lines) -func (w *cuiWidget) showWidgets() { - if (w.isFake) { - // don't display by default + tk := n.tk + if tk.isFake { + tk.gocuiSize.w0 = sizeW + tk.gocuiSize.h0 = sizeH + tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW + tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH + return + } + + if tk.frame { + tk.size.w0 = sizeW + tk.size.h0 = sizeH + tk.gocuiSize.w0 = tk.size.w0 + tk.gocuiSize.h0 = tk.size.h0 + tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW + tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH } else { - if w.IsCurrent() { - w.showWidgetPlacement(logInfo, "current:") - w.showView() - } else { - w.showWidgetPlacement(logInfo, "not:") - // w.drawView() + tk.size.w0 = sizeW - 1 + tk.size.h0 = sizeH - 1 + tk.gocuiSize.w0 = tk.size.w0 + tk.gocuiSize.h0 = tk.size.h0 + tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + 1 + tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + 1 + } +} + +func redoWindows(nextW int, nextH int) { + for _, n := range me.rootNode.children { + if n.WidgetType != toolkit.Window { + continue } - } - for _, child := range w.children { - child.showWidgets() - } -} - -func (w *cuiWidget) setWindowWH() { - w.gocuiSize.w0 = me.rootNode.nextW - w.gocuiSize.h0 = me.WindowH - - t := len(w.text) - w.gocuiSize.w1 = w.gocuiSize.w0 + t + me.PadW - w.gocuiSize.h1 = w.gocuiSize.h0 + me.DefaultHeight + me.PadH - - w.realWidth = w.gocuiSize.Width() - w.realHeight = w.gocuiSize.Height() - - // move the rootNode width over for the next window - me.rootNode.nextW += w.realWidth + me.WindowPadW - - w.nextW = 4 - w.nextH = 2 - - w.showWidgetPlacement(logNow, "setWindowWH:") -} - -func (w *cuiWidget) setTabWH() { - // set the start and size of the tab gocui button - - w.gocuiSize.w0 = w.parent.nextW - w.gocuiSize.h0 = me.TabH - - t := len(w.text) - w.gocuiSize.w1 = w.gocuiSize.w0 + t + me.PadW - w.gocuiSize.h1 = w.gocuiSize.h0 + me.DefaultHeight + me.PadH - - w.realWidth = w.gocuiSize.Width() - w.realHeight = w.gocuiSize.Height() - - w.realWidth += me.FramePadW - w.realHeight += me.FramePadH - - w.parent.nextW += w.realWidth + me.TabPadW - - w.showWidgetPlacement(logNow, "setTabWH:") -} - -func (w *cuiWidget) redoTabs(draw bool) { - if (w.widgetType == toolkit.Window) { - var tabs bool = false - // figure out if the window is just a bunch of tabs - for _, child := range w.children { - if (child.widgetType == toolkit.Tab) { + w := n.tk + var tabs bool + for _, child := range n.children { + if (child.WidgetType == toolkit.Tab) { tabs = true } } if (tabs) { // window is tabs. Don't show it as a standard button w.frame = false - w.hasTabs = true + n.hasTabs = true } else { - w.frame = true - w.hasTabs = false + w.frame = false + n.hasTabs = false } - w.setWindowWH() - w.deleteView() - w.showView() - } - if (w.widgetType == toolkit.Tab) { - w.setTabWH() - w.deleteView() - // show all the tabs for the current window - if w.parent.isCurrent { - w.showView() - } - } - for _, child := range w.children { - child.redoTabs(draw) + w.size.w0 = nextW + w.size.h0 = nextH + n.gocuiSetWH(nextW, nextH) + n.deleteView() + n.showView() + + sizeW := w.Width() + me.WindowPadW + sizeH := w.Height() + nextW += sizeW + log(logNow, "redoWindows() start nextW,H =", nextW, nextH, "gocuiSize.W,H =", sizeW, sizeH, n.Name) + + if n.hasTabs { + n.redoTabs(me.TabW, me.TabH) + } + } +} + +func (p *node) redoTabs(nextW int, nextH int) { + for _, n := range p.children { + if n.WidgetType != toolkit.Tab { + continue + } + w := n.tk + w.frame = true + + w.size.w0 = nextW + w.size.h0 = nextH + n.gocuiSetWH(nextW, nextH) + n.deleteView() + // setCurrentTab(n) + n.showView() + + sizeW := w.Width() + me.TabPadW + sizeH := w.Height() + log(logNow, "redoTabs() start nextW,H =", nextW, nextH, "gocuiSize.W,H =", sizeW, sizeH, n.Name) + nextW += sizeW } } diff --git a/toolkit/gocui/view.go b/toolkit/gocui/view.go index b47c075..e2c76aa 100644 --- a/toolkit/gocui/view.go +++ b/toolkit/gocui/view.go @@ -20,10 +20,11 @@ func splitLines(s string) []string { return lines } -func (w *cuiWidget) textResize() { +func (n *node) textResize() { + w := n.tk var width, height int - for i, s := range splitLines(w.text) { + for i, s := range splitLines(n.Text) { log(logNow, "textResize() len =", len(s), i, s) if (width < len(s)) { width = len(s) @@ -32,38 +33,52 @@ func (w *cuiWidget) textResize() { } w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH - w.showWidgetPlacement(logNow, "textResize()") + n.showWidgetPlacement(logNow, "textResize()") } // display's the text of the widget in gocui -func (w *cuiWidget) showView() { +func (n *node) showView() { var err error + w := n.tk + if (w.cuiName == "") { - log(logError, "drawView() w.cuiName was not set for widget", w) - w.cuiName = strconv.Itoa(w.id) + log(logError, "showView() w.cuiName was not set for widget", w) + w.cuiName = strconv.Itoa(n.WidgetId) } - if (w.v != nil) { - log(logInfo, "drawView() w.v already defined for widget", w) - v, _ := me.baseGui.View(w.cuiName) - if (v == nil) { - log(logError, "drawView() ERROR view does not really exist", w) - w.v = nil - } else { - return - } + if (w.v == nil) { + n.updateView() + } + x0, y0, x1, y1, err := me.baseGui.ViewPosition(w.cuiName) + log(logInfo, "showView() w.v already defined for widget", n.Name, err) + if (x0 != w.gocuiSize.w0) || (y0 != w.gocuiSize.h0) { + log(logError, "showView() w.v.w0 != x0", n.Name, w.gocuiSize.w0, x0) + log(logError, "showView() w.v.h0 != y0", n.Name, w.gocuiSize.h0, y0) + n.updateView() + return + } + if (x1 != w.gocuiSize.w1) || (y1 != w.gocuiSize.h1) { + log(logError, "showView() w.v.w1 != x1", n.Name, w.gocuiSize.w1, x1) + log(logError, "showView() w.v.h1 != y1", n.Name, w.gocuiSize.h1, y1) + n.updateView() + return } + if (w.v.Visible == false) { + log(logInfo, "showView() w.v.Visible set to true ", n.Name) + w.v.Visible = true + } +} + +func (n *node) updateView() { + var err error + w := n.tk if (me.baseGui == nil) { - log(logError, "drawView() ERROR: me.baseGui == nil", w) - return - } - v, _ := me.baseGui.View(w.cuiName) - if (v != nil) { - log(logError, "drawView() already defined for name", w.cuiName) - w.v = v + log(logError, "showView() ERROR: me.baseGui == nil", w) return } + me.baseGui.DeleteView(w.cuiName) + w.v = nil a := w.gocuiSize.w0 b := w.gocuiSize.h0 @@ -72,12 +87,12 @@ func (w *cuiWidget) showView() { w.v, err = me.baseGui.SetView(w.cuiName, a, b, c, d, 0) if err == nil { - w.showWidgetPlacement(logError, "drawView()") + n.showWidgetPlacement(logError, "drawView()") log(logError, "drawView() internal plugin error err = nil") return } if !errors.Is(err, gocui.ErrUnknownView) { - w.showWidgetPlacement(logError, "drawView()") + n.showWidgetPlacement(logError, "drawView()") log(logError, "drawView() internal plugin error error.IS()", err) return } @@ -85,13 +100,68 @@ func (w *cuiWidget) showView() { me.baseGui.SetKeybinding(w.v.Name(), gocui.MouseLeft, gocui.ModNone, click) w.v.Wrap = true - if (w.widgetType == toolkit.Window) { - w.v.Frame = w.frame - w.v.Clear() - fmt.Fprint(w.v, w.text) - } else { - fmt.Fprintln(w.v, w.text) - } + w.v.Frame = w.frame + w.v.Clear() + fmt.Fprint(w.v, n.Text) + n.showWidgetPlacement(logNow, "Window: " + n.Text) - w.setDefaultWidgetColor() + n.setDefaultHighlight() + n.setDefaultWidgetColor() +} + +func (n *node) hideWidgets() { + w := n.tk + w.isCurrent = false + switch n.WidgetType { + case toolkit.Root: + case toolkit.Flag: + case toolkit.Window: + case toolkit.Box: + case toolkit.Grid: + default: + n.deleteView() + } + for _, child := range n.children { + child.hideWidgets() + } +} + +func (n *node) hideFake() { + w := n.tk + if (w.isFake) { + n.deleteView() + } + for _, child := range n.children { + child.hideFake() + } +} + +func (n *node) showFake() { + w := n.tk + if (w.isFake) { + n.setFake() + n.showWidgetPlacement(logNow, "showFake:") + n.showView() + } + for _, child := range n.children { + child.showFake() + } +} + +func (n *node) showWidgets() { + w := n.tk + if (w.isFake) { + // don't display by default + } else { + if n.IsCurrent() { + n.showWidgetPlacement(logInfo, "current:") + n.showView() + } else { + n.showWidgetPlacement(logInfo, "not:") + // w.drawView() + } + } + for _, child := range n.children { + child.showWidgets() + } }