From 45f6db7a55b493e61c29ba2713bd4eb27cefaaf8 Mon Sep 17 00:00:00 2001 From: gucio321 Date: Wed, 6 Nov 2024 18:16:55 +0100 Subject: [PATCH 1/7] initial nodes infrastructure --- MasterWindow.go | 1 + Nodes.go | 14 ++++++++++++++ examples/nodes/nodes.go | 15 +++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 Nodes.go create mode 100644 examples/nodes/nodes.go diff --git a/MasterWindow.go b/MasterWindow.go index 95089b7e..bd64c785 100644 --- a/MasterWindow.go +++ b/MasterWindow.go @@ -85,6 +85,7 @@ func NewMasterWindow(title string, width, height int, flags MasterWindowFlags) * implot.CreateContext() imnodes.CreateContext() + imnodes.StyleColorsDark() io := imgui.CurrentIO() diff --git a/Nodes.go b/Nodes.go new file mode 100644 index 00000000..d7d5505d --- /dev/null +++ b/Nodes.go @@ -0,0 +1,14 @@ +package giu + +import "github.com/AllenDang/cimgui-go/imnodes" + +type NodeEditorWidget struct{} + +func NodeEditor() *NodeEditorWidget { + return &NodeEditorWidget{} +} + +func (n *NodeEditorWidget) Build() { + imnodes.BeginNodeEditor() + imnodes.EndNodeEditor() +} diff --git a/examples/nodes/nodes.go b/examples/nodes/nodes.go new file mode 100644 index 00000000..5f3a40db --- /dev/null +++ b/examples/nodes/nodes.go @@ -0,0 +1,15 @@ +package main + +import "github.com/AllenDang/giu" + +func loop() { + giu.SingleWindow().Layout( + giu.Label("hehehe"), + giu.NodeEditor(), + ) +} + +func main() { + wnd := giu.NewMasterWindow("ImNodes demo", 1280, 720, 0) + wnd.Run(loop) +} From 598dbef4ecc87dde86b4d5c0acc7234fea746225 Mon Sep 17 00:00:00 2001 From: gucio321 Date: Thu, 7 Nov 2024 15:41:44 +0100 Subject: [PATCH 2/7] nodes: add links and basic nodeWidget support --- Nodes.go | 156 +++++++++++++++++++++++++++++++++++++++- examples/nodes/nodes.go | 19 ++++- 2 files changed, 170 insertions(+), 5 deletions(-) diff --git a/Nodes.go b/Nodes.go index d7d5505d..75e0ae36 100644 --- a/Nodes.go +++ b/Nodes.go @@ -1,14 +1,164 @@ package giu -import "github.com/AllenDang/cimgui-go/imnodes" +import ( + "github.com/AllenDang/cimgui-go/imnodes" +) -type NodeEditorWidget struct{} +type nodeEditorState struct { + context *imnodes.EditorContext + links []*LinkWidget + linksCounter int32 +} + +func newNodeEditorState() *nodeEditorState { + return &nodeEditorState{ + context: imnodes.EditorContextCreate(), + links: make([]*LinkWidget, 0), + } +} + +func (n *nodeEditorState) Dispose() { + imnodes.EditorContextFree(n.context) +} + +func (n *NodeEditorWidget) getState() *nodeEditorState { + if state := GetState[nodeEditorState](Context, n.id); state != nil { + return state + } + + state := newNodeEditorState() + SetState[nodeEditorState](Context, n.id, state) + + return state +} + +type NodeEditorWidget struct { + nodes []*NodeWidget + idCounter int32 + id ID +} func NodeEditor() *NodeEditorWidget { - return &NodeEditorWidget{} + return &NodeEditorWidget{ + idCounter: 0, + id: GenAutoID("NodeEditor"), + } +} + +func (n *NodeEditorWidget) Nodes(nodes ...*NodeWidget) *NodeEditorWidget { + n.nodes = nodes + return n } func (n *NodeEditorWidget) Build() { + n.idCounter = 0 + state := n.getState() + imnodes.EditorContextSet(state.context) + imnodes.BeginNodeEditor() + for _, node := range n.nodes { + node.BuildNode(&n.idCounter) + } + + for _, link := range state.links { + imnodes.Link(link.linkID, link.startID, link.endID) + } + imnodes.EndNodeEditor() + + potentialNewLink := Link(state.linksCounter, 0, 0) + if imnodes.IsLinkCreatedBoolPtr(&potentialNewLink.startID, &potentialNewLink.endID) { + state.links = append(state.links, potentialNewLink) + state.linksCounter++ + } + + var id int32 + if imnodes.IsLinkDestroyed(&id) { + for i, link := range state.links { + if link.linkID == id { + state.links = append(state.links[:i], state.links[i+1:]...) + break + } + } + } +} + +type NodeWidget struct { + titleBar Layout + layout Layout + input Layout + output Layout +} + +func Node(staticLayout ...Widget) *NodeWidget { + return &NodeWidget{ + layout: Layout(staticLayout), + } +} + +func (n *NodeWidget) TitleBar(widgets ...Widget) *NodeWidget { + n.titleBar = Layout(widgets) + + return n +} + +func (n *NodeWidget) Input(widgets ...Widget) *NodeWidget { + n.input = Layout(widgets) + + return n +} + +func (n *NodeWidget) Output(widgets ...Widget) *NodeWidget { + n.output = Layout(widgets) + + return n +} + +func (n *NodeWidget) BuildNode(idCounter *int32) { + Assert(n.layout != nil && len(n.layout) > 0, "NodeWidget", "BuildNode", "Node layout is required") + imnodes.BeginNode(*idCounter) + *idCounter++ + + if n.titleBar != nil { + imnodes.BeginNodeTitleBar() + n.titleBar.Build() + imnodes.EndNodeTitleBar() + } + + if n.input != nil { + imnodes.PushAttributeFlag(imnodes.AttributeFlagsEnableLinkDetachWithDragClick) + imnodes.BeginInputAttribute(*idCounter) + *idCounter++ + n.input.Build() + imnodes.EndInputAttribute() + imnodes.PopAttributeFlag() + } + + imnodes.BeginStaticAttribute(*idCounter) + *idCounter++ + n.layout.Build() + imnodes.EndStaticAttribute() + + if n.output != nil { + imnodes.BeginOutputAttribute(*idCounter) + *idCounter++ + n.output.Build() + imnodes.EndOutputAttribute() + } + + imnodes.EndNode() +} + +type LinkWidget struct { + linkID int32 + startID int32 + endID int32 +} + +func Link(linkID, startID, endID int32) *LinkWidget { + return &LinkWidget{ + startID: startID, + endID: endID, + linkID: linkID, + } } diff --git a/examples/nodes/nodes.go b/examples/nodes/nodes.go index 5f3a40db..aaad1700 100644 --- a/examples/nodes/nodes.go +++ b/examples/nodes/nodes.go @@ -1,11 +1,26 @@ package main -import "github.com/AllenDang/giu" +import ( + "github.com/AllenDang/giu" +) func loop() { giu.SingleWindow().Layout( giu.Label("hehehe"), - giu.NodeEditor(), + giu.NodeEditor().Nodes( + giu.Node( + giu.Label("Main content"), + ).Output( + giu.Label("Output attribute"), + ), + giu.Node( + giu.Label("Main content"), + ).TitleBar( + giu.Label("This is a title bar"), + ).Input( + giu.Label("Iput attribute"), + ), + ), ) } From a02961b0e0a9cb74797a93bdad74d6faec112a03 Mon Sep 17 00:00:00 2001 From: gucio321 Date: Thu, 7 Nov 2024 16:28:35 +0100 Subject: [PATCH 3/7] nodes: redesign api after careful look --- Nodes.go | 91 ++++++++++++++++++++++++----------------- examples/nodes/nodes.go | 11 +++-- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/Nodes.go b/Nodes.go index 75e0ae36..f84b179f 100644 --- a/Nodes.go +++ b/Nodes.go @@ -1,6 +1,8 @@ package giu import ( + "fmt" + "github.com/AllenDang/cimgui-go/imnodes" ) @@ -32,6 +34,20 @@ func (n *NodeEditorWidget) getState() *nodeEditorState { return state } +type NodeElementType int + +const ( + NodeElementInput NodeElementType = iota + NodeElementOutput + NodeElementBody + NodeElementTitleBar +) + +type nodeElement struct { + elementType NodeElementType + layout Layout +} + type NodeEditorWidget struct { nodes []*NodeWidget idCounter int32 @@ -84,66 +100,67 @@ func (n *NodeEditorWidget) Build() { } type NodeWidget struct { - titleBar Layout - layout Layout - input Layout - output Layout + elements []nodeElement } -func Node(staticLayout ...Widget) *NodeWidget { - return &NodeWidget{ - layout: Layout(staticLayout), - } +func Node() *NodeWidget { + return &NodeWidget{} +} + +func (n *NodeWidget) Static(widgets ...Widget) *NodeWidget { + n.elements = append(n.elements, nodeElement{NodeElementBody, Layout(widgets)}) + + return n } func (n *NodeWidget) TitleBar(widgets ...Widget) *NodeWidget { - n.titleBar = Layout(widgets) + for i := range n.elements { + if n.elements[i].elementType != NodeElementTitleBar { + n.elements = append(n.elements[:i], append([]nodeElement{{NodeElementTitleBar, Layout(widgets)}}, n.elements[i:]...)...) + } + } return n } func (n *NodeWidget) Input(widgets ...Widget) *NodeWidget { - n.input = Layout(widgets) + n.elements = append(n.elements, nodeElement{NodeElementInput, Layout(widgets)}) return n } func (n *NodeWidget) Output(widgets ...Widget) *NodeWidget { - n.output = Layout(widgets) + n.elements = append(n.elements, nodeElement{NodeElementOutput, Layout(widgets)}) return n } func (n *NodeWidget) BuildNode(idCounter *int32) { - Assert(n.layout != nil && len(n.layout) > 0, "NodeWidget", "BuildNode", "Node layout is required") - imnodes.BeginNode(*idCounter) - *idCounter++ - - if n.titleBar != nil { - imnodes.BeginNodeTitleBar() - n.titleBar.Build() - imnodes.EndNodeTitleBar() + fMap := map[NodeElementType]struct { + begin func(int32) + end func() + }{ + NodeElementInput: {imnodes.BeginInputAttribute, imnodes.EndInputAttribute}, + NodeElementOutput: {imnodes.BeginOutputAttribute, imnodes.EndOutputAttribute}, + NodeElementBody: {imnodes.BeginStaticAttribute, imnodes.EndStaticAttribute}, + NodeElementTitleBar: {func(int32) { imnodes.BeginNodeTitleBar() }, imnodes.EndNodeTitleBar}, } - if n.input != nil { - imnodes.PushAttributeFlag(imnodes.AttributeFlagsEnableLinkDetachWithDragClick) - imnodes.BeginInputAttribute(*idCounter) - *idCounter++ - n.input.Build() - imnodes.EndInputAttribute() - imnodes.PopAttributeFlag() - } - - imnodes.BeginStaticAttribute(*idCounter) + // Assert(n.layout != nil && len(n.layout) > 0, "NodeWidget", "BuildNode", "Node layout is required") + imnodes.BeginNode(*idCounter) *idCounter++ - n.layout.Build() - imnodes.EndStaticAttribute() - - if n.output != nil { - imnodes.BeginOutputAttribute(*idCounter) - *idCounter++ - n.output.Build() - imnodes.EndOutputAttribute() + + for _, element := range n.elements { + if element.layout != nil { + f, ok := fMap[element.elementType] + if !ok { + panic(fmt.Sprintf("NodeWidget:BuildNode: Unknown node element type", element.elementType)) + } + f.begin(*idCounter) + *idCounter++ + element.layout.Build() + f.end() + } } imnodes.EndNode() diff --git a/examples/nodes/nodes.go b/examples/nodes/nodes.go index aaad1700..bf7ec581 100644 --- a/examples/nodes/nodes.go +++ b/examples/nodes/nodes.go @@ -8,12 +8,15 @@ func loop() { giu.SingleWindow().Layout( giu.Label("hehehe"), giu.NodeEditor().Nodes( - giu.Node( - giu.Label("Main content"), - ).Output( + giu.Node(). + Static( + giu.Label("Main content"), + ).Output( giu.Label("Output attribute"), + ).Input( + giu.Label("Input attribute"), ), - giu.Node( + giu.Node().Static( giu.Label("Main content"), ).TitleBar( giu.Label("This is a title bar"), From 5853ddf055da928ab7923ab810b89e55fee1f459 Mon Sep 17 00:00:00 2001 From: gucio321 Date: Fri, 8 Nov 2024 09:23:24 +0100 Subject: [PATCH 4/7] Nodes: add some comments and allow to specify IDs --- Nodes.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Nodes.go b/Nodes.go index f84b179f..88d59f8d 100644 --- a/Nodes.go +++ b/Nodes.go @@ -19,6 +19,7 @@ func newNodeEditorState() *nodeEditorState { } } +// Dispose implements Disposable interface. func (n *nodeEditorState) Dispose() { imnodes.EditorContextFree(n.context) } @@ -34,18 +35,25 @@ func (n *NodeEditorWidget) getState() *nodeEditorState { return state } +// NodeElementType represents a type of node element. type NodeElementType int const ( + // NodeElementInput describes a lyout associated with an input "point". NodeElementInput NodeElementType = iota + // NodeElementOutput describes a lyout associated with an output "point". NodeElementOutput + // NodeElementBody describes just a plain layout. NodeElementBody + // NodeElementTitleBar should be rendered before any other element. + // It describes a layout of a node title bar. NodeElementTitleBar ) type nodeElement struct { elementType NodeElementType layout Layout + id int32 } type NodeEditorWidget struct { @@ -63,6 +71,7 @@ func NodeEditor() *NodeEditorWidget { func (n *NodeEditorWidget) Nodes(nodes ...*NodeWidget) *NodeEditorWidget { n.nodes = nodes + return n } @@ -135,6 +144,14 @@ func (n *NodeWidget) Output(widgets ...Widget) *NodeWidget { return n } +// ElementID allows you to manually specify an ID for the last added element. +// Appliable only for NodeElementInput and NodeElementOutput. +func (n *NodeWidget) ElementID(id int32) *NodeWidget { + n.elements[len(n.elements)-1].id = id + + return n +} + func (n *NodeWidget) BuildNode(idCounter *int32) { fMap := map[NodeElementType]struct { begin func(int32) From b786717665e24da8ef1ea70c71c22eb8117b6f4d Mon Sep 17 00:00:00 2001 From: gucio321 Date: Tue, 18 Mar 2025 09:23:55 +0100 Subject: [PATCH 5/7] nodes: a bit of updates --- Nodes.go | 67 ++++++++++++++++++++++++----------------- examples/nodes/nodes.go | 3 ++ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/Nodes.go b/Nodes.go index 88d59f8d..c6bb102a 100644 --- a/Nodes.go +++ b/Nodes.go @@ -53,19 +53,23 @@ const ( type nodeElement struct { elementType NodeElementType layout Layout - id int32 + id string } type NodeEditorWidget struct { - nodes []*NodeWidget - idCounter int32 - id ID + nodes []*NodeWidget + idCounter int32 + inputAlias map[int32]string + outputAlias map[int32]string + id ID } func NodeEditor() *NodeEditorWidget { return &NodeEditorWidget{ - idCounter: 0, - id: GenAutoID("NodeEditor"), + idCounter: 0, + inputAlias: make(map[int32]string), + outputAlias: make(map[int32]string), + id: GenAutoID("NodeEditor"), } } @@ -82,11 +86,15 @@ func (n *NodeEditorWidget) Build() { imnodes.BeginNodeEditor() for _, node := range n.nodes { - node.BuildNode(&n.idCounter) + node.BuildNode(n) } for _, link := range state.links { - imnodes.Link(link.linkID, link.startID, link.endID) + id := n.idCounter + n.idCounter++ + start := n.inputAlias[link.startID] + end := n.outputAlias[link.endID] + imnodes.Link(id, start, end) } imnodes.EndNodeEditor() @@ -117,7 +125,7 @@ func Node() *NodeWidget { } func (n *NodeWidget) Static(widgets ...Widget) *NodeWidget { - n.elements = append(n.elements, nodeElement{NodeElementBody, Layout(widgets)}) + n.elements = append(n.elements, nodeElement{NodeElementBody, Layout(widgets), ""}) return n } @@ -125,21 +133,21 @@ func (n *NodeWidget) Static(widgets ...Widget) *NodeWidget { func (n *NodeWidget) TitleBar(widgets ...Widget) *NodeWidget { for i := range n.elements { if n.elements[i].elementType != NodeElementTitleBar { - n.elements = append(n.elements[:i], append([]nodeElement{{NodeElementTitleBar, Layout(widgets)}}, n.elements[i:]...)...) + n.elements = append(n.elements[:i], append([]nodeElement{{NodeElementTitleBar, Layout(widgets), ""}}, n.elements[i:]...)...) } } return n } -func (n *NodeWidget) Input(widgets ...Widget) *NodeWidget { - n.elements = append(n.elements, nodeElement{NodeElementInput, Layout(widgets)}) +func (n *NodeWidget) Input(id string, widgets ...Widget) *NodeWidget { + n.elements = append(n.elements, nodeElement{NodeElementInput, Layout(widgets), id}) return n } -func (n *NodeWidget) Output(widgets ...Widget) *NodeWidget { - n.elements = append(n.elements, nodeElement{NodeElementOutput, Layout(widgets)}) +func (n *NodeWidget) Output(id string, widgets ...Widget) *NodeWidget { + n.elements = append(n.elements, nodeElement{NodeElementOutput, Layout(widgets), id}) return n } @@ -147,25 +155,31 @@ func (n *NodeWidget) Output(widgets ...Widget) *NodeWidget { // ElementID allows you to manually specify an ID for the last added element. // Appliable only for NodeElementInput and NodeElementOutput. func (n *NodeWidget) ElementID(id int32) *NodeWidget { - n.elements[len(n.elements)-1].id = id + // n.elements[len(n.elements)-1].id = id return n } -func (n *NodeWidget) BuildNode(idCounter *int32) { +func (n *NodeWidget) BuildNode(s *NodeEditorWidget) { fMap := map[NodeElementType]struct { - begin func(int32) + begin func(int32, string) end func() }{ - NodeElementInput: {imnodes.BeginInputAttribute, imnodes.EndInputAttribute}, - NodeElementOutput: {imnodes.BeginOutputAttribute, imnodes.EndOutputAttribute}, - NodeElementBody: {imnodes.BeginStaticAttribute, imnodes.EndStaticAttribute}, - NodeElementTitleBar: {func(int32) { imnodes.BeginNodeTitleBar() }, imnodes.EndNodeTitleBar}, + NodeElementInput: {func(id int32, alias string) { + imnodes.BeginInputAttribute(id) + s.inputAlias[s.idCounter] = alias + }, imnodes.EndInputAttribute}, + NodeElementOutput: {func(id int32, alias string) { + imnodes.BeginOutputAttribute(id) + s.outputAlias[s.idCounter] = alias + }, imnodes.EndOutputAttribute}, + NodeElementBody: {func(id int32, _ string) { imnodes.BeginStaticAttribute(id) }, imnodes.EndStaticAttribute}, + NodeElementTitleBar: {func(int32, string) { imnodes.BeginNodeTitleBar() }, imnodes.EndNodeTitleBar}, } // Assert(n.layout != nil && len(n.layout) > 0, "NodeWidget", "BuildNode", "Node layout is required") - imnodes.BeginNode(*idCounter) - *idCounter++ + imnodes.BeginNode(s.idCounter) + s.idCounter++ for _, element := range n.elements { if element.layout != nil { @@ -173,8 +187,8 @@ func (n *NodeWidget) BuildNode(idCounter *int32) { if !ok { panic(fmt.Sprintf("NodeWidget:BuildNode: Unknown node element type", element.elementType)) } - f.begin(*idCounter) - *idCounter++ + f.begin(s.idCounter, element.id) + s.idCounter++ element.layout.Build() f.end() } @@ -189,10 +203,9 @@ type LinkWidget struct { endID int32 } -func Link(linkID, startID, endID int32) *LinkWidget { +func Link(startID, endID string) *LinkWidget { return &LinkWidget{ startID: startID, endID: endID, - linkID: linkID, } } diff --git a/examples/nodes/nodes.go b/examples/nodes/nodes.go index bf7ec581..5a88cbf4 100644 --- a/examples/nodes/nodes.go +++ b/examples/nodes/nodes.go @@ -12,8 +12,10 @@ func loop() { Static( giu.Label("Main content"), ).Output( + "out", giu.Label("Output attribute"), ).Input( + "in", giu.Label("Input attribute"), ), giu.Node().Static( @@ -21,6 +23,7 @@ func loop() { ).TitleBar( giu.Label("This is a title bar"), ).Input( + "in2", giu.Label("Iput attribute"), ), ), From 90b4846ed8c2aef9978fbd444cebbd6602e430e5 Mon Sep 17 00:00:00 2001 From: gucio321 Date: Wed, 9 Apr 2025 11:42:06 +0200 Subject: [PATCH 6/7] nodes work --- Nodes.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Nodes.go b/Nodes.go index c6bb102a..636672b0 100644 --- a/Nodes.go +++ b/Nodes.go @@ -59,16 +59,16 @@ type nodeElement struct { type NodeEditorWidget struct { nodes []*NodeWidget idCounter int32 - inputAlias map[int32]string - outputAlias map[int32]string + inputAlias map[string]int32 + outputAlias map[string]int32 id ID } func NodeEditor() *NodeEditorWidget { return &NodeEditorWidget{ idCounter: 0, - inputAlias: make(map[int32]string), - outputAlias: make(map[int32]string), + inputAlias: make(map[string]int32), + outputAlias: make(map[string]int32), id: GenAutoID("NodeEditor"), } } @@ -199,8 +199,8 @@ func (n *NodeWidget) BuildNode(s *NodeEditorWidget) { type LinkWidget struct { linkID int32 - startID int32 - endID int32 + startID string + endID string } func Link(startID, endID string) *LinkWidget { From 836bd94bd9ba5cc4e3a9722d72474f8bf8f8257d Mon Sep 17 00:00:00 2001 From: gucio321 Date: Tue, 20 May 2025 13:37:39 +0200 Subject: [PATCH 7/7] Nodes: fix names conflict --- Nodes.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Nodes.go b/Nodes.go index 636672b0..847a7775 100644 --- a/Nodes.go +++ b/Nodes.go @@ -99,7 +99,7 @@ func (n *NodeEditorWidget) Build() { imnodes.EndNodeEditor() - potentialNewLink := Link(state.linksCounter, 0, 0) + potentialNewLink := NodeLink(state.linksCounter, 0, 0) if imnodes.IsLinkCreatedBoolPtr(&potentialNewLink.startID, &potentialNewLink.endID) { state.links = append(state.links, potentialNewLink) state.linksCounter++ @@ -197,14 +197,14 @@ func (n *NodeWidget) BuildNode(s *NodeEditorWidget) { imnodes.EndNode() } -type LinkWidget struct { +type NodeLinkWidget struct { linkID int32 startID string endID string } -func Link(startID, endID string) *LinkWidget { - return &LinkWidget{ +func NodeLink(startID, endID string) *LinkWidget { + return &NodeLinkWidget{ startID: startID, endID: endID, }