diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index 2b5992c3..2c8d3f4d 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -38,6 +38,7 @@ func NewAnchoredText(text string) *AnchoredText { at.displayedTextBinding = binding.NewString() at.displayedTextBinding.Set(text) at.textEntry = widget.NewEntryWithData(at.displayedTextBinding) + at.displayedTextBinding.AddListener(at) at.textEntry.Wrapping = fyne.TextWrapOff at.textEntry.Validator = nil at.ExtendBaseWidget(at) @@ -54,6 +55,11 @@ func (at *AnchoredText) CreateRenderer() fyne.WidgetRenderer { return atr } +// DataChanged is the callback function for the displayedTextBinding. +func (at *AnchoredText) DataChanged() { + at.Refresh() +} + // Displace moves the anchored text relative to its reference position. func (at *AnchoredText) Displace(delta fyne.Position) { at.Move(at.Position().Add(delta)) @@ -91,8 +97,6 @@ func (at *AnchoredText) MinSize() fyne.Size { // MouseIn is one of the required methods for a mouseable widget. func (at *AnchoredText) MouseIn(event *desktop.MouseEvent) { - // at.textObject.TextStyle.Bold = true - at.Refresh() } // MouseMoved is one of the required methods for a mouseable widget @@ -102,8 +106,6 @@ func (at *AnchoredText) MouseMoved(event *desktop.MouseEvent) { // MouseOut is one of the required methods for a mouseable widget func (at *AnchoredText) MouseOut() { - // at.textObject.TextStyle.Bold = false - at.Refresh() } // Move overrides the BaseWidget's Move method. It updates the anchored text's offset diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 973e6b72..211adcee 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -1,8 +1,8 @@ package diagramwidget import ( + "container/list" "image/color" - "reflect" "fyne.io/fyne/v2" "fyne.io/fyne/v2/driver/desktop" @@ -33,11 +33,12 @@ type DiagramWidget struct { DesiredSize fyne.Size DefaultDiagramElementProperties DiagramElementProperties - Nodes map[string]DiagramNode - Links map[string]DiagramLink - primarySelection DiagramElement - selection map[string]DiagramElement - diagramElementLinkDependencies map[string][]linkPadPair + DiagramElements *list.List + // Nodes map[string]DiagramNode + // Links map[string]DiagramLink + primarySelection DiagramElement + selection map[string]DiagramElement + diagramElementLinkDependencies map[string][]linkPadPair // ConnectionTransaction holds transient data during the creation of a link. It is public for testing purposes ConnectionTransaction *ConnectionTransaction // IsConnectionAllowedCallback is called to determine whether a particular connection between a link and a pad is allowed @@ -75,11 +76,12 @@ type DiagramWidget struct { // to data structures within the of the application. It is expected to be unique within the application func NewDiagramWidget(id string) *DiagramWidget { dw := &DiagramWidget{ - ID: id, - DesiredSize: fyne.Size{Width: 800, Height: 600}, - Offset: fyne.Position{X: 0, Y: 0}, - Nodes: map[string]DiagramNode{}, - Links: map[string]DiagramLink{}, + ID: id, + DesiredSize: fyne.Size{Width: 800, Height: 600}, + Offset: fyne.Position{X: 0, Y: 0}, + DiagramElements: list.New(), + // Nodes: map[string]DiagramNode{}, + // Links: map[string]DiagramLink{}, selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, } @@ -116,7 +118,8 @@ func (dw *DiagramWidget) addElementToSelection(de DiagramElement) { // addLink adds a link to the diagram func (dw *DiagramWidget) addLink(link DiagramLink) { - dw.Links[link.GetDiagramElementID()] = link + dw.DiagramElements.PushBack(link) + // dw.Links[link.GetDiagramElementID()] = link link.Refresh() // TODO add logic to rezise diagram if necessary } @@ -139,7 +142,8 @@ func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link * // addNode adds a node to the diagram func (dw *DiagramWidget) addNode(node DiagramNode) { - dw.Nodes[node.GetDiagramElementID()] = node + dw.DiagramElements.PushBack(node) + // dw.Nodes[node.GetDiagramElementID()] = node node.Refresh() // TODO add logic to rezise diagram if necessary } @@ -218,12 +222,14 @@ func (dw *DiagramWidget) GetBackgroundColor() color.Color { // GetDiagramElement returns the diagram element with the specified ID, whether // it is a node or a link func (dw *DiagramWidget) GetDiagramElement(elementID string) DiagramElement { - var de DiagramElement - de = dw.Nodes[elementID] - if de == nil || reflect.ValueOf(de).IsNil() { - de = dw.Links[elementID] + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + value := listElement.Value + diagramElement := value.(DiagramElement) + if diagramElement.GetDiagramElementID() == elementID { + return diagramElement + } } - return de + return nil } // GetForegroundColor returns the foreground color from the diagram's theme, which may @@ -232,18 +238,62 @@ func (dw *DiagramWidget) GetForegroundColor() color.Color { return dw.DefaultDiagramElementProperties.ForegroundColor } -// GetDiagramElements returns a map of all of the diagram's DiagramElements -func (dw *DiagramWidget) GetDiagramElements() map[string]DiagramElement { - diagramElements := map[string]DiagramElement{} - for key, node := range dw.Nodes { - diagramElements[key] = node - } - for key, link := range dw.Links { - diagramElements[key] = link +// GetDiagramElements returns an array all of the diagram's DiagramElements +func (dw *DiagramWidget) GetDiagramElements() []DiagramElement { + diagramElements := []DiagramElement{} + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + diagramElement := listElement.Value.(DiagramElement) + diagramElements = append(diagramElements, diagramElement) } return diagramElements } +// GetDiagramLink returns the diagram link with the indicated ID +func (dw *DiagramWidget) GetDiagramLink(id string) DiagramLink { + { + diagramElement := dw.GetDiagramElement(id) + if diagramElement != nil && diagramElement.IsLink() { + return diagramElement.(DiagramLink) + } + return nil + } +} + +// GetDiagramLinks returns a map of all of the diagram's DiagramElements +func (dw *DiagramWidget) GetDiagramLinks() []DiagramLink { + diagramLinks := []DiagramLink{} + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + diagramElement := listElement.Value.(DiagramElement) + if diagramElement.IsLink() { + diagramLinks = append(diagramLinks, diagramElement.(DiagramLink)) + } + } + return diagramLinks +} + +// GetDiagramNode returns the diagram node with the indicated ID +func (dw *DiagramWidget) GetDiagramNode(id string) DiagramNode { + { + diagramElement := dw.GetDiagramElement(id) + if diagramElement != nil && diagramElement.IsNode() { + return diagramElement.(DiagramNode) + } + return nil + } +} + +// GetDiagramNodes returns a map of all of the diagram's DiagramElements +func (dw *DiagramWidget) GetDiagramNodes() []DiagramNode { + diagramNodes := []DiagramNode{} + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + diagramElement := listElement.Value.(DiagramElement) + if diagramElement.IsNode() { + diagramNodes = append(diagramNodes, diagramElement.(DiagramNode)) + } + } + return diagramNodes +} + // GetPrimarySelection returns the diagram element that is currently selected func (dw *DiagramWidget) GetPrimarySelection() DiagramElement { return dw.primarySelection @@ -253,13 +303,9 @@ func (dw *DiagramWidget) GetPrimarySelection() DiagramElement { // (i.e. the pad) masks the parent's Tappable interface. This function (and all references to // it) should be removed when this issue has been resolved func (dw *DiagramWidget) hideAllPads() { - for _, node := range dw.Nodes { - for _, pad := range node.GetConnectionPads() { - pad.Hide() - } - } - for _, link := range dw.Links { - for _, pad := range link.GetConnectionPads() { + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + diagramElement := listElement.Value.(DiagramElement) + for _, pad := range diagramElement.GetConnectionPads() { pad.Hide() } } @@ -370,10 +416,13 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { dw.RemoveElement(pair.link.id) } delete(dw.diagramElementLinkDependencies, elementID) - if element.IsNode() { - delete(dw.Nodes, elementID) - } else if element.IsLink() { - delete(dw.Links, elementID) + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + diagramElement := listElement.Value.(DiagramElement) + if diagramElement.GetDiagramElementID() == elementID { + dw.DiagramElements.Remove(listElement) + } + } + if element.IsLink() { dw.removeDependenciesInvolvingLink(elementID) } dw.Refresh() @@ -402,13 +451,9 @@ func (dw *DiagramWidget) SelectDiagramElementNoCallback(id string) { // (i.e. the pad) masks the parent's Tappable interface. This function (and all references to // it) should be removed when this issue has been resolved func (dw *DiagramWidget) showAllPads() { - for _, node := range dw.Nodes { - for _, pad := range node.GetConnectionPads() { - pad.Show() - } - } - for _, link := range dw.Links { - for _, pad := range link.GetConnectionPads() { + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + diagramElement := listElement.Value.(DiagramElement) + for _, pad := range diagramElement.GetConnectionPads() { pad.Show() } } @@ -447,20 +492,14 @@ func (r *diagramWidgetRenderer) MinSize() fyne.Size { func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { obj := make([]fyne.CanvasObject, 0) - for _, n := range r.diagramWidget.Nodes { + for _, n := range r.diagramWidget.GetDiagramElements() { obj = append(obj, n) } - for _, e := range r.diagramWidget.Links { - obj = append(obj, e) - } return obj } func (r *diagramWidgetRenderer) Refresh() { - for _, e := range r.diagramWidget.Links { - e.Refresh() - } - for _, n := range r.diagramWidget.Nodes { - n.Refresh() + for _, obj := range r.diagramWidget.GetDiagramElements() { + obj.Refresh() } } diff --git a/widget/diagramwidget/diagram_test.go b/widget/diagramwidget/diagram_test.go index 380a3e8c..3a3a4d75 100644 --- a/widget/diagramwidget/diagram_test.go +++ b/widget/diagramwidget/diagram_test.go @@ -21,25 +21,25 @@ func TestDependencies(t *testing.T) { assert.Equal(t, 0, len(diagram.diagramElementLinkDependencies)) linkID := "Link1" link := NewDiagramLink(diagram, linkID) - link.SetSourcePad(node1.pads["default"]) - link.SetTargetPad(node2.pads["default"]) + link.SetSourcePad(node1.GetDefaultConnectionPad()) + link.SetTargetPad(node2.GetDefaultConnectionPad()) assert.NotNil(t, link) assert.Equal(t, 2, len(diagram.diagramElementLinkDependencies)) node1Dependencies := diagram.diagramElementLinkDependencies[node1ID] assert.Equal(t, 1, len(node1Dependencies)) assert.Equal(t, link, node1Dependencies[0].link) - assert.Equal(t, node1.pads["default"], node1Dependencies[0].pad) + assert.Equal(t, node1.GetDefaultConnectionPad(), node1Dependencies[0].pad) node2Dependencies := diagram.diagramElementLinkDependencies[node2ID] assert.Equal(t, 1, len(node2Dependencies)) assert.Equal(t, link, node2Dependencies[0].link) - assert.Equal(t, node2.pads["default"], node2Dependencies[0].pad) + assert.Equal(t, node2.GetDefaultConnectionPad(), node2Dependencies[0].pad) // Now test the dependency management when a node is deleted diagram.RemoveElement(node2ID) - assert.Nil(t, diagram.Nodes[node2ID]) - assert.Nil(t, diagram.Links[linkID]) + assert.Nil(t, diagram.GetDiagramElement(node2ID)) + assert.Nil(t, diagram.GetDiagramElement(linkID)) assert.Equal(t, 0, len(diagram.diagramElementLinkDependencies)) } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index da6c22cf..7a9a6108 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -14,7 +14,9 @@ import ( type DiagramNode interface { DiagramElement getBaseDiagramNode() *BaseDiagramNode + GetEdgePad() ConnectionPad R2Center() r2.Vec2 + SetInnerObject(fyne.CanvasObject) } // Validate that BaseDiagramNode implements DiagramElement and Tappable @@ -49,10 +51,11 @@ type BaseDiagramNode struct { // nodeID string must be unique across all of the DiagramElements in the diagram. It can be used // to retrieve the DiagramNode from the DiagramWidget. It is permissible for the canvas object to // be nil when this function is called and then add the canvas object later. -func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string) *BaseDiagramNode { - bdn := &BaseDiagramNode{} - InitializeBaseDiagramNode(bdn, diagram, obj, nodeID) - return bdn +func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string) DiagramNode { + var diagramNode DiagramNode + diagramNode = &BaseDiagramNode{} + InitializeBaseDiagramNode(diagramNode, diagram, obj, nodeID) + return diagramNode } // InitializeBaseDiagramNode is used to initailize the BaseDiagramNode. It must be called by any extensions to the BaseDiagramNode diff --git a/widget/diagramwidget/springforcelayout.go b/widget/diagramwidget/springforcelayout.go index 5d036e96..a5eedb1d 100644 --- a/widget/diagramwidget/springforcelayout.go +++ b/widget/diagramwidget/springforcelayout.go @@ -11,7 +11,7 @@ import ( // adjacent returns true if there is at least one edge between n1 and n2 func adjacent(dw *DiagramWidget, n1, n2 DiagramNode) bool { // TODO: expensive, may be worth caching? - for _, e := range dw.Links { + for _, e := range dw.GetDiagramLinks() { if ((e.GetSourcePad().GetPadOwner() == n1) && (e.GetTargetPad().GetPadOwner() == n2)) || ((e.GetSourcePad().GetPadOwner() == n2) && (e.GetTargetPad().GetPadOwner() == n1)) { return true } @@ -60,13 +60,13 @@ func calculateForce(dw *DiagramWidget, n1, n2 DiagramNode, targetLength float64) // StepForceLayout calculates one step of force directed graph layout, with // the target distance between adjacent nodes being targetLength. func StepForceLayout(dw *DiagramWidget, targetLength float64) { - deltas := make(map[string]r2.Vec2) + deltas := make(map[int]r2.Vec2) // calculate all the deltas from the current state - for k, nk := range dw.Nodes { + for k, nk := range dw.GetDiagramNodes() { deltas[k] = r2.V2(0, 0) - for j, nj := range dw.Nodes { + for j, nj := range dw.GetDiagramNodes() { if j == k { continue } @@ -75,7 +75,7 @@ func StepForceLayout(dw *DiagramWidget, targetLength float64) { } // flip into current state - for k, nk := range dw.Nodes { + for k, nk := range dw.GetDiagramNodes() { dw.DisplaceNode(nk, fyne.Position{X: float32(deltas[k].X), Y: float32(deltas[k].Y)}) }