nocui: a template for porting new toolkits
Signed-off-by: Jeff Carr <jcarr@wit.com>
This commit is contained in:
parent
dc7762fc16
commit
065b931106
1
Makefile
1
Makefile
|
@ -83,6 +83,7 @@ plugins: plugins-gocui plugins-andlabs
|
|||
|
||||
plugins-gocui:
|
||||
GO111MODULE="off" go build -v -x -C toolkit/gocui -buildmode=plugin -o ../gocui.so
|
||||
GO111MODULE="off" go build -v -x -C toolkit/nocui -buildmode=plugin -o ../nocui.so
|
||||
|
||||
plugins-andlabs:
|
||||
GO111MODULE="off" go build -v -x -C toolkit/andlabs -buildmode=plugin -o ../andlabs.so
|
||||
|
|
|
@ -85,11 +85,40 @@ type config struct {
|
|||
writeMutex sync.Mutex
|
||||
}
|
||||
|
||||
// deprecate these
|
||||
var (
|
||||
initialMouseX, initialMouseY, xOffset, yOffset int
|
||||
globalMouseDown, msgMouseDown, movingMsg bool
|
||||
)
|
||||
|
||||
// this is the standard binary tree structure for toolkits
|
||||
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
|
||||
|
||||
// the internal plugin toolkit structure
|
||||
tk *cuiWidget
|
||||
}
|
||||
|
||||
// the gocui way
|
||||
// the logical size of the widget
|
||||
// corner starts at in the upper left corner
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
all: plugin
|
||||
ldd ../nocui.so
|
||||
|
||||
plugin:
|
||||
GO111MODULE="off" go build -v -x -buildmode=plugin -o ../nocui.so
|
|
@ -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.
|
|
@ -0,0 +1,152 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.wit.org/wit/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 action(a toolkit.Action) {
|
||||
log(logNow, "rawAction() START a.ActionType =", a.ActionType)
|
||||
log(logNow, "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(logNow, "rawAction() START a.WidgetId =", a.WidgetId, "a.ParentId =", a.ParentId)
|
||||
switch a.WidgetType {
|
||||
case toolkit.Flag:
|
||||
// flag(&a)
|
||||
return
|
||||
}
|
||||
|
||||
n := rootNode.findWidgetId(a.WidgetId)
|
||||
|
||||
switch a.ActionType {
|
||||
case toolkit.Add:
|
||||
// QueueMain(func() {
|
||||
// add(a)
|
||||
// })
|
||||
// sleep(.1)
|
||||
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, "rawAction() attempt to move() =", a.ActionType, a.WidgetType)
|
||||
newParent := rootNode.findWidgetId(a.ParentId)
|
||||
n.move(newParent)
|
||||
default:
|
||||
log(logError, "rawAction() Unknown =", a.ActionType, a.WidgetType)
|
||||
}
|
||||
log(logInfo, "rawAction() END =", a.ActionType, a.WidgetType)
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.wit.org/wit/gui/toolkit"
|
||||
)
|
||||
|
||||
// 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 addWidget(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
|
||||
|
||||
// store the internal toolkit information
|
||||
n.tk = new(nocuiT)
|
||||
|
||||
if (a.WidgetType == toolkit.Root) {
|
||||
log(logInfo, "addWidget() Root")
|
||||
return n
|
||||
}
|
||||
|
||||
if (rootNode.findWidgetId(a.WidgetId) != nil) {
|
||||
log(logError, "addWidget() WidgetId already exists", a.WidgetId)
|
||||
return rootNode.findWidgetId(a.WidgetId)
|
||||
}
|
||||
|
||||
// add this new widget on the binary tree
|
||||
n.parent = rootNode.findWidgetId(a.ParentId)
|
||||
if n.parent != nil {
|
||||
n.parent.children = append(n.parent.children, n)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
// "fmt"
|
||||
// "strings"
|
||||
witlog "git.wit.org/wit/gui/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(a ...any) {
|
||||
witlog.Where = "wit/gocui"
|
||||
witlog.Log(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.SetOutput(w)
|
||||
// witlog.SetToolkitOutput(newLog)
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"git.wit.org/wit/gui/toolkit"
|
||||
)
|
||||
|
||||
// this is the channel we get requests to make widgets
|
||||
var pluginChan chan toolkit.Action
|
||||
|
||||
// the starting point of the binary tree
|
||||
var rootNode *node
|
||||
|
||||
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()
|
||||
action(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()
|
||||
log(logNow, "Init() END")
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import "git.wit.org/wit/gui/toolkit"
|
||||
|
||||
var callback 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
|
||||
|
||||
// the internal plugin toolkit structure
|
||||
tk *nocuiT
|
||||
}
|
||||
|
||||
// stores the raw toolkit internals
|
||||
type nocuiT struct {
|
||||
Width int
|
||||
Height int
|
||||
|
||||
c int
|
||||
val map[int]string
|
||||
}
|
Loading…
Reference in New Issue