Skip to content

Commit

Permalink
Fixed display flickering
Browse files Browse the repository at this point in the history
1) Removed Refresh() call from AnchoredText MouseIn and MouseOut functions.
2) Changed DiagramWidget so that the renderer's objects come back in an ordered sequence (random ordering was the cause of the flicker). This required changing the widget's representation of the inventory of diagram elements to a linked list, which in turn required a number of modifications to functions involving lists of Nodes and Links.
  • Loading branch information
pbrown12303 committed Sep 6, 2023
1 parent c2b4174 commit 6f11e6d
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 72 deletions.
10 changes: 6 additions & 4 deletions widget/diagramwidget/anchoredtext.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
145 changes: 92 additions & 53 deletions widget/diagramwidget/diagram.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package diagramwidget

import (
"container/list"
"image/color"
"reflect"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/driver/desktop"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{},
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
}
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
}
}
Expand Down Expand Up @@ -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()
}
}
12 changes: 6 additions & 6 deletions widget/diagramwidget/diagram_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

}
11 changes: 7 additions & 4 deletions widget/diagramwidget/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions widget/diagramwidget/springforcelayout.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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)})
}

Expand Down

0 comments on commit 6f11e6d

Please sign in to comment.