From 36218f4535dd65d2c8d4ecbea761b3b0289e6f3c Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Mon, 1 Jan 2024 16:11:54 -0600 Subject: [PATCH] move into seperate repo --- .gitignore | 8 + Makefile | 115 ++++++++++++++ andlabs/Makefile | 7 + andlabs/action.go | 265 ++++++++++++++++++++++++++++++++ andlabs/add.go | 157 +++++++++++++++++++ andlabs/box.go | 29 ++++ andlabs/button.go | 31 ++++ andlabs/checkbox.go | 27 ++++ andlabs/combobox.go | 41 +++++ andlabs/common.go | 1 + andlabs/debug.go | 168 ++++++++++++++++++++ andlabs/delete.go | 51 +++++++ andlabs/demo.go | 90 +++++++++++ andlabs/dropdown.go | 79 ++++++++++ andlabs/grid.go | 25 +++ andlabs/group.go | 22 +++ andlabs/icon.go | 27 ++++ andlabs/image.go | 50 ++++++ andlabs/label.go | 18 +++ andlabs/log.go | 24 +++ andlabs/main.go | 57 +++++++ andlabs/setText.go | 128 ++++++++++++++++ andlabs/slider.go | 22 +++ andlabs/spinner.go | 22 +++ andlabs/structs.go | 58 +++++++ andlabs/tab.go | 116 ++++++++++++++ andlabs/textbox.go | 32 ++++ andlabs/updateui.go | 97 ++++++++++++ andlabs/widget.go | 29 ++++ andlabs/window.go | 46 ++++++ go.mod | 20 +++ go.sum | 24 +++ gocui/Makefile | 15 ++ gocui/add.go | 78 ++++++++++ gocui/checkbox.go | 33 ++++ gocui/click.go | 356 +++++++++++++++++++++++++++++++++++++++++++ gocui/color.go | 117 ++++++++++++++ gocui/common.go | 1 + gocui/debug.go | 73 +++++++++ gocui/fakefile.go | 58 +++++++ gocui/gocui.go | 100 ++++++++++++ gocui/help.go | 71 +++++++++ gocui/keybindings.go | 175 +++++++++++++++++++++ gocui/log.go | 55 +++++++ gocui/main.go | 101 ++++++++++++ gocui/mouse.go | 149 ++++++++++++++++++ gocui/place.go | 186 ++++++++++++++++++++++ gocui/plugin.go | 126 +++++++++++++++ gocui/showStdout.go | 97 ++++++++++++ gocui/structs.go | 224 +++++++++++++++++++++++++++ gocui/tab.go | 109 +++++++++++++ gocui/view.go | 232 ++++++++++++++++++++++++++++ gocui/widget.go | 147 ++++++++++++++++++ nocui/Makefile | 5 + nocui/README.md | 5 + nocui/action.go | 153 +++++++++++++++++++ nocui/common.go | 166 ++++++++++++++++++++ nocui/event.go | 47 ++++++ nocui/log.go | 26 ++++ nocui/main.go | 55 +++++++ nocui/stdin.go | 80 ++++++++++ nocui/structs.go | 17 +++ nocui/widget.go | 29 ++++ 63 files changed, 4972 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 andlabs/Makefile create mode 100644 andlabs/action.go create mode 100644 andlabs/add.go create mode 100644 andlabs/box.go create mode 100644 andlabs/button.go create mode 100644 andlabs/checkbox.go create mode 100644 andlabs/combobox.go create mode 120000 andlabs/common.go create mode 100644 andlabs/debug.go create mode 100644 andlabs/delete.go create mode 100644 andlabs/demo.go create mode 100644 andlabs/dropdown.go create mode 100644 andlabs/grid.go create mode 100644 andlabs/group.go create mode 100644 andlabs/icon.go create mode 100644 andlabs/image.go create mode 100644 andlabs/label.go create mode 100644 andlabs/log.go create mode 100644 andlabs/main.go create mode 100644 andlabs/setText.go create mode 100644 andlabs/slider.go create mode 100644 andlabs/spinner.go create mode 100644 andlabs/structs.go create mode 100644 andlabs/tab.go create mode 100644 andlabs/textbox.go create mode 100644 andlabs/updateui.go create mode 100644 andlabs/widget.go create mode 100644 andlabs/window.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 gocui/Makefile create mode 100644 gocui/add.go create mode 100644 gocui/checkbox.go create mode 100644 gocui/click.go create mode 100644 gocui/color.go create mode 120000 gocui/common.go create mode 100644 gocui/debug.go create mode 100644 gocui/fakefile.go create mode 100644 gocui/gocui.go create mode 100644 gocui/help.go create mode 100644 gocui/keybindings.go create mode 100644 gocui/log.go create mode 100644 gocui/main.go create mode 100644 gocui/mouse.go create mode 100644 gocui/place.go create mode 100644 gocui/plugin.go create mode 100644 gocui/showStdout.go create mode 100644 gocui/structs.go create mode 100644 gocui/tab.go create mode 100644 gocui/view.go create mode 100644 gocui/widget.go create mode 100644 nocui/Makefile create mode 100644 nocui/README.md create mode 100644 nocui/action.go create mode 100644 nocui/common.go create mode 100644 nocui/event.go create mode 100644 nocui/log.go create mode 100644 nocui/main.go create mode 100644 nocui/stdin.go create mode 100644 nocui/structs.go create mode 100644 nocui/widget.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c5452f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.swp + +# ignore compiled plugins +*.so + +# temporary files when building debian packages +/*.deb +/files diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..532b3ca --- /dev/null +++ b/Makefile @@ -0,0 +1,115 @@ +.PHONY: nocui gocui andlabs + +all: + # reset + @echo + @echo "make examples # will run all the Example demos and commands" + @echo "make update # full git update of all the dependencies" + @echo + @echo This Requires working IPv6 + @echo +ifeq ($(GO111MODULE),) + @echo + @echo If you are compiling this here, you probably want to set GO111MODULE + @echo + @echo Setting GO111MODULE means that the version you are compiling has plugins + @echo that get compiled against this current running version of the code + @echo Otherwise, the GO language plugins can complain about being compiled against + @echo mis-matched versions + @echo + @echo export GO111MODULE=off + @echo + sleep 3 +endif + make nocui gocui andlabs + +nocui: + go build -C nocui -v -buildmode=plugin -o ../nocui.so + +gocui: + go build -C gocui -v -buildmode=plugin -o ../gocui.so + +andlabs: + go build -C andlabs -v -buildmode=plugin -o ../andlabs.so + +something: +ifeq (,$(wildcard go.mod)) + go mod init gui + go mod tidy +endif + make clean + make plugins + +build-dep: + apt install -f libgtk-3-dev + +# should update every go dependancy (?) +update: + git pull + go get -v -t -u ./... + +deb: + cd debian && make + dpkg-deb -c go-wit-gui*.deb + -wit mirrors + +examples: \ + all \ + examples-helloworld \ + examples-buttons \ + examples-console-ui-helloworld + +# this is the most basic one. This syntax should always work +examples-helloworld: + make -C examples/helloworld + +examples-buttons: + make -C examples/buttons + +examples-console-ui-helloworld: + make -C examples/console-ui-helloworld + +# sync repo to the github backup +# git remote add github git@github.com:witorg/gui.git +# git remote add github2 git@github.com:wit-go/gui.git +github: + git push origin master + git push origin devel + git push origin --tags + git push github master + git push github devel + git push github --tags + @echo + @echo check https://github.com/wit-go/gui + @echo + +doc: + godoc -v + +goget: + go get -v -t -u + make -C toolkit/gocui goget + make -C toolkit/andlabs goget + +clean: + rm -f *.so + # cd debian && make clean + +plugins: plugins-gocui plugins-andlabs + +plugins-gocui: + go build -C toolkit/gocui -v -buildmode=plugin -o ../gocui.so + go build -C toolkit/nocui -v -buildmode=plugin -o ../nocui.so + +plugins-andlabs: + go build -C toolkit/andlabs -v -buildmode=plugin -o ../andlabs.so + +objdump: + objdump -t toolkit/andlabs.so |less + +log: + reset + tail -f /tmp/witgui.* /tmp/guilogfile + +submit-to-docs: + GOPROXY=https://proxy.golang.org GO111MODULE=on go get go.wit.com/gui@v1.0.0 diff --git a/andlabs/Makefile b/andlabs/Makefile new file mode 100644 index 0000000..b839c33 --- /dev/null +++ b/andlabs/Makefile @@ -0,0 +1,7 @@ +all: plugin + +plugin: + GO111MODULE="off" go build -v -x -buildmode=plugin -o ../andlabs.so + +goget: + GO111MODULE="off" go get -v -t -u diff --git a/andlabs/action.go b/andlabs/action.go new file mode 100644 index 0000000..82acd58 --- /dev/null +++ b/andlabs/action.go @@ -0,0 +1,265 @@ +package main + +import ( + "strconv" + "github.com/andlabs/ui" + "go.wit.com/gui/gui/toolkit" +) + +func (n *node) show(b bool) { + if n.tk == nil { + return + } + if n.tk.uiControl == nil { + return + } + if (b) { + n.tk.uiControl.Show() + } else { + n.tk.uiControl.Hide() + } +} + +func (n *node) enable(b bool) { + if n == nil { + panic("WHAT? enable was passed nil. How does this even happen?") + } + if n.tk == nil { + return + } + if n.tk.uiControl == nil { + return + } + if (b) { + n.tk.uiControl.Enable() + } else { + n.tk.uiControl.Disable() + } +} + +func (n *node) pad(at toolkit.ActionType) { + log(logInfo, "pad() on WidgetId =", n.WidgetId) + + t := n.tk + if (t == nil) { + log(logError, "pad() toolkit struct == nil. for", n.WidgetId) + return + } + + switch n.WidgetType { + case toolkit.Group: + switch at { + case toolkit.Margin: + t.uiGroup.SetMargined(true) + case toolkit.Unmargin: + t.uiGroup.SetMargined(false) + case toolkit.Pad: + t.uiGroup.SetMargined(true) + case toolkit.Unpad: + t.uiGroup.SetMargined(false) + } + case toolkit.Tab: + switch at { + case toolkit.Margin: + tabSetMargined(t.uiTab, true) + case toolkit.Unmargin: + tabSetMargined(t.uiTab, false) + case toolkit.Pad: + tabSetMargined(t.uiTab, true) + case toolkit.Unpad: + tabSetMargined(t.uiTab, false) + } + case toolkit.Window: + switch at { + case toolkit.Margin: + t.uiWindow.SetMargined(true) + case toolkit.Unmargin: + t.uiWindow.SetMargined(false) + case toolkit.Pad: + t.uiWindow.SetBorderless(false) + case toolkit.Unpad: + t.uiWindow.SetBorderless(true) + } + case toolkit.Grid: + switch at { + case toolkit.Margin: + t.uiGrid.SetPadded(true) + case toolkit.Unmargin: + t.uiGrid.SetPadded(false) + case toolkit.Pad: + t.uiGrid.SetPadded(true) + case toolkit.Unpad: + t.uiGrid.SetPadded(false) + } + case toolkit.Box: + switch at { + case toolkit.Margin: + t.uiBox.SetPadded(true) + case toolkit.Unmargin: + t.uiBox.SetPadded(false) + case toolkit.Pad: + t.uiBox.SetPadded(true) + case toolkit.Unpad: + t.uiBox.SetPadded(false) + } + case toolkit.Textbox: + log(debugError, "TODO: implement ActionType =", at) + default: + log(debugError, "TODO: implement pad() for", at) + } +} + +func (n *node) move(newParent *node) { + p := n.parent + + switch p.WidgetType { + case toolkit.Group: + case toolkit.Tab: + // tabSetMargined(tParent.uiTab, true) + case toolkit.Window: + // t.uiWindow.SetBorderless(false) + case toolkit.Grid: + // t.uiGrid.SetPadded(true) + case toolkit.Box: + log(logInfo, "TODO: move() where =", p.ParentId) + log(logInfo, "TODO: move() for widget =", n.WidgetId) + + stretchy = true + if (p.tk.uiBox != nil) { + p.tk.uiBox.Append(n.tk.uiControl, stretchy) + } + // log(debugNow, "is there a tParent parent? =", tParent.parent) + // tParent.uiBox.Delete(0) + + // this didn't work: + // tWidget.uiControl.Disable() + // sleep(.8) + default: + log(logError, "TODO: need to implement move() for type =", n.WidgetType) + log(logError, "TODO: need to implement move() for where =", p.ParentId) + log(logError, "TODO: need to implement move() for widget =", n.WidgetId) + } +} + +func (n *node) Delete() { + p := n.parent + log(debugNow, "uiDelete()", n.WidgetId, "to", p.WidgetId) + + switch p.WidgetType { + case toolkit.Group: + // tParent.uiGroup.SetMargined(true) + case toolkit.Tab: + // tabSetMargined(tParent.uiTab, true) + case toolkit.Window: + // t.uiWindow.SetBorderless(false) + case toolkit.Grid: + // t.uiGrid.SetPadded(true) + case toolkit.Box: + log(debugNow, "tWidget.boxC =", p.Name) + log(debugNow, "is there a tParent parent? =", p.parent) + if (p.tk.boxC < 1) { + log(debugNow, "Can not delete from Box. already empty. tWidget.boxC =", p.tk.boxC) + return + } + p.tk.uiBox.Delete(0) + p.tk.boxC -= 1 + + // this didn't work: + // tWidget.uiControl.Disable() + // sleep(.8) + // tParent.uiBox.Append(tWidget.uiControl, stretchy) + default: + log(debugError, "TODO: need to implement uiDelete() for widget =", n.WidgetId, n.WidgetType) + log(debugError, "TODO: need to implement uiDelete() for parent =", p.WidgetId, p.WidgetType) + } +} + +func rawAction(a *toolkit.Action) { + log(logInfo, "rawAction() START a.ActionType =", a.ActionType) + log(logInfo, "rawAction() START a.S =", a.S) + + if (a.ActionType == toolkit.InitToolkit) { + // TODO: make sure to only do this once + // go uiMain.Do(func() { + // ui.Main(demoUI) + // go catchActionChannel() + // }) + // try doing this on toolkit load in init() + return + } + + log(logInfo, "rawAction() START a.WidgetId =", a.WidgetId, "a.ParentId =", a.ParentId) + switch a.WidgetType { + case toolkit.Flag: + flag(a) + return + } + + n := me.rootNode.findWidgetId(a.WidgetId) + + if (a.ActionType == toolkit.Add) { + ui.QueueMain(func() { + add(a) + }) + // TODO: remove this artificial delay + // sleep(.001) + return + } + + if (a.ActionType == toolkit.Dump) { + log(debugNow, "rawAction() Dump =", a.ActionType, a.WidgetType, n.Name) + me.rootNode.listChildren(true) + return + } + + if (n == nil) { + me.rootNode.listChildren(true) + log(true, "rawAction() ERROR findWidgetId found nil", a.ActionType, a.WidgetType) + log(true, "rawAction() ERROR findWidgetId found nil for id =", a.WidgetId) + log(true, "rawAction() ERROR findWidgetId found nil", a.ActionType, a.WidgetType) + log(true, "rawAction() ERROR findWidgetId found nil for id =", a.WidgetId) + return + panic("findWidgetId found nil for id = " + strconv.Itoa(a.WidgetId)) + } + + switch a.ActionType { + case toolkit.Show: + n.show(true) + case toolkit.Hide: + n.show(false) + case toolkit.Enable: + n.enable(true) + case toolkit.Disable: + n.enable(false) + case toolkit.Get: + n.setText(a) + case toolkit.GetText: + switch a.WidgetType { + case toolkit.Textbox: + a.S = n.S + } + case toolkit.Set: + n.setText(a) + case toolkit.SetText: + n.setText(a) + case toolkit.AddText: + n.setText(a) + case toolkit.Margin: + n.pad(toolkit.Unmargin) + case toolkit.Unmargin: + n.pad(toolkit.Margin) + case toolkit.Pad: + n.pad(toolkit.Pad) + case toolkit.Unpad: + n.pad(toolkit.Unpad) + case toolkit.Delete: + n.Delete() + case toolkit.Move: + log(debugNow, "rawAction() attempt to move() =", a.ActionType, a.WidgetType) + newParent := me.rootNode.findWidgetId(a.ParentId) + n.move(newParent) + default: + log(debugError, "rawAction() Unknown =", a.ActionType, a.WidgetType) + } + log(logInfo, "rawAction() END =", a.ActionType, a.WidgetType) +} diff --git a/andlabs/add.go b/andlabs/add.go new file mode 100644 index 0000000..d78101f --- /dev/null +++ b/andlabs/add.go @@ -0,0 +1,157 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" + + "go.wit.com/gui/gui/toolkit" +) + +func actionDump(b bool, a *toolkit.Action) { + log(b, "actionDump() Widget.Type =", a.ActionType) + log(b, "actionDump() Widget.S =", a.S) + log(b, "actionDump() Widget.I =", a.I) + log(b, "actionDump() WidgetId =", a.WidgetId) + log(b, "actionDump() ParentId =", a.ParentId) +} + +func add(a *toolkit.Action) { + if (a.WidgetType == toolkit.Root) { + me.rootNode = addNode(a) + return + } + n := addNode(a) + + p := n.parent + switch n.WidgetType { + case toolkit.Window: + newWindow(n) + return + case toolkit.Tab: + p.newTab(n) + return + case toolkit.Label: + p.newLabel(n) + return + case toolkit.Button: + p.newButton(n) + return + case toolkit.Grid: + p.newGrid(n) + return + case toolkit.Checkbox: + p.newCheckbox(n) + return + case toolkit.Spinner: + p.newSpinner(n) + return + case toolkit.Slider: + p.newSlider(n) + return + case toolkit.Dropdown: + p.newDropdown(n) + return + case toolkit.Combobox: + p.newCombobox(n) + return + case toolkit.Textbox: + p.newTextbox(n) + return + case toolkit.Group: + p.newGroup(n) + return + case toolkit.Box: + p.newBox(n) + return + case toolkit.Image: + p.newImage(n) + return + default: + log(debugError, "add() error TODO: ", n.WidgetType, n.Name) + } +} + +// This routine is very specific to this toolkit +// It's annoying and has to be copied to each widget when there are changes +// it could be 'simplfied' maybe or made to be more generic, but this is as far as I've gotten +// it's probably not worth working much more on this toolkit, the andlabs/ui has been great and got me here! +// but it's time to write direct GTK, QT, macos and windows toolkit plugins +// -- jcarr 2023/03/09 + +// Grid numbering examples by (X,Y) +// --------- +// -- (1) -- +// -- (2) -- +// --------- +// +// ----------------------------- +// -- (1,1) -- (1,2) -- (1,3) -- +// -- (2,1) -- (2,2) -- (2,3) -- +// ----------------------------- + +// internally for andlabs/ui +// (x&y flipped and start at zero) +// ----------------------------- +// -- (0,0) -- (1,0) -- (1,0) -- +// -- (0,1) -- (1,1) -- (1,1) -- +// ----------------------------- +func (p *node) place(n *node) bool { + log(logInfo, "place() START", n.WidgetType, n.Name) + + if (p.tk == nil) { + log(logError, "p.tk == nil", p.Name, p.ParentId, p.WidgetType, p.tk) + log(logError, "n = ", n.Name, n.ParentId, n.WidgetType, n.tk) + panic("p.tk == nil") + } + + log(logInfo, "place() switch", p.WidgetType) + switch p.WidgetType { + case toolkit.Grid: + log(logInfo, "place() Grid try at Parent X,Y =", n.X, n.Y) + n.tk.gridX = n.AtW - 1 + n.tk.gridY = n.AtH - 1 + log(logInfo, "place() Grid try at gridX,gridY", n.tk.gridX, n.tk.gridY) + // at the very end, subtract 1 from X & Y since andlabs/ui starts counting at zero + p.tk.uiGrid.Append(n.tk.uiControl, + n.tk.gridX, n.tk.gridY, 1, 1, + false, ui.AlignFill, false, ui.AlignFill) + return true + case toolkit.Group: + if (p.tk.uiBox == nil) { + p.tk.uiGroup.SetChild(n.tk.uiControl) + log(logInfo, "place() hack Group to use this as the box?", n.Name, n.WidgetType) + p.tk.uiBox = n.tk.uiBox + } else { + p.tk.uiBox.Append(n.tk.uiControl, stretchy) + } + return true + case toolkit.Tab: + if (p.tk.uiTab == nil) { + log(logError, "p.tk.uiTab == nil for n.WidgetId =", n.WidgetId, "p.tk =", p.tk) + panic("p.tk.uiTab == nil") + } + if (n.tk.uiControl == nil) { + log(logError, "n.tk.uiControl == nil for n.WidgetId =", n.WidgetId, "n.tk =", n.tk) + panic("n.tk.uiControl == nil") + } + log(logError, "CHECK LOGIC ON THIS. APPENDING directly into a window without a tab") + // log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() node=", n.WidgetId, n.Name, n.Text, n.WidgetType) + // log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() on parent=", p.WidgetId, p.Name, p.Text, p.WidgetType) + // panic("n.tk.uiControl == nil") + p.tk.uiTab.Append(n.Text, n.tk.uiControl) + p.tk.boxC += 1 + return true + case toolkit.Box: + log(logInfo, "place() uiBox =", p.tk.uiBox) + log(logInfo, "place() uiControl =", n.tk.uiControl) + p.tk.uiBox.Append(n.tk.uiControl, stretchy) + p.tk.boxC += 1 + return true + case toolkit.Window: + p.tk.uiWindow.SetChild(n.tk.uiControl) + return true + default: + log(debugError, "place() how? Parent =", p.WidgetId, p.WidgetType) + } + return false +} diff --git a/andlabs/box.go b/andlabs/box.go new file mode 100644 index 0000000..e33b7a1 --- /dev/null +++ b/andlabs/box.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +// make new Box here +func (p *node) newBox(n *node) { + log(debugToolkit, "newBox()", n.Name) + + newt := new(guiWidget) + var box *ui.Box + + log(debugToolkit, "rawBox() create", n.Name) + + if (n.B) { + box = ui.NewHorizontalBox() + } else { + box = ui.NewVerticalBox() + } + box.SetPadded(padded) + + newt.uiBox = box + newt.uiControl = box + newt.boxC = 0 + n.tk = newt + p.place(n) +} diff --git a/andlabs/button.go b/andlabs/button.go new file mode 100644 index 0000000..1dbad5c --- /dev/null +++ b/andlabs/button.go @@ -0,0 +1,31 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (p *node) newButton(n *node) { + log(debugToolkit, "newButton() START", n.Name) + + t := p.tk + if (t == nil) { + log(debugToolkit, "newButton() toolkit struct == nil. name=", n.Name) + return + } + + newt := new(guiWidget) + + b := ui.NewButton(n.Text) + newt.uiButton = b + newt.uiControl = b + newt.parent = t + + b.OnClicked(func(*ui.Button) { + n.doUserEvent() + }) + + n.tk = newt + p.place(n) + log(debugToolkit, "newButton() END", n.Name) +} diff --git a/andlabs/checkbox.go b/andlabs/checkbox.go new file mode 100644 index 0000000..4c37fd6 --- /dev/null +++ b/andlabs/checkbox.go @@ -0,0 +1,27 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (p *node) newCheckbox(n *node) { + newt := new(guiWidget) + log(debugToolkit, "newCheckbox()", n.Name, n.WidgetType) + + newt.uiCheckbox = ui.NewCheckbox(n.Text) + newt.uiControl = newt.uiCheckbox + + newt.uiCheckbox.OnToggled(func(spin *ui.Checkbox) { + n.B = newt.checked() + log(debugChange, "val =", n.B) + n.doUserEvent() + }) + + n.tk = newt + p.place(n) +} + +func (t *guiWidget) checked() bool { + return t.uiCheckbox.Checked() +} diff --git a/andlabs/combobox.go b/andlabs/combobox.go new file mode 100644 index 0000000..283a29d --- /dev/null +++ b/andlabs/combobox.go @@ -0,0 +1,41 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (p *node) newCombobox(n *node) { + newt := new(guiWidget) + log(debugToolkit, "newCombobox() START", n.Name) + + cb := ui.NewEditableCombobox() + newt.uiEditableCombobox = cb + newt.uiControl = cb + + // initialize the index + newt.c = 0 + newt.val = make(map[int]string) + + cb.OnChanged(func(spin *ui.EditableCombobox) { + n.S = spin.Text() + n.doUserEvent() + }) + + n.tk = newt + p.place(n) +} + +func (t *guiWidget) AddComboboxName(title string) { + t.uiEditableCombobox.Append(title) + if (t.val == nil) { + log(debugToolkit, "make map didn't work") + return + } + t.val[t.c] = title + + // If this is the first menu added, set the dropdown to it + // if (t.c == 0) { + // } + t.c = t.c + 1 +} diff --git a/andlabs/common.go b/andlabs/common.go new file mode 120000 index 0000000..35417a1 --- /dev/null +++ b/andlabs/common.go @@ -0,0 +1 @@ +../nocui/common.go \ No newline at end of file diff --git a/andlabs/debug.go b/andlabs/debug.go new file mode 100644 index 0000000..c2ab2a2 --- /dev/null +++ b/andlabs/debug.go @@ -0,0 +1,168 @@ +package main + +import ( + "strconv" + "go.wit.com/gui/gui/toolkit" +) + +var defaultBehavior bool = true + +var bookshelf bool // do you want things arranged in the box like a bookshelf or a stack? +var canvas bool // if set to true, the windows are a raw canvas +var menubar bool // for windows +var stretchy bool // expand things like buttons to the maximum size +var padded bool // add space between things like buttons +var margin bool // add space around the frames of windows + +var debugToolkit bool = false +var debugChange bool = false +var debugPlugin bool = false +var debugAction bool = false +var debugFlags bool = false +var debugGrid bool = false +var debugNow bool = true +var debugError bool = true + +// This is important. This sets the defaults for the gui. Without this, there isn't correct padding, etc +func setDefaultBehavior(s bool) { + defaultBehavior = s + if (defaultBehavior) { + log(debugToolkit, "Setting this toolkit to use the default behavior.") + log(debugToolkit, "This is the 'guessing' part as defined by the wit/gui 'Principles'. Refer to the docs.") + stretchy = false + padded = true + menubar = true + margin = true + canvas = false + bookshelf = true // 99% of the time, things make a vertical stack of objects + } else { + log(debugToolkit, "This toolkit is set to ignore the default behavior.") + } +} + +func ShowDebug () { + log(true, "debugToolkit =", debugToolkit) + log(true, "debugChange =", debugChange) + log(true, "debugAction =", debugPlugin) + log(true, "debugFlags =", debugFlags) + log(true, "debugNow =", debugNow) + log(true, "debugError =", debugError) +} + +func (t *guiWidget) Dump(b bool) { + if ! b { + return + } + log(b, "Name = ", t.Width, t.Height) + if (t.uiBox != nil) { + log(b, "uiBox =", t.uiBox) + } + if (t.uiButton != nil) { + log(b, "uiButton =", t.uiButton) + } + if (t.uiCombobox != nil) { + log(b, "uiCombobox =", t.uiCombobox) + } + if (t.uiWindow != nil) { + log(b, "uiWindow =", t.uiWindow) + } + if (t.uiTab != nil) { + log(b, "uiTab =", t.uiTab) + } + if (t.uiGroup != nil) { + log(b, "uiGroup =", t.uiGroup) + } + if (t.uiEntry != nil) { + log(b, "uiEntry =", t.uiEntry) + } + if (t.uiMultilineEntry != nil) { + log(b, "uiMultilineEntry =", t.uiMultilineEntry) + } + if (t.uiSlider != nil) { + log(b, "uiSlider =", t.uiSlider) + } + if (t.uiCheckbox != nil) { + log(b, "uiCheckbox =", t.uiCheckbox) + } +} + +/* +func GetDebugToolkit () bool { + return debugToolkit +} +*/ + +func flag(a *toolkit.Action) { + // should set the checkbox to this value + switch a.S { + case "Quiet": + logInfo = a.B + logVerbose = a.B + logWarn = a.B + logError = a.B + case "Error": + logError = a.B + case "Info": + logInfo = a.B + case "Verbose": + logInfo = a.B + logVerbose = a.B + logWarn = a.B + logError = a.B + debugToolkit = a.B + debugChange = a.B + debugPlugin = a.B + debugFlags = a.B + case "Toolkit": + debugToolkit = a.B + case "Change": + debugChange = a.B + case "Plugin": + debugPlugin = a.B + case "Flags": + debugFlags = a.B + case "Now": + debugNow = a.B + case "Show": + ShowDebug() + default: + log(debugError, "Can't set unknown flag", a.S) + } +} + +func (n *node) dumpWidget(b bool) { + var info, d string + + if (n == nil) { + log(debugError, "dumpWidget() node == nil") + return + } + info = n.WidgetType.String() + + d = strconv.Itoa(n.WidgetId) + " " + info + " " + n.Name + + var tabs string + for i := 0; i < listChildrenDepth; i++ { + tabs = tabs + defaultPadding + } + log(b, tabs + d) +} + +var defaultPadding string = " " +var listChildrenDepth int = 0 + +func (n *node) listChildren(dump bool) { + if (n == nil) { + return + } + + n.dumpWidget(dump) + if len(n.children) == 0 { + return + } + for _, child := range n.children { + listChildrenDepth += 1 + child.listChildren(dump) + listChildrenDepth -= 1 + } +} diff --git a/andlabs/delete.go b/andlabs/delete.go new file mode 100644 index 0000000..591d75e --- /dev/null +++ b/andlabs/delete.go @@ -0,0 +1,51 @@ +package main + +// if you include more than just this import +// then your plugin might be doing something un-ideal (just a guess from 2023/02/27) +import "go.wit.com/gui/gui/toolkit" + +// delete the child widget from the parent +// p = parent, c = child +func (n *node) destroy() { + pId := n.parent.WidgetId + cId := n.WidgetId + log(logNow, "delete()", pId, cId) + + pt := n.parent.tk + ct := n.tk + if (ct == nil) { + log(true, "delete FAILED (ct = mapToolkit[c] == nil) for c", pId, cId) + // this pukes out a whole universe of shit + // listMap() + return + } + + switch n.WidgetType { + case toolkit.Button: + log(true, "Should delete Button here:", n.Name) + log(true, "Parent:") + pt.Dump(true) + log(true, "Child:") + ct.Dump(true) + if (pt.uiBox == nil) { + log(true, "Don't know how to destroy this") + } else { + log(true, "Fuck it, destroy the whole box", n.parent.Name) + // pt.uiBox.Destroy() // You have a bug: You cannot destroy a uiControl while it still has a parent. + pt.uiBox.SetPadded(false) + pt.uiBox.Delete(4) + ct.uiButton.Disable() + // ct.uiButton.Hide() + ct.uiButton.Destroy() + } + + case toolkit.Window: + log(true, "Should delete Window here:", n.Name) + default: + log(true, "Fuckit, let's destroy a button") + if (ct.uiButton != nil) { + pt.uiBox.Delete(4) + ct.uiButton.Destroy() + } + } +} diff --git a/andlabs/demo.go b/andlabs/demo.go new file mode 100644 index 0000000..c3cd418 --- /dev/null +++ b/andlabs/demo.go @@ -0,0 +1,90 @@ +package main + +import "github.com/andlabs/ui" +import _ "github.com/andlabs/ui/winmanifest" + +/* + This is a code example taken directly from the toolkit andlabs/ui + + This code is here to double check that the toolkit itself still works + the same way. This is intended as a sanity check. +*/ + +func BlankWindow(w *ui.Window) *ui.Box { + hbox := ui.NewHorizontalBox() + hbox.SetPadded(true) + w.SetChild(hbox) + return hbox +} + +func (t *guiWidget) DemoNumbersPage() { + var w *ui.Window + + log(debugToolkit, "Starting wit/gui toolkit andlabs/ui DemoNumbersPage()") + + w = t.uiWindow + t.uiBox = makeNumbersPage() + t.uiBox.SetPadded(true) + w.SetChild(t.uiBox) + w.SetTitle("Internal demo of andlabs/ui toolkit") +} + +func makeNumbersPage() *ui.Box { + hbox := ui.NewHorizontalBox() + hbox.SetPadded(true) + + group := ui.NewGroup("Numbers") + group.SetMargined(true) + hbox.Append(group, true) + + vbox := ui.NewVerticalBox() + vbox.SetPadded(true) + group.SetChild(vbox) + + spinbox := ui.NewSpinbox(0, 100) + slider := ui.NewSlider(0, 100) + pbar := ui.NewProgressBar() + spinbox.OnChanged(func(*ui.Spinbox) { + slider.SetValue(spinbox.Value()) + pbar.SetValue(spinbox.Value()) + }) + slider.OnChanged(func(*ui.Slider) { + spinbox.SetValue(slider.Value()) + pbar.SetValue(slider.Value()) + }) + vbox.Append(spinbox, false) + vbox.Append(slider, false) + vbox.Append(pbar, false) + + ip := ui.NewProgressBar() + ip.SetValue(-1) + vbox.Append(ip, false) + + group = ui.NewGroup("Lists") + group.SetMargined(true) + hbox.Append(group, true) + + vbox = ui.NewVerticalBox() + vbox.SetPadded(true) + group.SetChild(vbox) + + cbox := ui.NewCombobox() + cbox.Append("Combobox Item 1") + cbox.Append("Combobox Item 2") + cbox.Append("Combobox Item 3") + vbox.Append(cbox, false) + + ecbox := ui.NewEditableCombobox() + ecbox.Append("Editable Item 1") + ecbox.Append("Editable Item 2") + ecbox.Append("Editable Item 3") + vbox.Append(ecbox, false) + + rb := ui.NewRadioButtons() + rb.Append("Radio Button 1") + rb.Append("Radio Button 2") + rb.Append("Radio Button 3") + vbox.Append(rb, false) + + return hbox +} diff --git a/andlabs/dropdown.go b/andlabs/dropdown.go new file mode 100644 index 0000000..89cbf1a --- /dev/null +++ b/andlabs/dropdown.go @@ -0,0 +1,79 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" + + "go.wit.com/gui/gui/toolkit" +) + +func (p *node) newDropdown(n *node) { + newt := new(guiWidget) + log(debugToolkit, "gui.Toolbox.newDropdown() START", n.Name) + + cb := ui.NewCombobox() + newt.uiCombobox = cb + newt.uiControl = cb + + // initialize the index + newt.c = 0 + newt.val = make(map[int]string) + + cb.OnSelected(func(spin *ui.Combobox) { + i := spin.Selected() + if (newt.val == nil) { + log(logError, "make map didn't work") + n.S = "map did not work. ui.Combobox error" + } else { + n.S = newt.val[i] + } + n.doUserEvent() + }) + + n.tk = newt + p.place(n) +} + +func (t *guiWidget) addDropdownName(title string) { + t.uiCombobox.Append(title) + if (t.val == nil) { + log(debugToolkit, "make map didn't work") + return + } + t.val[t.c] = title + + // If this is the first menu added, set the dropdown to it + if (t.c == 0) { + log(debugChange, "THIS IS THE FIRST Dropdown", title) + t.uiCombobox.SetSelected(0) + } + t.c = t.c + 1 +} + +func (t *guiWidget) SetDropdown(i int) { + t.uiCombobox.SetSelected(i) +} + +func (n *node) AddDropdownName(s string) { + log(logInfo, "AddDropdownName()", n.WidgetId, "add:", s) + + t := n.tk + if (t == nil) { + log(logInfo, "AddDropdownName() toolkit struct == nil. name=", n.Name, s) + return + } + t.addDropdownName(s) +} + +func (n *node) SetDropdownName(a *toolkit.Action, s string) { + log(logInfo, "SetDropdown()", n.WidgetId, ",", s) + + t := n.tk + if (t == nil) { + log(debugError, "SetDropdown() FAILED mapToolkits[w] == nil. name=", n.WidgetId, s) + return + } + t.SetDropdown(1) + // TODO: send back to wit/gui goroutine with the chan + n.S = s +} diff --git a/andlabs/grid.go b/andlabs/grid.go new file mode 100644 index 0000000..6c47d1b --- /dev/null +++ b/andlabs/grid.go @@ -0,0 +1,25 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +// Grid numbering by (X,Y) +// ----------------------------- +// -- (1,1) -- (2,1) -- (3,1) -- +// -- (1,2) -- (2,1) -- (3,1) -- +// ----------------------------- +func (p *node) newGrid(n *node) { + var newt *guiWidget + log(debugToolkit, "newGrid()", n.WidgetId, "to", n.ParentId) + + newt = new(guiWidget) + + c := ui.NewGrid() + newt.uiGrid = c + newt.uiControl = c + + n.tk = newt + p.place(n) +} diff --git a/andlabs/group.go b/andlabs/group.go new file mode 100644 index 0000000..b7c450e --- /dev/null +++ b/andlabs/group.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (p *node) newGroup(n *node) { + log(debugToolkit, "NewGroup()", n.Name) + + newt := new(guiWidget) + + log(debugToolkit, "NewGroup() create", n.Name) + + g := ui.NewGroup(n.Name) + g.SetMargined(margin) + newt.uiGroup = g + newt.uiControl = g + + n.tk = newt + p.place(n) +} diff --git a/andlabs/icon.go b/andlabs/icon.go new file mode 100644 index 0000000..00c25f6 --- /dev/null +++ b/andlabs/icon.go @@ -0,0 +1,27 @@ +package main + +var rawImage = []byte{ + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0xf3, 0xff, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0xca, 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0xa5, 0x93, 0xb1, 0x0d, + 0xc2, 0x40, 0x0c, 0x45, 0x1d, 0xc4, 0x14, 0x0c, 0x12, 0x41, 0x0f, 0x62, + 0x12, 0x46, 0x80, 0x8a, 0x2e, 0x15, 0x30, 0x02, 0x93, 0x20, 0x68, 0x11, + 0x51, 0x06, 0x61, 0x0d, 0x88, 0x2d, 0x7f, 0xdb, 0x07, 0x87, 0x08, 0xdc, + 0x49, 0x91, 0x7d, 0xf6, 0xf7, 0xf3, 0x4f, 0xa4, 0x54, 0xbb, 0xeb, 0xf6, + 0x41, 0x05, 0x67, 0xcc, 0xb3, 0x9b, 0xfa, 0xf6, 0x17, 0x62, 0xdf, 0xcd, + 0x48, 0x00, 0x32, 0xbd, 0xa8, 0x1d, 0x72, 0xee, 0x3c, 0x47, 0x16, 0xfb, + 0x5c, 0x53, 0x8d, 0x03, 0x30, 0x14, 0x84, 0xf7, 0xd5, 0x89, 0x26, 0xc7, + 0x25, 0x10, 0x36, 0xe4, 0x05, 0xa2, 0x51, 0xbc, 0xc4, 0x1c, 0xc3, 0x1c, + 0xed, 0x30, 0x1c, 0x8f, 0x16, 0x3f, 0x02, 0x78, 0x33, 0x20, 0x06, 0x60, + 0x97, 0x70, 0xaa, 0x45, 0x7f, 0x85, 0x60, 0x5d, 0xb6, 0xf4, 0xc2, 0xc4, + 0x3e, 0x0f, 0x44, 0xcd, 0x1b, 0x20, 0x90, 0x0f, 0xed, 0x85, 0xa8, 0x55, + 0x05, 0x42, 0x43, 0xb4, 0x9e, 0xce, 0x71, 0xb3, 0xe8, 0x0e, 0xb4, 0xc4, + 0xc3, 0x39, 0x21, 0xb7, 0x73, 0xbd, 0xe4, 0x1b, 0xe4, 0x04, 0xb6, 0xaa, + 0x4f, 0x18, 0x2c, 0xee, 0x42, 0x31, 0x01, 0x84, 0xfa, 0xe0, 0xd4, 0x00, + 0xdf, 0xb6, 0x83, 0xf8, 0xea, 0xc2, 0x00, 0x10, 0xfc, 0x1a, 0x05, 0x30, + 0x74, 0x3b, 0xe0, 0xd1, 0x45, 0xb1, 0x83, 0xaa, 0xf4, 0x77, 0x7e, 0x02, + 0x87, 0x1f, 0x42, 0x7f, 0x9e, 0x2b, 0xe8, 0xdf, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, +} diff --git a/andlabs/image.go b/andlabs/image.go new file mode 100644 index 0000000..bab1a9e --- /dev/null +++ b/andlabs/image.go @@ -0,0 +1,50 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +// make new Image using andlabs/ui +func (p *node) newImage(n *node) { + newt := new(guiWidget) + var img *ui.Image + + log(debugToolkit, "rawImage() create", n.Name) + + img = ui.NewImage(16, 16) + + newt.uiImage = img + // newt.uiControl = img + + n.tk = newt + p.place(n) +} +/* + if (a.Name == "image") { + log(true, "NewTextbox() trying to add a new image") + i := ui.NewImage(16, 16) + img, _, err := image.Decode(bytes.NewReader(rawImage)) + if err != nil { + panic(err) + } + nr, ok := img.(*image.RGBA) + if !ok { + i2 := image.NewRGBA(img.Bounds()) + draw.Draw(i2, i2.Bounds(), img, img.Bounds().Min, draw.Src) + nr = i2 + } + i.Append(nr) + t.uiBox.Append(i, true) + + var img *ui.Image + var icon []byte + var imgA image.Image + + icon, _ = res.ReadFile("resources/ping6.working.png") + // imgA, _, err := image.Decode(bytes.NewReader(b)) + imgA, _, _ = image.Decode(icon) + img.Append(imgA) + img.Append(icon) + } +*/ diff --git a/andlabs/label.go b/andlabs/label.go new file mode 100644 index 0000000..3f06546 --- /dev/null +++ b/andlabs/label.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (p *node) newLabel(n *node) { + log(logInfo, "NewLabel()", n.Name) + + newt := new(guiWidget) + c := ui.NewLabel(n.Name) + newt.uiLabel = c + newt.uiControl = c + + n.tk = newt + p.place(n) +} diff --git a/andlabs/log.go b/andlabs/log.go new file mode 100644 index 0000000..5f5b81b --- /dev/null +++ b/andlabs/log.go @@ -0,0 +1,24 @@ +package main + +import ( + witlog "go.wit.com/log" +) + +// various debugging flags +var logNow bool = true // useful for active development +var logError bool = true +var logWarn bool = true +var logInfo bool = false +var logVerbose bool = false + +func log(b bool, a ...any) { + witlog.Log(b, a...) +} + +func sleep(a ...any) { + witlog.Sleep(a...) +} + +func exit(a ...any) { + witlog.Exit(a...) +} diff --git a/andlabs/main.go b/andlabs/main.go new file mode 100644 index 0000000..c1fc7ac --- /dev/null +++ b/andlabs/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "sync" + "go.wit.com/gui/gui/toolkit" + + "github.com/andlabs/ui" + // the _ means we only need this for the init() + _ "github.com/andlabs/ui/winmanifest" +) + +var uiMainUndef bool = true +var uiMain sync.Once +var muAction sync.Mutex + +func catchActionChannel() { + log(logInfo, "catchActionChannel() START") + for { + log(logInfo, "catchActionChannel() for loop") + select { + case a := <-pluginChan: + log(logInfo, "catchActionChannel() SELECT widget id =", a.WidgetId, a.Name) + log(logInfo, "catchActionChannel() STUFF", a.WidgetId, a.ActionType, a.WidgetType) + muAction.Lock() + // TODO ui.QueueMain(f) + // TODO ui.QueueMain( func() {rawAction(a)} ) + ui.QueueMain( func() {rawAction(&a)} ) + // rawAction(a) + muAction.Unlock() + log(logInfo, "catchActionChannel() STUFF END", a.WidgetId, a.ActionType, a.WidgetType) + } + } +} + +// This is important. This sets the defaults for the gui. Without this, there isn't correct padding, etc +func init() { + log(logNow, "Init() START") + log(debugToolkit, "Init()") + // Can you pass values to a plugin init() ? Otherwise, there is no way to safely print + // log(debugToolkit, "init() Setting defaultBehavior = true") + setDefaultBehavior(true) + + + // TODO: this is messed up. run ui.Main() from the first add? Initialize it with an empty thing first? + // fake out the OS toolkit by making a fake window. This is probably needed for macos & windows + // actually, this probably breaks the macos build + go ui.Main(func() { + demoUI() + }) + + // andlabs = make(map[int]*andlabsT) + pluginChan = make(chan toolkit.Action, 1) + + log(logNow, "Init() start channel reciever") + go catchActionChannel() + log(logNow, "Init() END") +} diff --git a/andlabs/setText.go b/andlabs/setText.go new file mode 100644 index 0000000..7452982 --- /dev/null +++ b/andlabs/setText.go @@ -0,0 +1,128 @@ +package main + +import ( + "go.wit.com/gui/gui/toolkit" +) + +func (n *node) setText(a *toolkit.Action) { + log(debugChange, "setText() START with a.S =", a.S) + t := n.tk + if (t == nil) { + log(debugError, "setText error. tk == nil", n.Name, n.WidgetId) + actionDump(debugError, a) + return + } + log(debugChange, "setText() Attempt on", n.WidgetType, "with", a.S) + + switch n.WidgetType { + case toolkit.Window: + t.uiWindow.SetTitle(a.S) + case toolkit.Tab: + case toolkit.Group: + t.uiGroup.SetTitle(a.S) + case toolkit.Checkbox: + switch a.ActionType { + case toolkit.SetText: + t.uiCheckbox.SetText(a.S) + case toolkit.Get: + n.B = t.uiCheckbox.Checked() + case toolkit.Set: + // TODO: commented out while working on chan + t.uiCheckbox.SetChecked(a.B) + default: + log(debugError, "setText() unknown", a.ActionType, "on checkbox", n.Name) + } + case toolkit.Textbox: + switch a.ActionType { + case toolkit.Set: + if (t.uiEntry != nil) { + t.uiEntry.SetText(a.S) + } + if (t.uiMultilineEntry != nil) { + t.uiMultilineEntry.SetText(a.S) + } + case toolkit.SetText: + if (t.uiEntry != nil) { + t.uiEntry.SetText(a.S) + } + if (t.uiMultilineEntry != nil) { + t.uiMultilineEntry.SetText(a.S) + } + default: + log(debugError, "setText() unknown", a.ActionType, "on checkbox", n.Name) + } + case toolkit.Label: + t.uiLabel.SetText(a.S) + case toolkit.Button: + t.uiButton.SetText(a.S) + case toolkit.Slider: + switch a.ActionType { + case toolkit.Get: + n.I = t.uiSlider.Value() + case toolkit.Set: + t.uiSlider.SetValue(a.I) + default: + log(debugError, "setText() unknown", a.ActionType, "on checkbox", n.Name) + } + case toolkit.Spinner: + switch a.ActionType { + case toolkit.Get: + n.I = t.uiSpinbox.Value() + case toolkit.Set: + t.uiSpinbox.SetValue(a.I) + default: + log(debugError, "setText() unknown", a.ActionType, "on checkbox", n.Name) + } + case toolkit.Dropdown: + switch a.ActionType { + case toolkit.AddText: + n.AddDropdownName(a.S) + case toolkit.Set: + var orig int + var i int = -1 + var s string + orig = t.uiCombobox.Selected() + log(debugChange, "try to set the Dropdown to", a.S, "from", orig) + // try to find the string + for i, s = range t.val { + log(debugChange, "i, s", i, s) + if (a.S == s) { + t.uiCombobox.SetSelected(i) + log(debugChange, "setText() Dropdown worked.", n.S) + return + } + } + log(debugError, "setText() Dropdown did not find:", a.S) + // if i == -1, then there are not any things in the menu to select + if (i == -1) { + return + } + // if the string was never set, then set the dropdown to the last thing added to the menu + if (orig == -1) { + t.uiCombobox.SetSelected(i) + } + case toolkit.Get: + // t.S = t.s + case toolkit.GetText: + // t.S = t.s + default: + log(debugError, "setText() unknown", a.ActionType, "on checkbox", n.Name) + } + case toolkit.Combobox: + switch a.ActionType { + case toolkit.AddText: + t.AddComboboxName(a.S) + case toolkit.Set: + t.uiEditableCombobox.SetText(a.S) + n.S = a.S + case toolkit.SetText: + t.uiEditableCombobox.SetText(a.S) + n.S = a.S + default: + log(debugError, "setText() unknown", a.ActionType, "on checkbox", n.Name) + } + default: + log(debugError, "plugin Send() Don't know how to setText on", n.WidgetType, "yet", a.ActionType) + } + log(debugChange, "setText() END with a.S =", a.S) +} diff --git a/andlabs/slider.go b/andlabs/slider.go new file mode 100644 index 0000000..5098943 --- /dev/null +++ b/andlabs/slider.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (p *node) newSlider(n *node) { + newt := new(guiWidget) + + s := ui.NewSlider(n.X, n.Y) + newt.uiSlider = s + newt.uiControl = s + + s.OnChanged(func(spin *ui.Slider) { + n.I = newt.uiSlider.Value() + n.doUserEvent() + }) + + n.tk = newt + p.place(n) +} diff --git a/andlabs/spinner.go b/andlabs/spinner.go new file mode 100644 index 0000000..bab7a29 --- /dev/null +++ b/andlabs/spinner.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (p *node) newSpinner(n *node) { + newt := new(guiWidget) + + s := ui.NewSpinbox(n.X, n.Y) + newt.uiSpinbox = s + newt.uiControl = s + + s.OnChanged(func(s *ui.Spinbox) { + n.I = newt.uiSpinbox.Value() + n.doUserEvent() + }) + + n.tk = newt + p.place(n) +} diff --git a/andlabs/structs.go b/andlabs/structs.go new file mode 100644 index 0000000..81c4669 --- /dev/null +++ b/andlabs/structs.go @@ -0,0 +1,58 @@ +package main + +import "github.com/andlabs/ui" +import _ "github.com/andlabs/ui/winmanifest" + +// var andlabs map[int]*andlabsT +// var callback func(int) bool +// var callback chan toolkit.Action + +// It's probably a terrible idea to call this 'me' +var me config + +type config struct { + rootNode *node // the base of the binary tree. it should have id == 0 +} + +// stores the raw toolkit internals +type guiWidget struct { + Width int + Height int + + // tw *toolkit.Widget + parent *guiWidget + children []*guiWidget + + // used to track if a tab has a child widget yet + child bool + + uiControl ui.Control + + uiBox *ui.Box + uiButton *ui.Button + uiCombobox *ui.Combobox + uiCheckbox *ui.Checkbox + uiEntry *ui.Entry + uiGroup *ui.Group + uiLabel *ui.Label + uiSlider *ui.Slider + uiSpinbox *ui.Spinbox + uiTab *ui.Tab + uiWindow *ui.Window + uiMultilineEntry *ui.MultilineEntry + uiEditableCombobox *ui.EditableCombobox + uiImage *ui.Image + + uiGrid *ui.Grid + gridX int + gridY int + + // used as a counter to work around limitations of widgets like combobox + // this is probably fucked up and in many ways wrong because of unsafe goroutine threading + // but it's working for now due to the need for need for a correct interaction layer betten toolkits + c int + val map[int]string + + // andlabs/ui only accesses widget id numbers + boxC int // how many things on in a box or how many tabs +} diff --git a/andlabs/tab.go b/andlabs/tab.go new file mode 100644 index 0000000..d075a51 --- /dev/null +++ b/andlabs/tab.go @@ -0,0 +1,116 @@ +package main + +import ( + "go.wit.com/gui/gui/toolkit" + + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +/* + This adds a tab + + andlabs/ui is goofy in the sense that you have to determine + if the ui.Window already has a tab in it. If it does, then + you need to add this tab and not run SetChild() on the window + or instead it replaces the existing tab with the new one + + I work around this by always sending a Toolkit that is a tab + once there is one. If you send a Window here, it will replace + any existing tabs rather than adding a new one +*/ +func (p *node) newTab(n *node) { + var newt *guiWidget + + if (p == nil) { + log(debugError, "newTab() p == nil. how the fuck does this happen?", n.WidgetId, n.ParentId) + } + if (p.WidgetType != toolkit.Window) { + log(debugError, "newTab() uiWindow == nil. I can't add a toolbar without window", n.WidgetId, n.ParentId) + return + } + t := p.tk + + log(debugToolkit, "newTab() START", n.WidgetId, n.ParentId) + + if (t.uiTab == nil) { + // this means you have to make a new tab + log(debugToolkit, "newTab() GOOD. This should be the first tab:", n.WidgetId, n.ParentId) + newt = rawTab(t.uiWindow, n.Text) + t.uiTab = newt.uiTab + } else { + // this means you have to append a tab + log(debugToolkit, "newTab() GOOD. This should be an additional tab:", n.WidgetId, n.ParentId) + if (n.WidgetType == toolkit.Tab) { + // andlabs doesn't have multiple tab widgets so make a fake one? + // this makes a guiWidget internal structure with the parent values + newt = new(guiWidget) + newt.uiWindow = t.uiWindow + newt.uiTab = t.uiTab + } else { + newt = t.appendTab(n.Text) + } + } + + n.tk = newt +} + +// This sets _all_ the tabs to Margin = true +// +// TODO: do proper tab tracking (will be complicated). low priority +func tabSetMargined(tab *ui.Tab, b bool) { + c := tab.NumPages() + for i := 0; i < c; i++ { + log(debugToolkit, "SetMargined", i, b) + tab.SetMargined(i, b) + } +} + +func rawTab(w *ui.Window, name string) *guiWidget { + var newt guiWidget + log(debugToolkit, "rawTab() START", name) + + if (w == nil) { + log(debugError, "UiWindow == nil. I can't add a tab without a window") + log(debugError, "UiWindow == nil. I can't add a tab without a window") + log(debugError, "UiWindow == nil. I can't add a tab without a window") + // sleep(1) + return nil + } + + tab := ui.NewTab() + w.SetChild(tab) + newt.uiTab = tab + newt.uiControl = tab + log(debugToolkit, "rawTab() END", name) + return &newt +} + +func (t *guiWidget) appendTab(name string) *guiWidget { + var newT guiWidget + log(debugToolkit, "appendTab() ADD", name) + + if (t.uiTab == nil) { + log(debugToolkit, "UiWindow == nil. I can't add a widget without a place to put it") + panic("should never have happened. wit/gui/toolkit has ui.Tab == nil") + } + log(debugToolkit, "appendTab() START name =", name) + + var hbox *ui.Box + if (defaultBehavior) { + hbox = ui.NewHorizontalBox() + } else { + if (bookshelf) { + hbox = ui.NewHorizontalBox() + } else { + hbox = ui.NewVerticalBox() + } + } + hbox.SetPadded(padded) + t.uiTab.Append(name, hbox) + + newT.uiWindow = t.uiWindow + newT.uiTab = t.uiTab + newT.uiBox = hbox + return &newT +} diff --git a/andlabs/textbox.go b/andlabs/textbox.go new file mode 100644 index 0000000..7cb5d63 --- /dev/null +++ b/andlabs/textbox.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (p *node) newTextbox(n *node) { + newt := new(guiWidget) + + if (n.X == 1) { + e := ui.NewEntry() + newt.uiEntry = e + newt.uiControl = e + + e.OnChanged(func(spin *ui.Entry) { + n.S = spin.Text() + n.doUserEvent() + }) + } else { + e := ui.NewNonWrappingMultilineEntry() + newt.uiMultilineEntry = e + newt.uiControl = e + + e.OnChanged(func(spin *ui.MultilineEntry) { + n.S = spin.Text() + n.doUserEvent() + }) + } + n.tk = newt + p.place(n) +} diff --git a/andlabs/updateui.go b/andlabs/updateui.go new file mode 100644 index 0000000..c43e15f --- /dev/null +++ b/andlabs/updateui.go @@ -0,0 +1,97 @@ +package main + +import ( + "github.com/andlabs/ui" +) + +// Example showing how to update the UI using the QueueMain function +// especially if the update is coming from another goroutine +// +// see QueueMain in 'main.go' for detailed description + +var count int + +func demoUI() { + mainWindow := ui.NewWindow("libui Updating UI", 640, 480, true) + mainWindow.OnClosing(func(*ui.Window) bool { + ui.Quit() + return true + }) + ui.OnShouldQuit(func() bool { + mainWindow.Destroy() + return true + }) + + vbContainer := ui.NewVerticalBox() + vbContainer.SetPadded(true) + + inputGroup := ui.NewGroup("Input") + inputGroup.SetMargined(true) + + vbInput := ui.NewVerticalBox() + vbInput.SetPadded(true) + + inputForm := ui.NewForm() + inputForm.SetPadded(true) + + message := ui.NewEntry() + message.SetText("Hello World") + inputForm.Append("What message do you want to show?", message, false) + + showMessageButton := ui.NewButton("Show message") + clearMessageButton := ui.NewButton("Clear message") + + vbInput.Append(inputForm, false) + vbInput.Append(showMessageButton, false) + vbInput.Append(clearMessageButton, false) + + inputGroup.SetChild(vbInput) + + messageGroup := ui.NewGroup("Message") + messageGroup.SetMargined(true) + + vbMessage := ui.NewVerticalBox() + vbMessage.SetPadded(true) + + messageLabel := ui.NewLabel("") + + vbMessage.Append(messageLabel, false) + + messageGroup.SetChild(vbMessage) + + countGroup := ui.NewGroup("Counter") + countGroup.SetMargined(true) + + vbCounter := ui.NewVerticalBox() + vbCounter.SetPadded(true) + + countLabel := ui.NewLabel("blah") + + vbCounter.Append(countLabel, false) + countGroup.SetChild(vbCounter) + + vbContainer.Append(inputGroup, false) + vbContainer.Append(messageGroup, false) + vbContainer.Append(countGroup, false) + + mainWindow.SetChild(vbContainer) + + showMessageButton.OnClicked(func(*ui.Button) { + // Update the UI directly as it is called from the main thread + messageLabel.SetText(message.Text()) + }) + + clearMessageButton.OnClicked(func(*ui.Button) { + // Update the UI directly as it is called from the main thread + messageLabel.SetText("") + }) + + // this is messed up. + // mainWindow.Show() +} + +/* +func main() { + ui.Main(setupUI) +} +*/ diff --git a/andlabs/widget.go b/andlabs/widget.go new file mode 100644 index 0000000..cf05a9e --- /dev/null +++ b/andlabs/widget.go @@ -0,0 +1,29 @@ +package main + +import ( + "go.wit.com/gui/gui/toolkit" +) + +// this is specific to the nocui toolkit +func initWidget(n *node) *guiWidget { + var w *guiWidget + w = new(guiWidget) + // Set(w, "default") + + if n.WidgetType == toolkit.Root { + log(logInfo, "setupWidget() FOUND ROOT w.id =", n.WidgetId) + n.WidgetId = 0 + me.rootNode = n + return w + } + + if (n.WidgetType == toolkit.Box) { + if (n.B) { + n.horizontal = true + } else { + n.horizontal = false + } + } + + return w +} diff --git a/andlabs/window.go b/andlabs/window.go new file mode 100644 index 0000000..f51536b --- /dev/null +++ b/andlabs/window.go @@ -0,0 +1,46 @@ +package main + +import ( + "github.com/andlabs/ui" + _ "github.com/andlabs/ui/winmanifest" +) + +func (t *guiWidget) MessageWindow(msg1 string, msg2 string) { + ui.MsgBox(t.uiWindow, msg1, msg2) +} + +func (t *guiWidget) ErrorWindow(msg1 string, msg2 string) { + ui.MsgBoxError(t.uiWindow, msg1, msg2) +} + +func newWindow(n *node) { + var newt *guiWidget + + newt = new(guiWidget) + + // menubar bool is if the OS defined border on the window should be used + win := ui.NewWindow(n.Name, n.X, n.Y, menubar) + win.SetBorderless(canvas) + win.SetMargined(margin) + win.OnClosing(func(*ui.Window) bool { + n.doUserEvent() + return true + }) + newt.uiWindow = win + newt.uiControl = win + + n.tk = newt + win.Show() + return +} + +func (n *node) SetWindowTitle(title string) { + log(debugToolkit, "toolkit NewWindow", n.Text, "title", title) + win := n.tk.uiWindow + if (win == nil) { + log(debugError, "Error: no window", n.WidgetId) + } else { + win.SetTitle(title) + log(debugToolkit, "Setting the window title", title) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ef8e734 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module go.wit.com/toolkits + +go 1.21.4 + +require ( + github.com/andlabs/ui v0.0.0-20200610043537-70a69d6ae31e + github.com/awesome-gocui/gocui v1.1.0 + go.wit.com/gui v0.9.2 +) + +require ( + github.com/gdamore/encoding v1.0.0 // indirect + github.com/gdamore/tcell/v2 v2.4.0 // indirect + github.com/lucasb-eyer/go-colorful v1.0.3 // indirect + github.com/mattn/go-runewidth v0.0.10 // indirect + github.com/rivo/uniseg v0.1.0 // indirect + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect + golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect + golang.org/x/text v0.3.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..936d163 --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +github.com/andlabs/ui v0.0.0-20200610043537-70a69d6ae31e h1:wSQCJiig/QkoUnpvelSPbLiZNWvh2yMqQTQvIQqSUkU= +github.com/andlabs/ui v0.0.0-20200610043537-70a69d6ae31e/go.mod h1:5G2EjwzgZUPnnReoKvPWVneT8APYbyKkihDVAHUi0II= +github.com/awesome-gocui/gocui v1.1.0 h1:db2j7yFEoHZjpQFeE2xqiatS8bm1lO3THeLwE6MzOII= +github.com/awesome-gocui/gocui v1.1.0/go.mod h1:M2BXkrp7PR97CKnPRT7Rk0+rtswChPtksw/vRAESGpg= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM= +github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +go.wit.com/gui v0.9.2 h1:QHMYdwpV6MzKwmFUMGevKUDn2a6GAqHN2Ltx8V3HufI= +go.wit.com/gui v0.9.2/go.mod h1:asRXEYKmdjhtg1yiBi5A8YEY2YG4lWPS0gvNz4NXGDE= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/gocui/Makefile b/gocui/Makefile new file mode 100644 index 0000000..4f2a6ee --- /dev/null +++ b/gocui/Makefile @@ -0,0 +1,15 @@ +all: plugin + ldd ../gocui.so + +goget: + go get -v -t -u + +plugin: + go build -v -x -buildmode=plugin -o ../gocui.so + +objdump: + objdump -t ../gocui.so |less + +log: + reset + tail -f /tmp/witgui.* /tmp/guilogfile diff --git a/gocui/add.go b/gocui/add.go new file mode 100644 index 0000000..8e87f86 --- /dev/null +++ b/gocui/add.go @@ -0,0 +1,78 @@ +package main + +import ( + "go.wit.com/gui/gui/toolkit" +) + +var fakeStartWidth int = me.FakeW +var fakeStartHeight int = me.TabH + me.FramePadH +// setup fake labels for non-visible things off screen +func (n *node) setFake() { + w := n.tk + w.isFake = true + + n.gocuiSetWH(fakeStartWidth, fakeStartHeight) + + fakeStartHeight += w.gocuiSize.Height() + // TODO: use the actual max hight of the terminal window + if (fakeStartHeight > 24) { + fakeStartHeight = me.TabH + fakeStartWidth += me.FakeW + } + if (logInfo) { + n.showView() + } +} + +// set the widget start width & height +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 =", n.WidgetId, "w.name", n.Name) + nw.color = &colorRoot + n.setFake() + return + case toolkit.Flag: + nw.color = &colorFlag + n.setFake() + return + case toolkit.Window: + nw.frame = false + nw.color = &colorWindow + // redoWindows(0,0) + return + case toolkit.Tab: + nw.color = &colorTab + // redoWindows(0,0) + return + case toolkit.Button: + nw.color = &colorButton + case toolkit.Box: + nw.color = &colorBox + nw.isFake = true + n.setFake() + return + case toolkit.Grid: + nw.color = &colorGrid + nw.isFake = true + n.setFake() + return + case toolkit.Group: + nw.color = &colorGroup + nw.frame = false + return + case toolkit.Label: + nw.color = &colorLabel + nw.frame = false + return + default: + /* + if n.IsCurrent() { + n.updateCurrent() + } + */ + } + n.showWidgetPlacement(logInfo, "addWidget()") +} diff --git a/gocui/checkbox.go b/gocui/checkbox.go new file mode 100644 index 0000000..d8c6f4b --- /dev/null +++ b/gocui/checkbox.go @@ -0,0 +1,33 @@ +package main + +import ( +// "github.com/awesome-gocui/gocui" + "go.wit.com/gui/gui/toolkit" +) + +func (n *node) setCheckbox(b bool) { + w := n.tk + if (n.WidgetType != toolkit.Checkbox) { + return + } + if (b) { + n.B = b + n.Text = "X " + n.Name + } else { + n.B = b + n.Text = " " + n.Name + } + 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 + +// if w.frame { +// w.realWidth += me.FramePadW +// w.realHeight += me.FramePadH +// } + + n.deleteView() + n.showView() +} diff --git a/gocui/click.go b/gocui/click.go new file mode 100644 index 0000000..45823e3 --- /dev/null +++ b/gocui/click.go @@ -0,0 +1,356 @@ +package main + +import ( + "fmt" + "github.com/awesome-gocui/gocui" + "go.wit.com/gui/gui/toolkit" +) + +// set isCurrent = false everywhere +func unsetCurrent(n *node) { + w := n.tk + w.isCurrent = false + + if n.WidgetType == toolkit.Tab { + // n.tk.color = &colorTab + // n.setColor() + } + + for _, child := range n.children { + unsetCurrent(child) + } +} + +// 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 (n *node) updateCurrent() { + log(true, "updateCurrent()", n.Name) + if n.WidgetType == toolkit.Tab { + if n.IsCurrent() { + // n.tk.color = &colorActiveT + n.setColor(&colorActiveT) + n.hideView() + n.showView() + setCurrentTab(n) + } else { + // n.tk.color = &colorTab + // n.setColor() + } + return + } + if n.WidgetType == toolkit.Window { + if n.IsCurrent() { + // setCurrentWindow(n) + } + return + } + if n.WidgetType == toolkit.Root { + return + } + n.parent.updateCurrent() +} + +// shows the widgets in a window +func setCurrentWindow(n *node) { + if n.IsCurrent() { + return + } + w := n.tk + if n.WidgetType != toolkit.Window { + return + } + unsetCurrent(me.rootNode) + + if n.hasTabs { + // set isCurrent = true on the first tab + for _, child := range n.children { + child.tk.isCurrent = true + break + } + } else { + w.isCurrent = true + } +} + +// shows the widgets in a tab +func setCurrentTab(n *node) { + w := n.tk + if n.WidgetType != toolkit.Tab { + return + } + unsetCurrent(me.rootNode) + w.isCurrent = true + p := n.parent.tk + p.isCurrent = true + log(true, "setCurrent()", n.Name) +} + +func (n *node) doWidgetClick() { + switch n.WidgetType { + case toolkit.Root: + // THIS IS THE BEGINING OF THE LAYOUT + log(true, "doWidgetClick()", n.Name) + redoWindows(0,0) + case toolkit.Flag: + log(true, "doWidgetClick() FLAG widget name =", n.Name) + log(true, "doWidgetClick() if this is the dropdown menu, handle it here?") + case toolkit.Window: + if (me.currentWindow == n) { + return + } + if (me.currentWindow != nil) { + unsetCurrent(me.currentWindow) + me.currentWindow.setColor(&colorWindow) + me.currentWindow.hideWidgets() + } + n.hideWidgets() + me.currentWindow = n + // setCurrentWindow(n) // probably delete this + n.setColor(&colorActiveW) + n.redoTabs(me.TabW, me.TabH) + for _, child := range n.children { + if (child.currentTab == true) { + log(true, "FOUND CURRENT TAB", child.Name) + setCurrentTab(child) + child.placeWidgets(me.RawW, me.RawH) + child.showWidgets() + return + } + } + /* FIXME: redo this + if ! n.hasTabs { + } + */ + case toolkit.Tab: + if (n.IsCurrent()) { + return // do nothing if you reclick on the already selected tab + } + // find the window and disable the active tab + p := n.parent + if (p != nil) { + p.hideWidgets() + p.redoTabs(me.TabW, me.TabH) + unsetCurrent(p) + for _, child := range p.children { + if child.WidgetType == toolkit.Tab { + child.setColor(&colorTab) + n.currentTab = false + } + } + } + n.currentTab = true + n.setColor(&colorActiveT) + 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: + newR := n.realGocuiSize() + + // w,h := n.logicalSize() + // w := newR.w1 - newR.w0 + // h := newR.h1 - newR.h0 + + n.placeGrid(newR.w0, newR.h0) + n.showWidgets() + case toolkit.Box: + // w.showWidgetPlacement(logNow, "drawTree()") + if (n.horizontal) { + log(true, "BOX IS HORIZONTAL", n.Name) + } else { + log(true, "BOX IS VERTICAL", n.Name) + } + // n.placeWidgets() + n.toggleTree() + case toolkit.Button: + n.doUserEvent() + case toolkit.Dropdown: + log(true, "do the dropdown here") + if (me.ddview == nil) { + me.ddview = addDropdown() + tk := me.ddview.tk + tk.gocuiSize.w0 = 20 + tk.gocuiSize.w1 = 40 + tk.gocuiSize.h0 = 10 + tk.gocuiSize.h1 = 25 + tk.v, _ = me.baseGui.SetView("ddview", + tk.gocuiSize.w0, + tk.gocuiSize.h0, + tk.gocuiSize.w1, + tk.gocuiSize.h1, 0) + if (tk.v == nil) { + return + } + tk.v.Wrap = true + tk.v.Frame = true + tk.v.Clear() + fmt.Fprint(tk.v, "example.com\nwit.com") + me.ddview.SetVisible(true) + return + } + log(true, "doWidgetClick() visible =", me.ddview.Visible()) + if (me.ddview.Visible()) { + me.ddview.SetVisible(false) + me.baseGui.DeleteView("ddview") + me.ddview.tk.v = nil + } else { + var dnsList string + for i, s := range n.vals { + log(logNow, "AddText()", n.Name, i, s) + dnsList += s + "\n" + } + me.ddNode = n + log(logNow, "new dns list should be set to:", dnsList) + me.ddview.Text = dnsList + me.ddview.SetText(dnsList) + me.ddview.SetVisible(true) + } + for i, s := range n.vals { + log(logNow, "AddText()", n.Name, i, s) + } + default: + } +} + +var toggle bool = true +func (n *node) toggleTree() { + if (toggle) { + n.drawTree(toggle) + toggle = false + } else { + n.hideWidgets() + toggle = true + } +} + + +// display the widgets in the binary tree +func (n *node) drawTree(draw bool) { + w := n.tk + if (w == nil) { + return + } + n.showWidgetPlacement(logNow, "drawTree()") + if (draw) { + // w.textResize() + n.showView() + } else { + n.deleteView() + } + + for _, child := range n.children { + child.drawTree(draw) + } +} + +func click(g *gocui.Gui, v *gocui.View) error { + // var l string + // var err error + + 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) + if (n.Name == "DropBox") { + log(logNow, "click() this is the dropdown menu. set a flag here what did I click? where is the mouse?") + log(logNow, "click() set a global dropdown clicked flag=true here") + me.ddClicked = true + } + n.doWidgetClick() + } else { + log(logNow, "click() could not find node name =", v.Name()) + } + + if _, err := g.SetCurrentView(v.Name()); err != nil { + log(logNow, "click() END err =", err) + return err + } + + log(logVerbose, "click() END") + return nil +} + +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(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 + } + } + if (n == me.ddview) { + log(true, "findUnderMouse() found ddview") + if n.Visible() { + log(true, "findUnderMouse() and ddview is visable. hide it here. TODO: find highlighted row") + found = n + // find the actual value here and set the dropdown widget + me.baseGui.DeleteView("ddview") + } else { + log(true, "findUnderMouse() I was lying, actually it's not found") + } + } + + for _, child := range n.children { + f(child) + } + } + f(me.rootNode) + // 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, "findUnderMouse() FOUND") + } + 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() + if (me.ctrlDown == nil) { + setupCtrlDownWidget() + 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 + } + 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.hideView() + } else { + me.ctrlDown.showView() + } + me.ctrlDown.showWidgetPlacement(logNow, "ctrlDown:") + return nil +} diff --git a/gocui/color.go b/gocui/color.go new file mode 100644 index 0000000..c665bb6 --- /dev/null +++ b/gocui/color.go @@ -0,0 +1,117 @@ +package main + +import ( + "math/rand" + "github.com/awesome-gocui/gocui" +) + +//w.v.SelBgColor = gocui.ColorCyan +//color.go: w.v.SelFgColor = gocui.ColorBlack +//color.go: w.v.BgColor = gocui.ColorGreen + +type colorT struct { + frame gocui.Attribute + fg gocui.Attribute + bg gocui.Attribute + selFg gocui.Attribute + selBg gocui.Attribute + name string +} + +var none gocui.Attribute = gocui.AttrNone +var lightPurple gocui.Attribute = gocui.GetColor("#DDDDDD") // light purple +var darkPurple gocui.Attribute = gocui.GetColor("#FFAA55") // Dark Purple +var heavyPurple gocui.Attribute = gocui.GetColor("#88AA55") // heavy purple +var powdererBlue gocui.Attribute = gocui.GetColor("#B0E0E6") // w3c 'powerder blue' +var superLightGrey gocui.Attribute = gocui.GetColor("#55AAFF") // super light grey + +// Standard defined colors from gocui: +// ColorBlack ColorRed ColorGreen ColorYellow ColorBlue ColorMagenta ColorCyan ColorWhite + +// v.BgColor = gocui.GetColor("#111111") // crazy red +// v.BgColor = gocui.GetColor("#FF9911") // heavy red +// v.SelBgColor = gocui.GetColor("#FFEE11") // blood red + +// v.BgColor = gocui.GetColor("#55AAFF") // super light grey +// v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow + +// Normal Text On mouseover +// Widget Frame Text background Text background +var colorWindow colorT = colorT{ none , gocui.ColorBlue, none , none , powdererBlue , "normal window"} +var colorActiveW colorT = colorT{ none , none , powdererBlue , none , powdererBlue , "active window"} + +var colorTab colorT = colorT{gocui.ColorBlue, gocui.ColorBlue, none , none , powdererBlue , "normal tab"} +var colorActiveT colorT = colorT{gocui.ColorBlue, none , powdererBlue , none , powdererBlue , "active tab"} + +var colorButton colorT = colorT{gocui.ColorGreen, none , gocui.ColorWhite, gocui.ColorGreen, gocui.ColorBlack, "normal button"} +var colorLabel colorT = colorT{ none , none , superLightGrey , none , superLightGrey , "normal label"} +var colorGroup colorT = colorT{ none , none , superLightGrey , none , superLightGrey , "normal group"} + +// widget debugging colors. these widgets aren't displayed unless you are debugging +var colorRoot colorT = colorT{gocui.ColorRed , none , powdererBlue , none , gocui.ColorBlue, "debug root"} +var colorFlag colorT = colorT{gocui.ColorRed , none , powdererBlue , none , gocui.ColorGreen, "debug flag"} +var colorBox colorT = colorT{gocui.ColorRed , none , lightPurple , none , gocui.ColorCyan, "debug box"} +var colorGrid colorT = colorT{gocui.ColorRed , none , lightPurple , none , gocui.ColorRed, "debug grid"} +var colorNone colorT = colorT{ none , none , none , none , none , "debug none"} + +// actually sets the colors for the gocui element +// the user will see the colors change when this runs +// TODO: add black/white only flag for ttyS0 +// TODO: or fix kvm/qemu serial console & SIGWINCH. +// TODO: and minicom and uboot and 5 million other things. +// TODO: maybe enough of us could actually do that if we made it a goal. +// TODO: start with riscv boards and fix it universally there +// TODO: so just a small little 'todo' item here +func (n *node) setColor(newColor *colorT) { + tk := n.tk + if (tk.color == newColor) { + // nothing to do since the colors have nto changed + return + } + tk.color = newColor + if (tk.v == nil) { + return + } + if (tk.color == nil) { + log(true, "Set the node to color = nil") + tk.color = &colorNone + } + log(true, "Set the node to color =", tk.color.name) + n.recreateView() +} + +func (n *node) setDefaultWidgetColor() { + n.showView() +} + +func (n *node) setDefaultHighlight() { + w := n.tk + if (w.v == nil) { + log(logError, "SetColor() failed on view == nil") + return + } + w.v.SelBgColor = gocui.ColorGreen + w.v.SelFgColor = gocui.ColorBlack +} + +func randColor() gocui.Attribute { + colors := []string{"Green", "#FFAA55", "Yellow", "Blue", "Red", "Black", "White"} + i := rand.Intn(len(colors)) + log(true, "randColor() i =", i) + return gocui.GetColor(colors[i]) +} + +func (n *node) redoColor(draw bool) { + w := n.tk + if (w == nil) { + return + } + + sleep(.05) + n.setDefaultHighlight() + n.setDefaultWidgetColor() + + for _, child := range n.children { + child.redoColor(draw) + } +} diff --git a/gocui/common.go b/gocui/common.go new file mode 120000 index 0000000..35417a1 --- /dev/null +++ b/gocui/common.go @@ -0,0 +1 @@ +../nocui/common.go \ No newline at end of file diff --git a/gocui/debug.go b/gocui/debug.go new file mode 100644 index 0000000..db94e37 --- /dev/null +++ b/gocui/debug.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "go.wit.com/gui/gui/toolkit" +) + +func (n *node) dumpTree(draw bool) { + w := n.tk + if (w == nil) { + return + } + n.showWidgetPlacement(logNow, "dumpTree()") + + for _, child := range n.children { + child.dumpTree(draw) + } +} + +func (n *node) showWidgetPlacement(b bool, s string) { + if (n == nil) { + log(logError, "WTF w == nil") + return + } + 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 = n.parent.WidgetId + } + s1 = fmt.Sprintf("(wId,pId)=(%2d,%2d) ", n.WidgetId, pId) + 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) + } else { + s1 += fmt.Sprintf(" ") + } + if (n.parent != nil) { + if (n.parent.WidgetType == toolkit.Grid) { + s1 += fmt.Sprintf("At(%2d,%2d) ", n.AtW, n.AtH) + } + } + tmp := "." + n.Name + "." + log(b, s1, s, n.WidgetType, ",", tmp) // , "text=", w.text) +} + +func (n *node) dumpWidget(pad string) { + log(true, "node:", pad, n.WidgetId, "At(", n.AtW, n.AtH, ") ,", n.WidgetType, ", n.Name =", n.Name, ", n.Text =", n.Text) +} + +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/gocui/fakefile.go b/gocui/fakefile.go new file mode 100644 index 0000000..c6b8877 --- /dev/null +++ b/gocui/fakefile.go @@ -0,0 +1,58 @@ +package main + +import ( + "bytes" + "io" + "errors" +) + +type FakeFile struct { + reader *bytes.Reader + buffer *bytes.Buffer + offset int64 +} + +func (f *FakeFile) Read(p []byte) (n int, err error) { + n, err = f.reader.ReadAt(p, f.offset) + f.offset += int64(n) + return n, err +} + +func (f *FakeFile) Write(p []byte) (n int, err error) { + n, err = f.buffer.Write(p) + f.offset += int64(n) + f.reader.Reset(f.buffer.Bytes()) + return n, err +} + +func (f *FakeFile) Seek(offset int64, whence int) (int64, error) { + newOffset := f.offset + + switch whence { + case io.SeekStart: + newOffset = offset + case io.SeekCurrent: + newOffset += offset + case io.SeekEnd: + newOffset = int64(f.buffer.Len()) + offset + default: + return 0, errors.New("Seek: whence not at start,current or end") + } + // never can get here right? + + if newOffset < 0 { + return 0, errors.New("Seek: offset < 0") + } + + f.offset = newOffset + return f.offset, nil +} + +func NewFakeFile() *FakeFile { + buf := &bytes.Buffer{} + return &FakeFile{ + reader: bytes.NewReader(buf.Bytes()), + buffer: buf, + offset: 0, + } +} diff --git a/gocui/gocui.go b/gocui/gocui.go new file mode 100644 index 0000000..d2877d7 --- /dev/null +++ b/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", 5, 10, 5, 10, 0) // x0, x1, y1, y2, overlap + 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/gocui/help.go b/gocui/help.go new file mode 100644 index 0000000..82d5cc5 --- /dev/null +++ b/gocui/help.go @@ -0,0 +1,71 @@ +// 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" + "strings" + + "github.com/awesome-gocui/gocui" +) + +var helpText []string = []string{"KEYBINDINGS", + "", + "?: toggle help", + "d: toggle debugging", + "r: redraw widgets", + "s/h: show/hide all widgets", + "L: list all widgets", + "M: list all widgets positions", + "q: quit()", + "p: panic()", + "o: show Stdout", + "l: log to /tmp/witgui.log", + "Ctrl-D: Toggle Debugging", + "Ctrl-V: Toggle Verbose Debugging", + "Ctrl-C: Exit", + "", +} + +func hidehelplayout() { + me.baseGui.DeleteView("help") + // n.deleteView() + // child.hideFake() +} + +func helplayout() error { + g := me.baseGui + var err error + maxX, _ := g.Size() + + var newW int = 8 + for _, s := range(helpText) { + if newW < len(s) { + newW = len(s) + } + } + + help, err := g.SetView("help", maxX-(newW + me.FramePadW), 0, maxX-1, len(helpText) + me.FramePadH, 0) + if err != nil { + if !errors.Is(err, gocui.ErrUnknownView) { + return err + } + help.SelBgColor = gocui.ColorGreen + help.SelFgColor = gocui.ColorBlack + // fmt.Fprintln(help, "Enter: Click Button") + // fmt.Fprintln(help, "Tab/Space: Switch Buttons") + // fmt.Fprintln(help, "Backspace: Delete Button") + // fmt.Fprintln(help, "Arrow keys: Move Button") + + fmt.Fprintln(help, strings.Join(helpText, "\n")) + + if _, err := g.SetCurrentView("help"); err != nil { + return err + } + } + me.helpLabel = help + return nil +} diff --git a/gocui/keybindings.go b/gocui/keybindings.go new file mode 100644 index 0000000..2741800 --- /dev/null +++ b/gocui/keybindings.go @@ -0,0 +1,175 @@ +// 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 ( + "os" + "github.com/awesome-gocui/gocui" + "go.wit.com/gui/gui/toolkit" +) + +func defaultKeybindings(g *gocui.Gui) error { + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + return err + } + for _, n := range []string{"but1", "but2", "help", "but3"} { + if err := g.SetKeybinding(n, gocui.MouseLeft, gocui.ModNone, showMsg); err != nil { + return err + } + } + if err := g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp); err != nil { + return err + } + // mouseDown() runs whenever you click on an unknown view (?) + if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown); err != nil { + return err + } + if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModMouseCtrl, ctrlDown); err != nil { + return err + } +// if err := g.SetKeybinding(w.v.Name(), gocui.MouseLeft, gocui.ModNone, click); err != nil { +// return err +// } + /* + if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, globalDown); err != nil { + return err + } + */ + if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, msgDown); err != nil { + return err + } + addDebugKeys(g) + return nil +} + +func addDebugKeys(g *gocui.Gui) { + // show debugging buttons + g.SetKeybinding("", 'd', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + fakeStartWidth = me.FakeW + fakeStartHeight = me.TabH + me.FramePadH + if (showDebug) { + me.rootNode.showFake() + showDebug = false + } else { + me.rootNode.hideFake() + showDebug = true + } + return nil + }) + + // display the help menu + g.SetKeybinding("", '?', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + if (showHelp) { + helplayout() + showHelp = false + } else { + me.baseGui.DeleteView("help") + showHelp = true + } + return nil + }) + + // redraw all the widgets + g.SetKeybinding("", 'r', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + if (redoWidgets) { + redoWindows(0,0) + redoWidgets = false + } else { + me.rootNode.hideWidgets() + redoWidgets = true + } + return nil + }) + + // hide all widgets + g.SetKeybinding("", 'h', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + me.rootNode.hideWidgets() + return nil + }) + + // show all widgets + g.SetKeybinding("", 's', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + me.rootNode.showWidgets() + return nil + }) + + // list all widgets + g.SetKeybinding("", 'L', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + me.rootNode.listWidgets() + return nil + }) + + // list all widgets with positions + g.SetKeybinding("", 'M', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + me.rootNode.dumpTree(true) + 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 { + standardExit() + return nil + }) + g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + standardExit() + return nil + }) + g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + if (showDebug) { + var a toolkit.Action + a.B = true + a.ActionType = toolkit.EnableDebug + 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 + }) + + // panic + g.SetKeybinding("", 'p', gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + standardExit() + panic("forced panic in gocui") + return nil + }) +} diff --git a/gocui/log.go b/gocui/log.go new file mode 100644 index 0000000..33dc558 --- /dev/null +++ b/gocui/log.go @@ -0,0 +1,55 @@ +package main + +import ( + "io" +// "fmt" +// "strings" + witlog "go.wit.com/log" +) + +// various debugging flags +var logNow bool = true // useful for active development +var logError bool = true +var logWarn bool = false +var logInfo bool = false +var logVerbose bool = false + +var outputS []string + +func log(b bool, a ...any) { + witlog.Log(b, a...) +} + +func sleep(a ...any) { + witlog.Sleep(a...) +} + +func exit(a ...any) { + witlog.Exit(a...) +} + +/* +func newLog(a ...any) { + s := fmt.Sprint(a...) + tmp := strings.Split(s, "\n") + outputS = append(outputS, tmp...) + if (len(outputS) > 50) { + outputS = outputS[10:] + } + if (me.baseGui != nil) { + v, _ := me.baseGui.View("msg") + if (v != nil) { + v.Clear() + fmt.Fprintln(v, strings.Join(outputS, "\n")) + } + } +} +*/ + +func setOutput(w io.Writer) { + if (w == nil) { + return + } + witlog.SetTmp() + // witlog.SetToolkitOutput(newLog) +} diff --git a/gocui/main.go b/gocui/main.go new file mode 100644 index 0000000..2b0e06d --- /dev/null +++ b/gocui/main.go @@ -0,0 +1,101 @@ +// 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 ( + "os" + "go.wit.com/gui/gui/toolkit" +) + +// sets defaults and establishes communication +// to this toolkit from the wit/gui golang package +func init() { + log(logInfo, "Init() of awesome-gocui") + + // init the config struct default values + Set(&me, "default") + + pluginChan = make(chan toolkit.Action) + + log(logNow, "Init() start pluginChan") + go catchActionChannel() + sleep(.1) // probably not needed, but in here for now under development + go main() + sleep(.1) // probably not needed, but in here for now under development +} + +/* +recieves requests from the program to do things like: +* add new widgets +* change the text of a label +* etc.. +*/ +func catchActionChannel() { + log(logInfo, "catchActionChannel() START") + for { + log(logInfo, "catchActionChannel() infinite for() loop restarted select on channel") + select { + case a := <-pluginChan: + if (me.baseGui == nil) { + // something went wrong initializing the gocui + log(logError,"ERROR: console did not initialize") + continue + } + log(logInfo, "catchActionChannel()", a.WidgetId, a.ActionType, a.WidgetType, a.Name) + action(&a) + } + } +} + +func Exit() { + // TODO: what should actually happen here? + log(true, "Exit() here. doing standardExit()") + standardExit() +} + +func standardExit() { + log(true, "standardExit() doing baseGui.Close()") + me.baseGui.Close() + log(true, "standardExit() doing outf.Close()") + outf.Close() + // log(true, "standardExit() setOutput(os.Stdout)") + // setOutput(os.Stdout) + log(true, "standardExit() send back Quit()") + go sendBackQuit() // don't stall here in case the + // induces a delay in case the callback channel is broken + sleep(1) + log(true, "standardExit() exit()") + exit() +} +func sendBackQuit() { + // send 'Quit' back to the program (?) + var a toolkit.Action + a.ActionType = toolkit.UserQuit + callback <- a +} + +var outf *os.File + +func main() { + var err error + log(logInfo, "main() start Init()") + + outf, err = os.OpenFile("/tmp/witgui.log", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) + if err != nil { + exit("error opening file: %v", err) + } + os.Stdout = outf + defer outf.Close() + + // setOutput(outf) + // log("This is a test log entry") + + ferr, _ := os.OpenFile("/tmp/witgui.err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) + os.Stderr = ferr + gocuiMain() + + log(true, "MouseMain() closed") + standardExit() +} diff --git a/gocui/mouse.go b/gocui/mouse.go new file mode 100644 index 0000000..50c2834 --- /dev/null +++ b/gocui/mouse.go @@ -0,0 +1,149 @@ +// 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" +) + +// 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 + } + if v, err := g.ViewByPosition(mx, my); err == nil { + v.Highlight = true + } +} + +func msgDown(g *gocui.Gui, v *gocui.View) error { + initialMouseX, initialMouseY = g.MousePosition() + log(true, "msgDown() X,Y", initialMouseX, initialMouseY) + if vx, vy, _, _, err := g.ViewPosition("msg"); err == nil { + xOffset = initialMouseX - vx + yOffset = initialMouseY - vy + msgMouseDown = true + } + return nil +} + +func hideDDview() error { + w, h := me.baseGui.MousePosition() + log(true, "hide dropdown menu() view msgMouseDown (w,h) =", w, h) + if (me.ddview == nil) { + return gocui.ErrUnknownView + } + if (me.ddview.tk.v == nil) { + return gocui.ErrUnknownView + } + me.ddview.SetVisible(false) + return nil +} + +func showDDview() error { + w, h := me.baseGui.MousePosition() + log(true, "show dropdown menu() view msgMouseDown (w,h) =", w, h) + if (me.ddview == nil) { + return gocui.ErrUnknownView + } + if (me.ddview.tk.v == nil) { + return gocui.ErrUnknownView + } + me.ddview.SetVisible(true) + return nil +} + +func mouseUp(g *gocui.Gui, v *gocui.View) error { + w, h := g.MousePosition() + log(true, "mouseUp() view msgMouseDown (check here for dropdown menu click) (w,h) =", w, h) + if (me.ddClicked) { + me.ddClicked = false + log(true, "mouseUp() ddview is the thing that was clicked", w, h) + log(true, "mouseUp() find out what the string is here", w, h, me.ddview.tk.gocuiSize.h1) + + var newZone string = "" + if (me.ddNode != nil) { + value := h - me.ddview.tk.gocuiSize.h0 - 1 + log(true, "mouseUp() me.ddview.tk.gocuiSize.h1 =", me.ddview.tk.gocuiSize.h1) + log(true, "mouseUp() me.ddNode.vals =", me.ddNode.vals) + valsLen := len(me.ddNode.vals) + log(true, "mouseUp() value =", value, "valsLen =", valsLen) + log(true, "mouseUp() me.ddNode.vals =", me.ddNode.vals) + if ((value >= 0) && (value < valsLen)) { + newZone = me.ddNode.vals[value] + log(true, "mouseUp() value =", value, "newZone =", newZone) + } + } + hideDDview() + if (newZone != "") { + if (me.ddNode != nil) { + me.ddNode.SetText(newZone) + me.ddNode.S = newZone + me.ddNode.doUserEvent() + } + } + return nil + } + /* + // if there is a drop down view active, treat it like a dialog box and close it + if (hideDDview() == nil) { + return nil + } + */ + if msgMouseDown { + msgMouseDown = false + if movingMsg { + movingMsg = false + return nil + } else { + g.DeleteView("msg") + } + } else if globalMouseDown { + globalMouseDown = false + g.DeleteView("globalDown") + } + 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() + test := findUnderMouse() + msg := fmt.Sprintf("Mouse really down at: %d,%d", mx, my) + "foobar" + if (test == me.ddview) { + if (me.ddview.Visible()) { + log(true, "hide DDview() Mouse really down at:", mx, my) + hideDDview() + } else { + log(true, "show DDview() Mouse really down at:", mx, my) + showDDview() + } + return nil + } + x := mx - len(msg)/2 + if x < 0 { + x = 0 + } else if x+len(msg)+1 > maxX-1 { + x = maxX - 1 - len(msg) - 1 + } + log(true, "mouseDown() about to write out message to 'globalDown' view. msg =", msg) + 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/gocui/place.go b/gocui/place.go new file mode 100644 index 0000000..1ed46bd --- /dev/null +++ b/gocui/place.go @@ -0,0 +1,186 @@ +package main + +import ( + "strings" + "go.wit.com/gui/gui/toolkit" +) + +func (n *node) placeBox(startW int, startH int) { + if (n.WidgetType != toolkit.Box) { + return + } + n.showWidgetPlacement(logNow, "boxS()") + + newW := startW + newH := startH + for _, child := range n.children { + child.placeWidgets(newW, newH) + // n.showWidgetPlacement(logNow, "boxS()") + 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 + newW += w + } else { + log(logNow, "BOX IS VERTICAL ", n.Name, "newWH()", newW, newH, "child()", w, h, child.Name) + // expand based on the child height + newH += h + } + } + + // just compute this every time? + // newR := n.realGocuiSize() + + n.showWidgetPlacement(logNow, "boxE()") +} + +func (n *node) placeWidgets(startW int, startH int) { + if (n == nil) { + return + } + if (me.rootNode == nil) { + return + } + + switch n.WidgetType { + case toolkit.Window: + for _, child := range n.children { + child.placeWidgets(me.RawW, me.RawH) + return + } + case toolkit.Tab: + for _, child := range n.children { + child.placeWidgets(me.RawW, me.RawH) + return + } + case toolkit.Grid: + n.placeGrid(startW, startH) + case toolkit.Box: + n.placeBox(startW, startH) + case toolkit.Group: + // move the group to the parent's next location + n.gocuiSetWH(startW, startH) + n.showWidgetPlacement(logNow, "group()") + + 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) + newR := child.realGocuiSize() + // w := newR.w1 - newR.w0 + h := newR.h1 - newR.h0 + + // increment straight down + newH += h + } + default: + n.gocuiSetWH(startW, startH) + // n.moveTo(startW, startH) + } +} + +func (n *node) placeGrid(startW int, startH int) { + w := n.tk + n.showWidgetPlacement(logInfo, "grid0:") + if (n.WidgetType != toolkit.Grid) { + return + } + + // first compute the max sizes of the rows and columns + for _, child := range n.children { + newR := child.realGocuiSize() + childW := newR.w1 - newR.w0 + childH := newR.h1 - newR.h0 + + // set the child's realWidth, and grid offset + if (w.widths[child.AtW] < childW) { + w.widths[child.AtW] = childW + } + 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) + } + + // find the width and height offset of the grid for AtW,AtH + for _, child := range n.children { + child.showWidgetPlacement(logInfo, "grid1:") + + var totalW, totalH int + for i, w := range w.widths { + if (i < child.AtW) { + totalW += w + } + } + for i, h := range w.heights { + if (i < child.AtH) { + totalH += h + } + } + + // the new corner to move the child to + newW := startW + totalW + newH := startH + totalH + + log(logVerbose, "placeGrid:", child.Name, "new()", newW, newH, "At()", child.AtW, child.AtH) + child.placeWidgets(newW, newH) + child.showWidgetPlacement(logInfo, "grid2:") + } + n.showWidgetPlacement(logInfo, "grid3:") +} + +// 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/gocui/plugin.go b/gocui/plugin.go new file mode 100644 index 0000000..381880c --- /dev/null +++ b/gocui/plugin.go @@ -0,0 +1,126 @@ +package main + +import ( + // if you include more than just this import + // then your plugin might be doing something un-ideal (just a guess from 2023/02/27) + "go.wit.com/gui/gui/toolkit" +) + +func action(a *toolkit.Action) { + log(logInfo, "action() START", a.WidgetId, a.ActionType, a.WidgetType, a.Name) + n := me.rootNode.findWidgetId(a.WidgetId) + var w *guiWidget + if (n != nil) { + w = n.tk + } + switch a.ActionType { + case toolkit.Add: + if (w == nil) { + 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 + log(logError, "action() Add ignored for already defined widget", + a.WidgetId, a.ActionType, a.WidgetType, a.Name) + } + case toolkit.Show: + if (a.B) { + n.showView() + } else { + n.hideWidgets() + } + case toolkit.Set: + 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: + n.SetText(a.S) + case toolkit.AddText: + n.AddText(a.S) + case toolkit.Move: + log(logNow, "attempt to move() =", a.ActionType, a.WidgetType, a.Name) + case toolkit.CloseToolkit: + log(logNow, "attempting to close the plugin and release stdout and stderr") + standardExit() + case toolkit.Enable: + if n.Visible() { + // widget was already shown + } else { + log(logInfo, "Setting Visable to true", a.Name) + n.SetVisible(true) + } + case toolkit.Disable: + if n.Visible() { + log(logInfo, "Setting Visable to false", a.Name) + n.SetVisible(false) + } else { + // widget was already hidden + } + default: + log(logError, "action() ActionType =", a.ActionType, "WidgetType =", a.WidgetType, "Name =", a.Name) + } + log(logInfo, "action() END") +} + +func (n *node) AddText(text string) { + if (n == nil) { + log(logNow, "widget is nil") + return + } + n.vals = append(n.vals, text) + for i, s := range n.vals { + log(logNow, "AddText()", n.Name, i, s) + } + n.SetText(text) +} + +func (n *node) SetText(text string) { + var changed bool = false + if (n == nil) { + log(logNow, "widget is nil") + return + } + if (n.Text != text) { + n.Text = text + changed = true + } + if (n.S != text) { + n.S = text + changed = true + } + if (! changed) { + return + } + + if (n.Visible()) { + n.textResize() + n.deleteView() + n.showView() + } +} + +func (n *node) Set(val any) { + // w := n.tk + log(logInfo, "Set() value =", val) + + switch v := val.(type) { + case bool: + n.B = val.(bool) + n.setCheckbox(val.(bool)) + case string: + n.SetText(val.(string)) + case int: + n.I = val.(int) + default: + log(logError, "Set() unknown type =", val, v) + } +} diff --git a/gocui/showStdout.go b/gocui/showStdout.go new file mode 100644 index 0000000..70f7cb7 --- /dev/null +++ b/gocui/showStdout.go @@ -0,0 +1,97 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/awesome-gocui/gocui" + "go.wit.com/gui/gui/toolkit" +) + +var outputW int = 180 +var outputH int = 24 + +func moveMsg(g *gocui.Gui) { + mx, my := g.MousePosition() + if !movingMsg && (mx != initialMouseX || my != initialMouseY) { + movingMsg = true + } + g.SetView("msg", mx-xOffset, my-yOffset, mx-xOffset+outputW, my-yOffset+outputH + me.FramePadH, 0) + g.SetViewOnBottom("msg") +} + +func showMsg(g *gocui.Gui, v *gocui.View) error { + var l string + var err error + + log(true, "showMsg() v.name =", v.Name()) + if _, err := g.SetCurrentView(v.Name()); err != nil { + return err + } + + _, cy := v.Cursor() + if l, err = v.Line(cy); err != nil { + l = "" + } + + makeOutputWidget(g, l) + return nil +} + +func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View { + maxX, maxY := g.Size() + + if (me.rootNode == nil) { + // keep skipping this until the binary tree is initialized + return nil + } + + if (me.logStdout == nil) { + a := new(toolkit.Action) + a.Name = "stdout" + a.Text = "stdout" + a.WidgetType = toolkit.Stdout + a.WidgetId = -3 + a.ParentId = 0 + 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) { + log(true, "makeoutputwindow() this is supposed to happen. v == nil", err) + } else { + log(true, "makeoutputwindow() msg != nil. WTF now? err =", err) + } + + // help, err := g.SetView("help", maxX-32, 0, maxX-1, 13, 0) + // v, err = g.SetView("msg", 3, 3, 30, 30, 0) + + v, err = g.SetView("msg", maxX-32, maxY/2, maxX/2+outputW, maxY/2+outputH, 0) + if errors.Is(err, gocui.ErrUnknownView) { + log(true, "makeoutputwindow() this is supposed to happen?", err) + } + + if (err != nil) { + log(true, "makeoutputwindow() create output window failed", err) + return nil + } + + if (v == nil) { + log(true, "makeoutputwindow() msg == nil. WTF now? err =", err) + return nil + } else { + me.logStdout.tk.v = v + } + + 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()) + return v +} diff --git a/gocui/structs.go b/gocui/structs.go new file mode 100644 index 0000000..d1fe381 --- /dev/null +++ b/gocui/structs.go @@ -0,0 +1,224 @@ +// LICENSE: same as the go language itself +// Copyright 2023 WIT.COM + +// all structures and variables are local (aka lowercase) +// since the plugin should be isolated to access only +// by functions() to insure everything here is run +// inside a dedicated goroutine + +package main + +import ( + "fmt" + "reflect" + "strconv" + "sync" + "strings" + "github.com/awesome-gocui/gocui" +) + +// It's probably a terrible idea to call this 'me' +var me config + +var showDebug bool = true +var showHelp bool = true +var redoWidgets bool = true + +// This is the window that is currently active +var currentWindow *node + +type config struct { + baseGui *gocui.Gui // the main gocui handle + 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 + currentWindow *node // this is the current tab or window to show + logStdout *node // where to show STDOUT + helpLabel *gocui.View + ddview *node // the gocui view to select dropdrown lists + ddClicked bool // the dropdown menu view was clicked + ddNode *node // the dropdown menu is for this widget + + /* + // this is the channel we send user events like + // mouse clicks or keyboard events back to the program + callback chan toolkit.Action + + // this is the channel we get requests to make widgets + pluginChan chan toolkit.Action + */ + + // 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:"1" dense:"0"` + FramePadH int `default:"1" dense:"0"` + + PadW int `default:"1" dense:"0"` + PadH int `default:"1" dense:"0"` + + // how far down to start Window or Tab headings + WindowW int `default:"8" dense:"0"` + WindowH int `default:"-1"` + 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:"1"` + RawH int `default:"5"` + + // offset for the hidden widgets + 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 + 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 +var ( + initialMouseX, initialMouseY, xOffset, yOffset int + globalMouseDown, msgMouseDown, movingMsg bool +) + +// this is the gocui way +// corner starts at in the upper left corner +type rectType struct { + w0, h0, w1, h1 int // left top right bottom +} + +func (r *rectType) Width() int { + return r.w1 - r.w0 +} + +func (r *rectType) Height() int { + return r.h1 - r.h0 +} + +type guiWidget struct { + // 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 + + // 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 visible like with a Box or Grid + gocuiSize rectType + + isCurrent bool // is this the currently displayed Window or Tab? + isFake bool // widget types like 'box' are 'false' + + // 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 + + tainted bool + frame bool + + // for a window, this is currently selected tab + selectedTab *node + + // what color to use + color *colorT +} + +// from the gocui devs: +// Write appends a byte slice into the view's internal buffer. Because +// View implements the io.Writer interface, it can be passed as parameter +// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must +// be called to clear the view's buffer. + +func (w *guiWidget) Write(p []byte) (n int, err error) { + w.tainted = true + me.writeMutex.Lock() + defer me.writeMutex.Unlock() + if (me.logStdout.tk.v == nil) { + // optionally write the output to /tmp + s := fmt.Sprint(string(p)) + s = strings.TrimSuffix(s, "\n") + fmt.Fprintln(outf, s) + v, _ := me.baseGui.View("msg") + if (v != nil) { + // fmt.Fprintln(outf, "found msg") + me.logStdout.tk.v = v + } + } else { + // display the output in the gocui window + me.logStdout.tk.v.Clear() + + s := fmt.Sprint(string(p)) + s = strings.TrimSuffix(s, "\n") + tmp := strings.Split(s, "\n") + outputS = append(outputS, tmp...) + if (len(outputS) > outputH) { + l := len(outputS) - outputH + outputS = outputS[l:] + } + fmt.Fprintln(me.logStdout.tk.v, strings.Join(outputS, "\n")) + } + + return len(p), nil +} + +func Set(ptr interface{}, tag string) error { + if reflect.TypeOf(ptr).Kind() != reflect.Ptr { + log(logError, "Set() Not a pointer", ptr, "with tag =", tag) + return fmt.Errorf("Not a pointer") + } + + v := reflect.ValueOf(ptr).Elem() + t := v.Type() + + for i := 0; i < t.NumField(); i++ { + defaultVal := t.Field(i).Tag.Get(tag) + name := t.Field(i).Name + // log("Set() try name =", name, "defaultVal =", defaultVal) + setField(v.Field(i), defaultVal, name) + } + return nil +} + +func setField(field reflect.Value, defaultVal string, name string) error { + + if !field.CanSet() { + // log("setField() Can't set value", field, defaultVal) + return fmt.Errorf("Can't set value\n") + } else { + log(true, "setField() Can set value", name, defaultVal) + } + + switch field.Kind() { + case reflect.Int: + val, _ := strconv.Atoi(defaultVal) + field.Set(reflect.ValueOf(int(val)).Convert(field.Type())) + case reflect.String: + field.Set(reflect.ValueOf(defaultVal).Convert(field.Type())) + case reflect.Bool: + if defaultVal == "true" { + field.Set(reflect.ValueOf(true)) + } else { + field.Set(reflect.ValueOf(false)) + } + } + + return nil +} diff --git a/gocui/tab.go b/gocui/tab.go new file mode 100644 index 0000000..2f909ae --- /dev/null +++ b/gocui/tab.go @@ -0,0 +1,109 @@ +package main + +// implements widgets 'Window' and 'Tab' + +import ( + "strings" + "go.wit.com/gui/gui/toolkit" +) + +func (w *guiWidget) Width() int { + if w.frame { + return w.gocuiSize.w1 - w.gocuiSize.w0 + } + return w.gocuiSize.w1 - w.gocuiSize.w0 - 1 +} + +func (w *guiWidget) Height() int { + if w.frame { + return w.gocuiSize.h1 - w.gocuiSize.h0 + } + return w.gocuiSize.h1 - w.gocuiSize.h0 - 1 +} + +func (n *node) gocuiSetWH(sizeW, sizeH int) { + w := len(n.Text) + lines := strings.Split(n.Text, "\n") + h := len(lines) + + 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.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 + } else { + tk.gocuiSize.w0 = sizeW - 1 + tk.gocuiSize.h0 = sizeH - 1 + 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 + } + 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 + n.hasTabs = true + } else { + w.frame = false + n.hasTabs = false + } + + 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 + + n.gocuiSetWH(nextW, nextH) + n.deleteView() + // setCurrentTab(n) + // if (len(w.cuiName) < 4) { + // w.cuiName = "abcd" + // } + + 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/gocui/view.go b/gocui/view.go new file mode 100644 index 0000000..08117fc --- /dev/null +++ b/gocui/view.go @@ -0,0 +1,232 @@ +package main + +import ( + "fmt" + "errors" + "strconv" + "bufio" + "strings" + + "github.com/awesome-gocui/gocui" + "go.wit.com/gui/gui/toolkit" +) + +func splitLines(s string) []string { + var lines []string + sc := bufio.NewScanner(strings.NewReader(s)) + for sc.Scan() { + lines = append(lines, sc.Text()) + } + return lines +} + +func (n *node) textResize() bool { + w := n.tk + var width, height int = 0, 0 + var changed bool = false + + for i, s := range splitLines(n.Text) { + log(logInfo, "textResize() len =", len(s), i, s) + if (width < len(s)) { + width = len(s) + } + height += 1 + } + if (w.gocuiSize.w1 != w.gocuiSize.w0 + width + me.FramePadW) { + w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW + changed = true + } + if (w.gocuiSize.h1 != w.gocuiSize.h0 + height + me.FramePadH) { + w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH + changed = true + } + if (changed) { + n.showWidgetPlacement(logNow, "textResize() changed") + } + return changed +} + +func (n *node) hideView() { + n.SetVisible(false) +} + +// display's the text of the widget in gocui +// will create a new gocui view if there isn't one or if it has been moved +func (n *node) showView() { + var err error + w := n.tk + + if (w.cuiName == "") { + log(logError, "showView() w.cuiName was not set for widget", w) + w.cuiName = strconv.Itoa(n.WidgetId) + } + + // if the gocui element doesn't exist, create it + if (w.v == nil) { + n.recreateView() + } + x0, y0, x1, y1, err := me.baseGui.ViewPosition(w.cuiName) + log(logInfo, "showView() w.v already defined for widget", n.Name, err) + + // n.smartGocuiSize() + changed := n.textResize() + + if (changed) { + log(logNow, "showView() textResize() changed. Should recreateView here wId =", w.cuiName) + } else { + log(logNow, "showView() Clear() and Fprint() here wId =", w.cuiName) + w.v.Clear() + fmt.Fprint(w.v, n.Text) + n.SetVisible(false) + n.SetVisible(true) + return + } + + // if the gocui element has changed where it is supposed to be on the screen + // recreate it + if (x0 != w.gocuiSize.w0) { + n.recreateView() + return + } + if (y0 != w.gocuiSize.h0) { + log(logError, "showView() start hight mismatch id=", w.cuiName, "gocui h vs computed h =", w.gocuiSize.h0, y0) + n.recreateView() + return + } + if (x1 != w.gocuiSize.w1) { + log(logError, "showView() too wide", w.cuiName, "w,w", w.gocuiSize.w1, x1) + n.recreateView() + return + } + if (y1 != w.gocuiSize.h1) { + log(logError, "showView() too high", w.cuiName, "h,h", w.gocuiSize.h1, y1) + n.recreateView() + return + } + + n.SetVisible(true) +} + +// create or recreate the gocui widget visible +// deletes the old view if it exists and recreates it +func (n *node) recreateView() { + var err error + w := n.tk + log(logError, "recreateView() START", n.WidgetType, n.Name) + if (me.baseGui == nil) { + log(logError, "recreateView() ERROR: me.baseGui == nil", w) + return + } + + // this deletes the button from gocui + me.baseGui.DeleteView(w.cuiName) + w.v = nil + + if (n.Name == "CLOUDFLARE_EMAIL") { + n.showWidgetPlacement(logNow, "n.Name=" + n.Name + " n.Text=" + n.Text + " " + w.cuiName) + n.dumpWidget("jwc") + n.textResize() + n.showWidgetPlacement(logNow, "n.Name=" + n.Name + " n.Text=" + n.Text + " " + w.cuiName) + } + + a := w.gocuiSize.w0 + b := w.gocuiSize.h0 + c := w.gocuiSize.w1 + d := w.gocuiSize.h1 + + w.v, err = me.baseGui.SetView(w.cuiName, a, b, c, d, 0) + if err == nil { + n.showWidgetPlacement(logError, "recreateView()") + log(logError, "recreateView() internal plugin error err = nil") + return + } + if !errors.Is(err, gocui.ErrUnknownView) { + n.showWidgetPlacement(logError, "recreateView()") + log(logError, "recreateView() internal plugin error error.IS()", err) + return + } + + // this sets up the keybinding for the name of the window + // does this really need to be done? I think we probably already + // know everything about where all the widgets are so we could bypass + // the gocui package and just handle all the mouse events internally here (?) + // for now, the w.v.Name is a string "1", "2", "3", etc from the widgetId + + // set the binding for this gocui view now that it has been created + // gocui handles overlaps of views so it will run on the view that is clicked on + me.baseGui.SetKeybinding(w.v.Name(), gocui.MouseLeft, gocui.ModNone, click) + + // this actually sends the text to display to gocui + w.v.Wrap = true + w.v.Frame = w.frame + w.v.Clear() + fmt.Fprint(w.v, n.Text) + // n.showWidgetPlacement(logNow, "n.Name=" + n.Name + " n.Text=" + n.Text + " " + w.cuiName) + // n.dumpWidget("jwc 2") + + // if you don't do this here, it will be black & white only + if (w.color != nil) { + w.v.FrameColor = w.color.frame + w.v.FgColor = w.color.fg + w.v.BgColor = w.color.bg + w.v.SelFgColor = w.color.selFg + w.v.SelBgColor = w.color.selBg + } + if (n.Name == "CLOUDFLARE_EMAIL") { + n.showWidgetPlacement(logNow, "n.Name=" + n.Name + " n.Text=" + n.Text + " " + w.cuiName) + n.dumpTree(true) + } + log(logError, "recreateView() END") +} + +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.hideView() + } + for _, child := range n.children { + child.hideWidgets() + } +} + +func (n *node) hideFake() { + w := n.tk + if (w.isFake) { + n.hideView() + } + 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 { + n.showWidgetPlacement(logInfo, "current:") + n.showView() + } + for _, child := range n.children { + child.showWidgets() + } +} diff --git a/gocui/widget.go b/gocui/widget.go new file mode 100644 index 0000000..87856e4 --- /dev/null +++ b/gocui/widget.go @@ -0,0 +1,147 @@ +package main + +import ( + "strconv" + "go.wit.com/gui/gui/toolkit" +) + +func initWidget(n *node) *guiWidget { + var w *guiWidget + w = new(guiWidget) + // Set(w, "default") + + w.frame = true + + // set the name used by gocui to the id + w.cuiName = strconv.Itoa(n.WidgetId) + + if n.WidgetType == toolkit.Root { + log(logInfo, "setupWidget() FOUND ROOT w.id =", n.WidgetId) + n.WidgetId = 0 + me.rootNode = n + return w + } + + if (n.WidgetType == toolkit.Box) { + if (n.B) { + n.horizontal = true + } else { + n.horizontal = false + } + } + + 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() { + a := new(toolkit.Action) + a.Name = "ctrlDown" + a.WidgetType = toolkit.Dialog + a.WidgetId = -1 + a.ParentId = 0 + n := addNode(a) + + me.ctrlDown = n +} + +func (n *node) deleteView() { + w := n.tk + if (w.v != nil) { + w.v.Visible = false + return + } + // make sure the view isn't really there + me.baseGui.DeleteView(w.cuiName) + w.v = 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 (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 +} + +func addDropdown() *node { + n := new(node) + n.WidgetType = toolkit.Flag + n.WidgetId = -2 + n.ParentId = 0 + + // copy the data from the action message + n.Name = "DropBox" + n.Text = "DropBox text" + + // store the internal toolkit information + n.tk = new(guiWidget) + n.tk.frame = true + + // set the name used by gocui to the id + n.tk.cuiName = "-1 dropbox" + + n.tk.color = &colorFlag + + // add this new widget on the binary tree + n.parent = me.rootNode + if n.parent != nil { + n.parent.children = append(n.parent.children, n) + } + return n +} diff --git a/nocui/Makefile b/nocui/Makefile new file mode 100644 index 0000000..acbf437 --- /dev/null +++ b/nocui/Makefile @@ -0,0 +1,5 @@ +all: plugin + ldd ../nocui.so + +plugin: + GO111MODULE="off" go build -v -x -buildmode=plugin -o ../nocui.so diff --git a/nocui/README.md b/nocui/README.md new file mode 100644 index 0000000..018b9ce --- /dev/null +++ b/nocui/README.md @@ -0,0 +1,5 @@ +# nogui + +Package gui implements a abstraction layer for Go visual elements. + +This is a sample plugin. It's a skeleton intended to be used when making a new toolkit plugin. diff --git a/nocui/action.go b/nocui/action.go new file mode 100644 index 0000000..86645cd --- /dev/null +++ b/nocui/action.go @@ -0,0 +1,153 @@ +package main + +import ( + "go.wit.com/gui/gui/toolkit" +) + +func (n *node) show(b bool) { +} + +func (n *node) enable(b bool) { +} + +func (n *node) pad(at toolkit.ActionType) { + switch n.WidgetType { + case toolkit.Group: + switch at { + case toolkit.Margin: + // SetMargined(true) + case toolkit.Unmargin: + // SetMargined(false) + case toolkit.Pad: + // SetMargined(true) + case toolkit.Unpad: + // SetMargined(false) + } + case toolkit.Tab: + case toolkit.Window: + case toolkit.Grid: + case toolkit.Box: + case toolkit.Textbox: + log(logError, "TODO: implement ActionType =", at) + default: + log(logError, "TODO: implement pad() for", at) + } +} + +func (n *node) move(newParent *node) { + p := n.parent + + switch p.WidgetType { + case toolkit.Group: + case toolkit.Tab: + // tabSetMargined(tParent.uiTab, true) + case toolkit.Window: + // t.uiWindow.SetBorderless(false) + case toolkit.Grid: + // t.uiGrid.SetPadded(true) + case toolkit.Box: + log(logInfo, "TODO: move() where =", p.ParentId) + log(logInfo, "TODO: move() for widget =", n.WidgetId) + default: + log(logError, "TODO: need to implement move() for type =", n.WidgetType) + log(logError, "TODO: need to implement move() for where =", p.ParentId) + log(logError, "TODO: need to implement move() for widget =", n.WidgetId) + } +} + +func (n *node) Delete() { + p := n.parent + log(logNow, "uiDelete()", n.WidgetId, "to", p.WidgetId) + + switch p.WidgetType { + case toolkit.Group: + // tParent.uiGroup.SetMargined(true) + case toolkit.Tab: + // tabSetMargined(tParent.uiTab, true) + case toolkit.Window: + // t.uiWindow.SetBorderless(false) + case toolkit.Grid: + // t.uiGrid.SetPadded(true) + case toolkit.Box: + log(logNow, "tWidget.boxC =", p.Name) + log(logNow, "is there a tParent parent? =", p.parent) + // this didn't work: + // tWidget.uiControl.Disable() + // sleep(.8) + // tParent.uiBox.Append(tWidget.uiControl, stretchy) + default: + log(logError, "TODO: need to implement uiDelete() for widget =", n.WidgetId, n.WidgetType) + log(logError, "TODO: need to implement uiDelete() for parent =", p.WidgetId, p.WidgetType) + } +} + +func doAction(a *toolkit.Action) { + log(logNow, "doAction() START a.ActionType =", a.ActionType) + log(logNow, "doAction() START a.S =", a.S) + + if (a.ActionType == toolkit.InitToolkit) { + // TODO: make sure to only do this once + // go uiMain.Do(func() { + // ui.Main(demoUI) + // go catchActionChannel() + // }) + // try doing this on toolkit load in init() + return + } + + log(logNow, "doAction() START a.WidgetId =", a.WidgetId, "a.ParentId =", a.ParentId) + switch a.WidgetType { + case toolkit.Root: + me.rootNode = addNode(a) + log(logNow, "doAction() found rootNode") + return + case toolkit.Flag: + // flag(&a) + return + } + + n := me.rootNode.findWidgetId(a.WidgetId) + + switch a.ActionType { + case toolkit.Add: + addNode(a) + case toolkit.Show: + n.show(true) + case toolkit.Hide: + n.show(false) + case toolkit.Enable: + n.enable(true) + case toolkit.Disable: + n.enable(false) + case toolkit.Get: + // n.setText(a.S) + case toolkit.GetText: + switch a.WidgetType { + case toolkit.Textbox: + a.S = n.S + } + case toolkit.Set: + // n.setText(a.S) + case toolkit.SetText: + // n.setText(a.S) + case toolkit.AddText: + // n.setText(a.S) + case toolkit.Margin: + n.pad(toolkit.Unmargin) + case toolkit.Unmargin: + n.pad(toolkit.Margin) + case toolkit.Pad: + n.pad(toolkit.Pad) + case toolkit.Unpad: + n.pad(toolkit.Unpad) + case toolkit.Delete: + n.Delete() + case toolkit.Move: + log(logNow, "doAction() attempt to move() =", a.ActionType, a.WidgetType) + newParent := me.rootNode.findWidgetId(a.ParentId) + n.move(newParent) + default: + log(logError, "doAction() Unknown =", a.ActionType, a.WidgetType) + } + log(logInfo, "doAction() END =", a.ActionType, a.WidgetType) +} diff --git a/nocui/common.go b/nocui/common.go new file mode 100644 index 0000000..a8d9684 --- /dev/null +++ b/nocui/common.go @@ -0,0 +1,166 @@ +package main + +/* + These code should be common to all gui plugins + + There are some helper functions that are probably going to be + the same everywhere. Mostly due to handling the binary tree structure + and the channel communication + + For now, it's just a symlink to the 'master' version in + ./toolkit/nocui/common.go +*/ + +import ( + "go.wit.com/gui/gui/toolkit" +) + +// this is the channel we send user events like +// mouse clicks or keyboard events back to the program +var callback chan toolkit.Action + +// this is the channel we get requests to make widgets +var pluginChan chan toolkit.Action + +type node struct { + parent *node + children []*node + + WidgetId int // widget ID + WidgetType toolkit.WidgetType + ParentId int // parent ID + + Name string + Text string + + // This is how the values are passed back and forth + // values from things like checkboxes & dropdown's + B bool + I int + S string + + A any // switch to this or deprecate this? pros/cons? + + // This is used for things like a slider(0,100) + X int + Y int + + // This is for the grid size & widget position + W int + H int + 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? + currentTab bool // the visible tab + + // the internal plugin toolkit structure + // in the gtk plugin, it has gtk things like margin & border settings + // in the text console one, it has text console things like colors for menus & buttons + tk *guiWidget +} + +// searches the binary tree for a WidgetId +func (n *node) findWidgetId(id int) *node { + if (n == nil) { + return nil + } + + if n.WidgetId == id { + return n + } + + for _, child := range n.children { + newN := child.findWidgetId(id) + if (newN != nil) { + return newN + } + } + return nil +} + +func (n *node) doUserEvent() { + if (callback == nil) { + log(logError, "doUserEvent() callback == nil", n.WidgetId) + return + } + var a toolkit.Action + a.WidgetId = n.WidgetId + a.Name = n.Name + a.Text = n.Text + a.S = n.S + a.I = n.I + a.B = n.B + a.ActionType = toolkit.User + log(logInfo, "doUserEvent() START: send a user event to the callback channel") + callback <- a + log(logInfo, "doUserEvent() END: sent a user event to the callback channel") + return +} + +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 = initWidget(n) + // n.tk = new(guiWidget) + + 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 +} + +// 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 +// Linux, MacOS and Windows work (they all work differently. suprise. surprise.) +// +// this sets the channel to send user events back from the plugin +func Callback(guiCallback chan toolkit.Action) { + callback = guiCallback +} + +func PluginChannel() chan toolkit.Action { + return pluginChan +} diff --git a/nocui/event.go b/nocui/event.go new file mode 100644 index 0000000..17d262a --- /dev/null +++ b/nocui/event.go @@ -0,0 +1,47 @@ +package main + +import ( + "go.wit.com/gui/gui/toolkit" +) + +func (n *node) doWidgetClick() { + switch n.WidgetType { + case toolkit.Root: + // THIS IS THE BEGINING OF THE LAYOUT + // rootNode.nextW = 0 + // rootNode.nextH = 0 + // rootNode.redoTabs(true) + case toolkit.Flag: + // me.rootNode.redoColor(true) + // rootNode.dumpTree(true) + case toolkit.Window: + // setCurrentWindow(w) + n.doUserEvent() + case toolkit.Tab: + // setCurrentTab(w) + case toolkit.Group: + // n.placeWidgets() + // n.toggleTree() + case toolkit.Checkbox: + if (n.B) { + // n.setCheckbox(false) + } else { + // n.setCheckbox(true) + } + n.doUserEvent() + case toolkit.Grid: + // rootNode.hideWidgets() + // n.placeGrid() + // n.showWidgets() + case toolkit.Box: + // n.showWidgetPlacement(logNow, "drawTree()") + if (n.B) { + log(true, "BOX IS HORIZONTAL", n.Name) + } else { + log(true, "BOX IS VERTICAL", n.Name) + } + case toolkit.Button: + n.doUserEvent() + default: + } +} diff --git a/nocui/log.go b/nocui/log.go new file mode 100644 index 0000000..0dc05ca --- /dev/null +++ b/nocui/log.go @@ -0,0 +1,26 @@ +package main + +import ( + witlog "go.wit.com/log" +) + +// various debugging flags +var logNow bool = true // useful for active development +var logError bool = true +var logWarn bool = false +var logInfo bool = false +var logVerbose bool = false + +var outputS []string + +func log(b bool, a ...any) { + witlog.Log(b, a...) +} + +func sleep(a ...any) { + witlog.Sleep(a...) +} + +func exit(a ...any) { + witlog.Exit(a...) +} diff --git a/nocui/main.go b/nocui/main.go new file mode 100644 index 0000000..15f2954 --- /dev/null +++ b/nocui/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "sync" + "go.wit.com/gui/gui/toolkit" +) + +var muAction sync.Mutex + +func catchActionChannel() { + log(logNow, "catchActionChannel() START") + for { + log(logNow, "catchActionChannel() for loop") + select { + case a := <-pluginChan: + log(logNow, "catchActionChannel() SELECT widget id =", a.WidgetId, a.Name) + log(logNow, "catchActionChannel() STUFF", a.WidgetId, a.ActionType, a.WidgetType) + muAction.Lock() + doAction(&a) + muAction.Unlock() + log(logNow, "catchActionChannel() STUFF END", a.WidgetId, a.ActionType, a.WidgetType) + } + } +} + +/* +// 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 +// Linux, MacOS and Windows work (they all work differently. suprise. surprise.) +// +// this sets the channel to send user events back from the plugin +func Callback(guiCallback chan toolkit.Action) { + callback = guiCallback +} + +func PluginChannel() chan toolkit.Action { + return pluginChan +} +*/ + +// This is important. This sets the defaults for the gui. Without this, there isn't correct padding, etc +func init() { + log(logNow, "Init() START") + log(logInfo, "Init()") + + // andlabs = make(map[int]*andlabsT) + pluginChan = make(chan toolkit.Action, 1) + + log(logNow, "Init() start channel reciever") + go catchActionChannel() + go simpleStdin() + log(logNow, "Init() END") +} diff --git a/nocui/stdin.go b/nocui/stdin.go new file mode 100644 index 0000000..f1c6537 --- /dev/null +++ b/nocui/stdin.go @@ -0,0 +1,80 @@ +package main + +import ( + "os" + "fmt" + "bufio" + "strings" + "strconv" + + "go.wit.com/gui/gui/toolkit" +) + +func simpleStdin() { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + s := scanner.Text() + s = strings.TrimSuffix(s, "\n") + switch s { + case "l": + log(true, "list widgets") + me.rootNode.listWidgets() + case "b": + log(true, "show buttons") + me.rootNode.showButtons() + case "d": + var a toolkit.Action + a.ActionType = toolkit.EnableDebug + callback <- a + case "": + fmt.Println("") + fmt.Println("Enter:") + fmt.Println("'l': list all widgets") + fmt.Println("'b': for buttons") + fmt.Println("'d': enable debugging") + default: + i, _ := strconv.Atoi(s) + log(true, "got input:", i) + n := me.rootNode.findWidgetId(i) + if (n != nil) { + n.dumpWidget("found node") + n.doUserEvent() + } + } + } +} + +func (n *node) showButtons() { + if n.WidgetType == toolkit.Button { + n.dumpWidget("Button:") + } + + for _, child := range n.children { + child.showButtons() + } +} + +func (n *node) dumpWidget(pad string) { + log(true, "node:", pad, n.WidgetId, ",", n.WidgetType, ",", n.Name) +} + +var depth int = 0 + +func (n *node) listWidgets() { + if (n == nil) { + return + } + + var pad string + for i := 0; i < depth; i++ { + pad = pad + " " + } + n.dumpWidget(pad) + + for _, child := range n.children { + depth += 1 + child.listWidgets() + depth -= 1 + } + return +} diff --git a/nocui/structs.go b/nocui/structs.go new file mode 100644 index 0000000..888bcb5 --- /dev/null +++ b/nocui/structs.go @@ -0,0 +1,17 @@ +package main + +// stores the raw toolkit internals +type guiWidget struct { + Width int + Height int + + c int + val map[int]string +} + +// It's probably a terrible idea to call this 'me' +var me config + +type config struct { + rootNode *node // the base of the binary tree. it should have id == 0 +} diff --git a/nocui/widget.go b/nocui/widget.go new file mode 100644 index 0000000..cf05a9e --- /dev/null +++ b/nocui/widget.go @@ -0,0 +1,29 @@ +package main + +import ( + "go.wit.com/gui/gui/toolkit" +) + +// this is specific to the nocui toolkit +func initWidget(n *node) *guiWidget { + var w *guiWidget + w = new(guiWidget) + // Set(w, "default") + + if n.WidgetType == toolkit.Root { + log(logInfo, "setupWidget() FOUND ROOT w.id =", n.WidgetId) + n.WidgetId = 0 + me.rootNode = n + return w + } + + if (n.WidgetType == toolkit.Box) { + if (n.B) { + n.horizontal = true + } else { + n.horizontal = false + } + } + + return w +}