From 931b1397fc889a356b984c8ec610123fad1e8091 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Mon, 13 Mar 2023 10:54:32 -0400 Subject: [PATCH 01/53] Added diagramwidget, updated terminology --- cmd/diagramdemo/main.go | 114 +++++++++ cmd/tabledemo/main.go | 80 ++++++ cmd/tabledemo/sample.csv | 9 + cmd/viewportdemo/main.go | 112 +++++++++ go.mod | 4 +- go.sum | 233 ++++++++++++++++- .../diagramwidget/.gitarchive/COMMIT_EDITMSG | 1 + widget/diagramwidget/.gitarchive/FETCH_HEAD | 1 + widget/diagramwidget/.gitarchive/HEAD | 1 + widget/diagramwidget/.gitarchive/config | 12 + widget/diagramwidget/.gitarchive/description | 1 + .../.gitarchive/hooks/applypatch-msg.sample | 15 ++ .../.gitarchive/hooks/commit-msg.sample | 24 ++ .../hooks/fsmonitor-watchman.sample | 174 +++++++++++++ .../.gitarchive/hooks/post-update.sample | 8 + .../.gitarchive/hooks/pre-applypatch.sample | 14 ++ .../.gitarchive/hooks/pre-commit.sample | 49 ++++ .../.gitarchive/hooks/pre-merge-commit.sample | 13 + .../.gitarchive/hooks/pre-push.sample | 53 ++++ .../.gitarchive/hooks/pre-rebase.sample | 169 +++++++++++++ .../.gitarchive/hooks/pre-receive.sample | 24 ++ .../hooks/prepare-commit-msg.sample | 42 ++++ .../.gitarchive/hooks/push-to-checkout.sample | 78 ++++++ .../.gitarchive/hooks/update.sample | 128 ++++++++++ widget/diagramwidget/.gitarchive/index | Bin 0 -> 2242 bytes widget/diagramwidget/.gitarchive/info/exclude | 6 + widget/diagramwidget/.gitarchive/logs/HEAD | 2 + .../.gitarchive/logs/refs/heads/master | 2 + .../.gitarchive/logs/refs/remotes/origin/HEAD | 1 + .../17/405e5eeee11b3313baec5a5f30836880bcb6bb | Bin 0 -> 391 bytes .../6f/12fb35c43922c619da894876d6c1905ca4fd8c | Bin 0 -> 1053 bytes .../90/027c0cdc3acf4f23846a4e71b21a0b6661a3ab | Bin 0 -> 52 bytes .../ca/7d6114760e08cee5a2e622b98c97213732c16c | 2 + ...52e4c029b0d1030cb93a7bfb9152a2869365c2.idx | Bin 0 -> 5664 bytes ...2e4c029b0d1030cb93a7bfb9152a2869365c2.pack | Bin 0 -> 47997 bytes widget/diagramwidget/.gitarchive/packed-refs | 2 + .../.gitarchive/refs/heads/master | 1 + .../.gitarchive/refs/remotes/origin/HEAD | 1 + widget/diagramwidget/.gitignore | 21 ++ widget/diagramwidget/LICENSE | 30 +++ widget/diagramwidget/README.md | 36 +++ widget/diagramwidget/arrowhead/arrowhead.go | 191 ++++++++++++++ widget/diagramwidget/diagram.go | 125 +++++++++ widget/diagramwidget/forcelayout.go | 84 +++++++ widget/diagramwidget/geometry/geometry.go | 2 + widget/diagramwidget/geometry/r2/box.go | 175 +++++++++++++ widget/diagramwidget/geometry/r2/geometry.go | 1 + widget/diagramwidget/geometry/r2/line.go | 129 ++++++++++ widget/diagramwidget/geometry/r2/vec2.go | 60 +++++ widget/diagramwidget/link.go | 141 +++++++++++ widget/diagramwidget/node.go | 238 ++++++++++++++++++ widget/diagramwidget/table/table.go | 196 +++++++++++++++ widget/diagramwidget/viewport/viewport.go | 173 +++++++++++++ 53 files changed, 2976 insertions(+), 2 deletions(-) create mode 100644 cmd/diagramdemo/main.go create mode 100644 cmd/tabledemo/main.go create mode 100644 cmd/tabledemo/sample.csv create mode 100644 cmd/viewportdemo/main.go create mode 100644 widget/diagramwidget/.gitarchive/COMMIT_EDITMSG create mode 100644 widget/diagramwidget/.gitarchive/FETCH_HEAD create mode 100644 widget/diagramwidget/.gitarchive/HEAD create mode 100644 widget/diagramwidget/.gitarchive/config create mode 100644 widget/diagramwidget/.gitarchive/description create mode 100644 widget/diagramwidget/.gitarchive/hooks/applypatch-msg.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/commit-msg.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/fsmonitor-watchman.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/post-update.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-applypatch.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-commit.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-merge-commit.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-push.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-rebase.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-receive.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/prepare-commit-msg.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/push-to-checkout.sample create mode 100644 widget/diagramwidget/.gitarchive/hooks/update.sample create mode 100644 widget/diagramwidget/.gitarchive/index create mode 100644 widget/diagramwidget/.gitarchive/info/exclude create mode 100644 widget/diagramwidget/.gitarchive/logs/HEAD create mode 100644 widget/diagramwidget/.gitarchive/logs/refs/heads/master create mode 100644 widget/diagramwidget/.gitarchive/logs/refs/remotes/origin/HEAD create mode 100644 widget/diagramwidget/.gitarchive/objects/17/405e5eeee11b3313baec5a5f30836880bcb6bb create mode 100644 widget/diagramwidget/.gitarchive/objects/6f/12fb35c43922c619da894876d6c1905ca4fd8c create mode 100644 widget/diagramwidget/.gitarchive/objects/90/027c0cdc3acf4f23846a4e71b21a0b6661a3ab create mode 100644 widget/diagramwidget/.gitarchive/objects/ca/7d6114760e08cee5a2e622b98c97213732c16c create mode 100644 widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.idx create mode 100644 widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.pack create mode 100644 widget/diagramwidget/.gitarchive/packed-refs create mode 100644 widget/diagramwidget/.gitarchive/refs/heads/master create mode 100644 widget/diagramwidget/.gitarchive/refs/remotes/origin/HEAD create mode 100644 widget/diagramwidget/.gitignore create mode 100644 widget/diagramwidget/LICENSE create mode 100644 widget/diagramwidget/README.md create mode 100644 widget/diagramwidget/arrowhead/arrowhead.go create mode 100644 widget/diagramwidget/diagram.go create mode 100644 widget/diagramwidget/forcelayout.go create mode 100644 widget/diagramwidget/geometry/geometry.go create mode 100644 widget/diagramwidget/geometry/r2/box.go create mode 100644 widget/diagramwidget/geometry/r2/geometry.go create mode 100644 widget/diagramwidget/geometry/r2/line.go create mode 100644 widget/diagramwidget/geometry/r2/vec2.go create mode 100644 widget/diagramwidget/link.go create mode 100644 widget/diagramwidget/node.go create mode 100644 widget/diagramwidget/table/table.go create mode 100644 widget/diagramwidget/viewport/viewport.go diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go new file mode 100644 index 00000000..0f3cf8dd --- /dev/null +++ b/cmd/diagramdemo/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "fmt" + "image/color" + "time" + + "fyne.io/x/fyne/widget/diagramwidget" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" +) + +var forceticks int = 0 + +var globaldiagram *diagramwidget.DiagramWidget + +func forceanim() { + + // XXX: very naughty -- accesses shared memory in potentially unsafe + // ways, this almost certainly has race conditions... don't do this! + + for { + if forceticks > 0 { + globaldiagram.StepForceLayout(300) + globaldiagram.Refresh() + forceticks-- + fmt.Printf("forceticks=%d\n", forceticks) + } + + time.Sleep(time.Millisecond * (1000 / 30)) + } +} + +func main() { + app := app.New() + w := app.NewWindow("Diagram Demo") + + w.SetMaster() + + g := diagramwidget.NewDiagram() + + go forceanim() + + l := widget.NewLabel("teeexxttt") + n := diagramwidget.NewDiagramNode(g, l) + g.Nodes["node0"] = n + n1 := n + + b := widget.NewButton("button", func() { fmt.Printf("tapped!\n") }) + n = diagramwidget.NewDiagramNode(g, b) + n.Move(fyne.Position{X: 200, Y: 200}) + g.Nodes["node1"] = n + n2 := n + + n = diagramwidget.NewDiagramNode(g, nil) + c := container.NewVBox( + widget.NewLabel("Fancy node!"), + widget.NewButton("Up", func() { + n.Displace(fyne.Position{X: 0, Y: -10}) + n.Refresh() + }), + widget.NewButton("Down", func() { + n.Displace(fyne.Position{X: 0, Y: 10}) + n.Refresh() + }), + container.NewHBox( + widget.NewButton("Left", func() { + n.Displace(fyne.Position{X: -10, Y: 0}) + n.Refresh() + }), + widget.NewButton("Right", func() { + n.Displace(fyne.Position{X: 10, Y: 0}) + n.Refresh() + }), + ), + ) + n.InnerObject = c + n.Move(fyne.Position{X: 300, Y: 300}) + g.Nodes["node2"] = n + n3 := n + + n = diagramwidget.NewDiagramNode(g, widget.NewButton("force layout step", func() { + g.StepForceLayout(300) + g.Refresh() + })) + n.Move(fyne.Position{X: 400, Y: 200}) + g.Nodes["node4"] = n + n4 := n + + n = diagramwidget.NewDiagramNode(g, widget.NewButton("auto layout", func() { + forceticks += 100 + g.Refresh() + })) + n.Move(fyne.Position{X: 400, Y: 500}) + g.Nodes["node5"] = n + n5 := n + + globaldiagram = g + + g.Links["edge0"] = diagramwidget.NewDiagramEdge(g, n1, n2) + g.Links["edge1"] = diagramwidget.NewDiagramEdge(g, n3, n2) + g.Links["edge1"].LinkColor = color.RGBA{255, 64, 64, 255} + g.Links["edge1"].Directed = true + g.Links["edge2"] = diagramwidget.NewDiagramEdge(g, n1, n4) + g.Links["edge3"] = diagramwidget.NewDiagramEdge(g, n3, n4) + g.Links["edge4"] = diagramwidget.NewDiagramEdge(g, n5, n4) + + w.SetContent(g) + + w.ShowAndRun() +} diff --git a/cmd/tabledemo/main.go b/cmd/tabledemo/main.go new file mode 100644 index 00000000..3d31a93b --- /dev/null +++ b/cmd/tabledemo/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "fmt" + "io/ioutil" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/dialog" + + "fyne.io/x/fyne/widget/diagramwidget/table" + + "github.com/rocketlaunchr/dataframe-go" + "github.com/rocketlaunchr/dataframe-go/imports" +) + +var mainTable *table.TableWidget + +func main() { + + s1 := dataframe.NewSeriesInt64("day", nil, 1, 2, 3, 4, 5, 6, 7, 8) + s2 := dataframe.NewSeriesFloat64("sales", nil, 50.3, 23.4, 56.2, nil, nil, 84.2, 72, 89) + s3 := dataframe.NewSeriesString("string!", nil, "foo", "bar", "three", "four", "five", "six", "seven", "eight") + df := dataframe.NewDataFrame(s1, s2, s3) + + fmt.Print(df.Table()) + + app := app.New() + w := app.NewWindow("Table Demo") + + w.SetMainMenu( + fyne.NewMainMenu( + fyne.NewMenu("File", + fyne.NewMenuItem("Load CSV", func() { + dialog.ShowFileOpen(func(uri fyne.URIReadCloser, e error) { + + if e != nil { + dialog.ShowError(e, w) + return + } + + content, err := ioutil.ReadAll(uri) + if err != nil { + dialog.ShowError(err, w) + return + } + text := string(content) + reader := strings.NewReader(text) + + ctx := context.Background() + opts := imports.CSVLoadOptions{ + InferDataTypes: true, + } + loaded, err := imports.LoadFromCSV(ctx, reader, opts) + + if err != nil { + dialog.ShowError(e, w) + return + } + + fmt.Printf("loaded new table:\n%s\n", loaded.Table()) + + mainTable.ReplaceDataFrame(loaded) + + }, w) + }), + ), + ), + ) + w.SetMaster() + + mainTable = table.NewTableWidget(df) + + w.SetContent(mainTable) + + w.ShowAndRun() + +} diff --git a/cmd/tabledemo/sample.csv b/cmd/tabledemo/sample.csv new file mode 100644 index 00000000..756448cb --- /dev/null +++ b/cmd/tabledemo/sample.csv @@ -0,0 +1,9 @@ +Country,Date,Age,Amount,Id +"United States",2012-02-01,50,112.1,01234 +"United States",2012-02-01,32,321.31,54320 +"United Kingdom",2012-02-01,17,18.2,12345 +"United States",2012-02-01,32,321.31,54320 +"United Kingdom",2015-05-07,NA,18.2,12345 +"United States",2012-02-01,32,321.31,54320 +"United States",2012-02-01,32,321.31,54320 +Spain,2012-02-01,66,555.42,00241 diff --git a/cmd/viewportdemo/main.go b/cmd/viewportdemo/main.go new file mode 100644 index 00000000..c3f4475b --- /dev/null +++ b/cmd/viewportdemo/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "image/color" + "strconv" + + "fyne.io/x/fyne/widget/diagramwidget/viewport" + + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" + + "github.com/pkg/profile" +) + +func main() { + + defer profile.Start(profile.MemProfile).Stop() + + app := app.New() + w := app.NewWindow("Viewport Demo") + + w.SetMaster() + + stepSize := 1.0 + + stepSizeEntry := widget.NewEntry() + stepSizeEntry.OnChanged = func(text string) { + var err error + stepSize, err = strconv.ParseFloat(text, 64) + if err != nil { + stepSizeEntry.SetText(fmt.Sprintf("%f", stepSize)) + } + } + stepSizeEntry.SetText("1.0") + + vp := viewport.NewViewportWidget(800, 600) + + vp.Objects = append(vp.Objects, &viewport.ViewportLine{ + X1: 20, + Y1: 20, + X2: 200, + Y2: 400, + StrokeColor: color.RGBA{255, 255, 255, 255}, + StrokeWidth: 1, + }) + + vp.Objects = append(vp.Objects, &viewport.ViewportLine{ + X1: 40, + Y1: 20, + X2: 220, + Y2: 400, + StrokeColor: color.RGBA{255, 128, 128, 255}, + StrokeWidth: 3, + }) + + vp.Objects = append(vp.Objects, &viewport.ViewportLine{ + X1: 10, + Y1: 200, + X2: 300, + Y2: 10, + StrokeColor: color.RGBA{64, 255, 64, 255}, + StrokeWidth: 0.5, + }) + + w.SetContent(container.NewHSplit( + vp, + container.NewVBox( + stepSizeEntry, + widget.NewButton("Pan Left", func() { + fmt.Printf("vp.XOffset %v", vp.XOffset) + vp.XOffset += vp.Zoom * stepSize + vp.Refresh() + fmt.Printf(" -> %v\n", vp.XOffset) + }), + widget.NewButton("Pan Right", func() { + fmt.Printf("vp.XOffset %v", vp.XOffset) + vp.XOffset -= vp.Zoom * stepSize + vp.Refresh() + fmt.Printf(" -> %v\n", vp.XOffset) + }), + widget.NewButton("Pan Up", func() { + fmt.Printf("vp.YOffset %v", vp.YOffset) + vp.YOffset += vp.Zoom * stepSize + vp.Refresh() + fmt.Printf(" -> %v\n", vp.YOffset) + }), + widget.NewButton("Pan Down", func() { + fmt.Printf("vp.YOffset %v", vp.YOffset) + vp.YOffset -= vp.Zoom * stepSize + vp.Refresh() + fmt.Printf(" -> %v\n", vp.YOffset) + }), + widget.NewButton("Zoom In", func() { + fmt.Printf("vp.Zoom %v", vp.Zoom) + vp.Zoom *= 1.15 + vp.Refresh() + fmt.Printf(" -> %v\n", vp.Zoom) + }), + widget.NewButton("Zoom Out", func() { + fmt.Printf("vp.Zoom %v", vp.Zoom) + vp.Zoom *= 0.85 + vp.Refresh() + fmt.Printf(" -> %v\n", vp.Zoom) + }), + ), + )) + + w.ShowAndRun() + +} diff --git a/go.mod b/go.mod index e8834a43..38aec92b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,9 @@ require ( github.com/eclipse/paho.mqtt.golang v1.3.5 github.com/gorilla/websocket v1.4.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/stretchr/testify v1.7.2 + github.com/pkg/profile v1.7.0 + github.com/rocketlaunchr/dataframe-go v0.0.0-20211025052708-a1030444159b + github.com/stretchr/testify v1.8.0 github.com/wagslane/go-password-validator v0.3.0 golang.org/x/image v0.0.0-20220601225756-64ec528b34cd ) diff --git a/go.sum b/go.sum index fa9ab427..daf4d336 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -5,6 +6,7 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= @@ -17,6 +19,7 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -43,16 +46,38 @@ fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVm fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DzananGanic/numericalgo v0.0.0-20170804125527-2b389385baf0/go.mod h1:uIo7VpFvBkDQoCyKqUL/mTNjpOlv1KdWaJyCsBSpCe4= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.0.0-20181112125854-24918abba929 h1:ubPe2yRkS6A/X37s0TVGfuN42NV2h0BlzWj0X76RoUw= +github.com/apache/thrift v0.0.0-20181112125854-24918abba929/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.30.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blend/go-sdk v1.1.1/go.mod h1:IP1XHXFveOXHRnojRJO7XvqWGqyzevtXND9AdSztAe8= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/brianvoe/gofakeit/v4 v4.3.0/go.mod h1:GC/GhKWdGJ2eskBf4zGdjo3eHj8rX4E9hFLFg0bqK4s= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -61,13 +86,24 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cnkei/gospline v0.0.0-20191204072713-842a72f86331/go.mod h1:DXXGDL64/wxXgBSgmGMEL0vYC0tdvpgNhkJrvavhqDM= +github.com/colinmarc/hdfs/v2 v2.1.1/go.mod h1:M3x+k8UKKmxtFu++uAZ0OtDU8jR3jnaZIAc6yK4Ue0c= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk= +github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -78,8 +114,14 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -90,6 +132,7 @@ github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYn github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= @@ -99,12 +142,18 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/goccy/go-json v0.7.6 h1:H0wq4jppBQ+9222sk5+hPLL25abZQiRuQ6YPnjO9c+A= +github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -117,6 +166,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -134,6 +184,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -147,7 +199,10 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -163,6 +218,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -173,9 +230,14 @@ github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25d github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY= github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/guptarohit/asciigraph v0.5.1 h1:rzRUdibSt3ff75gVGtcUXQ0dEkNgG0A20fXkA8cOMsA= +github.com/guptarohit/asciigraph v0.5.1/go.mod h1:9fYEfE5IGJGxlP1B+w8wHFy7sNZMhPtn59f0RLtpRFM= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -186,6 +248,7 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -196,10 +259,18 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/icza/gox v0.0.0-20200320174535-a6ff52ab3d90/go.mod h1:VbcN86fRkkUMPX2ufM85Um8zFndLZswoIW1eYtpAcVk= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= +github.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -208,17 +279,65 @@ github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A= +github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= +github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18= +github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY= +github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/errors v0.0.0-20200330140219-3fe23663418f h1:MCOvExGLpaSIzLYB4iQXEHP4jYVU6vmzLNQPdMVrxnM= +github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g= +github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e h1:FdDd7bdI6cjq5vaoYlK1mfQYfF9sF2VZw8VEZMsl5t8= +github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY= +github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= +github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU= +github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= +github.com/juju/testing v0.0.0-20180402130637-44801989f0f7/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc= +github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= +github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647 h1:wQpkHVbIIpz1PCcLYku9KFWsJ7aEMQXHBBmLy3tRBTk= +github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= +github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1 h1:3y/lDs71xT7YIYtlfODytPNGEF4XVvUUZhFe3s5kkQQ= +github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI= +github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= +github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt/BxiIxjLt9IKGvS+0injMHbag= +github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= +github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.7 h1:hYW1gP94JUmAhBtJ+LNz5My+gBobDxPR1iVuKug26aA= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= +github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/masterzen/azure-sdk-for-go v3.2.0-beta.0.20161014135628-ee4f0065d00c+incompatible/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE= +github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= +github.com/masterzen/winrm v0.0.0-20161014151040-7a535cd943fc/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E= +github.com/masterzen/xmlpath v0.0.0-20140218185901-13f4951698ad/go.mod h1:A0zPC53iKKKcXYxr4ROjpQRQ5FgJXtelNdSmHHuq/tY= +github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -238,33 +357,72 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/ompluscator/dynamic-struct v1.3.0/go.mod h1:ADQ1+6Ox1D+ntuNwTHyl1NvpAqY2lBXPSPbcO4CJdeA= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rocketlaunchr/dataframe-go v0.0.0-20211025052708-a1030444159b h1:VHVU5r4JpQu1QmnRMrvn8bJ9WxqZpWm5cY+ylVz22/s= +github.com/rocketlaunchr/dataframe-go v0.0.0-20211025052708-a1030444159b/go.mod h1:uokiUsvKMBQyLXbfCAIwc9aw+1PBsy+0U7IKrpO1j60= +github.com/rocketlaunchr/dbq/v2 v2.5.0/go.mod h1:MckY8J697t+AGc0ENl968yDVnD5cP/FFOBSPPyJXY5A= +github.com/rocketlaunchr/mysql-go v1.1.3 h1:7wYwOWWSl2tP6D9AI3MKqVJdiI5YL3uDnHV40b5e6CE= +github.com/rocketlaunchr/mysql-go v1.1.3/go.mod h1:SD/1bpRrmcdnBYRJq8eCerqqS1nTR9Y9WdW+LPzDLAQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sandertv/go-formula/v2 v2.0.0-alpha.7/go.mod h1:Ag4V2fiOHWXct3SraXNN3dFzFtyu9vqBfrjfYWMGLhE= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= @@ -272,6 +430,8 @@ github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1 github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM= github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -279,14 +439,24 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tealeg/xlsx/v3 v3.0.0/go.mod h1:fSua0Owrk9yAMAFGZI7piq5UL2BcubuQuLNOEhr3X80= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= +github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE= +github.com/xitongsys/parquet-go v1.5.1/go.mod h1:xUxwM8ELydxh4edHGegYq1pA8NnMKDx0K/GyB0o2bww= +github.com/xitongsys/parquet-go v1.5.2 h1:t8kVBM+7jPIbM+9ptrpZajWV1lOyHHVIQkTRUTlbK84= +github.com/xitongsys/parquet-go v1.5.2/go.mod h1:90swTgY6VkNM4MkMDsNxq8h30m6Yj1Arv9UMEl5V5DM= +github.com/xitongsys/parquet-go-source v0.0.0-20190524061010-2b72cbee77d5/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA= +github.com/xitongsys/parquet-go-source v0.0.0-20200326031722-42b453e70c3b/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA= +github.com/xitongsys/parquet-go-source v0.0.0-20200509081216-8db33acb0acf/go.mod h1:EVm7J5W7X/BJsvlGnCaj81kYxgbNzssi/+LF16FoV2s= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -294,6 +464,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zserge/lorca v0.1.9/go.mod h1:bVmnIbIRlOcoV285KIRSe4bUABKi7R7384Ycuum6e4A= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -307,15 +478,26 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20200402160453-61705b562fc9/go.mod h1:BEpJH1kxLue/53k7XKPHBk7Qb3nvSlpB/rQWTi7bMdA= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= @@ -326,6 +508,9 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw= @@ -355,8 +540,10 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -384,6 +571,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -395,6 +583,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -406,6 +595,7 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -416,13 +606,17 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -464,6 +658,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -480,8 +675,11 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -515,6 +713,7 @@ golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200402223321-bcf690261a44/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -536,7 +735,12 @@ golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -639,20 +843,44 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700= honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -662,6 +890,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= +launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/widget/diagramwidget/.gitarchive/COMMIT_EDITMSG b/widget/diagramwidget/.gitarchive/COMMIT_EDITMSG new file mode 100644 index 00000000..0f7020e6 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/COMMIT_EDITMSG @@ -0,0 +1 @@ +Change terminology: graph to diagram diff --git a/widget/diagramwidget/.gitarchive/FETCH_HEAD b/widget/diagramwidget/.gitarchive/FETCH_HEAD new file mode 100644 index 00000000..0e2a37bd --- /dev/null +++ b/widget/diagramwidget/.gitarchive/FETCH_HEAD @@ -0,0 +1 @@ +583b47634a43f829bce77cb7b9203108ba165d5d branch 'master' of https://git.sr.ht/~charles/fynehax diff --git a/widget/diagramwidget/.gitarchive/HEAD b/widget/diagramwidget/.gitarchive/HEAD new file mode 100644 index 00000000..cb089cd8 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/widget/diagramwidget/.gitarchive/config b/widget/diagramwidget/.gitarchive/config new file mode 100644 index 00000000..92768d4f --- /dev/null +++ b/widget/diagramwidget/.gitarchive/config @@ -0,0 +1,12 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + ignorecase = true +[remote "origin"] + url = https://git.sr.ht/~charles/fynehax + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/widget/diagramwidget/.gitarchive/description b/widget/diagramwidget/.gitarchive/description new file mode 100644 index 00000000..498b267a --- /dev/null +++ b/widget/diagramwidget/.gitarchive/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/widget/diagramwidget/.gitarchive/hooks/applypatch-msg.sample b/widget/diagramwidget/.gitarchive/hooks/applypatch-msg.sample new file mode 100644 index 00000000..a5d7b84a --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/widget/diagramwidget/.gitarchive/hooks/commit-msg.sample b/widget/diagramwidget/.gitarchive/hooks/commit-msg.sample new file mode 100644 index 00000000..b58d1184 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/widget/diagramwidget/.gitarchive/hooks/fsmonitor-watchman.sample b/widget/diagramwidget/.gitarchive/hooks/fsmonitor-watchman.sample new file mode 100644 index 00000000..23e856f5 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/widget/diagramwidget/.gitarchive/hooks/post-update.sample b/widget/diagramwidget/.gitarchive/hooks/post-update.sample new file mode 100644 index 00000000..ec17ec19 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-applypatch.sample b/widget/diagramwidget/.gitarchive/hooks/pre-applypatch.sample new file mode 100644 index 00000000..4142082b --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-commit.sample b/widget/diagramwidget/.gitarchive/hooks/pre-commit.sample new file mode 100644 index 00000000..e144712c --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-merge-commit.sample b/widget/diagramwidget/.gitarchive/hooks/pre-merge-commit.sample new file mode 100644 index 00000000..399eab19 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-push.sample b/widget/diagramwidget/.gitarchive/hooks/pre-push.sample new file mode 100644 index 00000000..4ce688d3 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-rebase.sample b/widget/diagramwidget/.gitarchive/hooks/pre-rebase.sample new file mode 100644 index 00000000..6cbef5c3 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-receive.sample b/widget/diagramwidget/.gitarchive/hooks/pre-receive.sample new file mode 100644 index 00000000..a1fd29ec --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/widget/diagramwidget/.gitarchive/hooks/prepare-commit-msg.sample b/widget/diagramwidget/.gitarchive/hooks/prepare-commit-msg.sample new file mode 100644 index 00000000..10fa14c5 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/widget/diagramwidget/.gitarchive/hooks/push-to-checkout.sample b/widget/diagramwidget/.gitarchive/hooks/push-to-checkout.sample new file mode 100644 index 00000000..af5a0c00 --- /dev/null +++ b/widget/diagramwidget/.gitarchive/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/widget/diagramwidget/.gitarchive/index b/widget/diagramwidget/.gitarchive/index new file mode 100644 index 0000000000000000000000000000000000000000..06a3f74c984aaaaf2e31f45ab20a64a11eff2bd2 GIT binary patch literal 2242 zcmZ?q402{*U|<4bu@vqo8}p)&D=FNO+jx)Hc>u+rpm7O|#lXNg<788T`C#?UXQhM)QVqo|2bawR%b_Hqzn{!bMV$Rd|NNzzkhxxp+s8aC5sjrVlIz9Lx zwRd+Kx8=Mm44gr(jxN5gdbueeb8H2!)VP4$!^culfZaWU4ZAZeU7oW99+?vr;+&s; z^%8IG)no>-#G<17@{H8P6n!{LFFhY*2FP3?#~l!Jl`j6kZZ2mFhg#1?DeHN)Leq6l z_TJcTe6J*KCWB~lZi;?-QDQ+xN@{Mter{rB9@JQ{xxz*27r^c{Vh_P?F57h7GdCAZ zb-vc!>ALP^SG&fmiq$ncA?B7OCgr4JHCKeqN&sxGqr)HU<}&7&rg)s*r?przv&D)h z==@qU@5ky23=$ysBFrsL%q_@C)k`iegM@>q*$fV_xdCN+u$#+yJ=HtUyn=zxNW;E< z)}!p_njCD-J0RwkWu}%F;KXz-}(r+kBzlrbjH5j!E9? z^eDS_a6-(Izda26DVd4D;LX*~$;``!ge=HBG5gdSush?|;YbGz;ng?xt96CMW!jeO z%SdKOuPKgv^q4^;JvBc!wWO$0AI5-17dXDeJ(n&4n_H)N0J}T+_HMi#RQ~AfwoM)O z@xktXx7BiO0?#uD!Hm-{GSW}VuYj8h30LulcP+r?_Qv8cmw_ikI4j*@<`1iHjjO{A zPKca5{ydN?&+%OcT}=0U3yHQ@X)cnvOAtI z2*b<*xfd9=sR##y!dG&7u>{!M)yH07HG^uO`6(bx zK=;{LEXah!)6HBPgh9yV$ttU<6OY;Hi8`e(OMs!5@glqz)OHdi2pO#;goSKtZnO|B04>*vyGI__& zfXx;2*THTsr=I?+sNTJgSF8!XAfhV$eEOC0T3HhYK8U$s5^5sIJXz<1C1CSZn`5w> zCuZ+-JE6@W%_zB3JnzkH_QX>v@prRe{?5x!LGia-Ne9H=g|l(wN0H`ikrVglo=;l* z_VEjQ-lpy_qH{YcJyZiXhmHqy^=#kccK6kcbNf$Ls zj{N`sSDrxxRtD+A81Qfm333HiM~qbrMn($eT;dLKaqk{V8w>Ax6BTdJoYAmn+iqal z$Y7#i%(XCCd!m?C-Hqe|$;aP*q+9g}+WC zxn|b?`4!V$?r{|vfo;wWJkT%yV0Hgd=KY@S!t19!vRrl;=6JB-A~rV-B9ou;M1& literal 0 HcmV?d00001 diff --git a/widget/diagramwidget/.gitarchive/info/exclude b/widget/diagramwidget/.gitarchive/info/exclude new file mode 100644 index 00000000..a5196d1b --- /dev/null +++ b/widget/diagramwidget/.gitarchive/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/widget/diagramwidget/.gitarchive/logs/HEAD b/widget/diagramwidget/.gitarchive/logs/HEAD new file mode 100644 index 00000000..bd12ddeb --- /dev/null +++ b/widget/diagramwidget/.gitarchive/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 583b47634a43f829bce77cb7b9203108ba165d5d pbrown12303 1678465462 -0500 clone: from https://git.sr.ht/~charles/fynehax +583b47634a43f829bce77cb7b9203108ba165d5d ca7d6114760e08cee5a2e622b98c97213732c16c pbrown12303 1678546829 -0500 commit: Change terminology: graph to diagram diff --git a/widget/diagramwidget/.gitarchive/logs/refs/heads/master b/widget/diagramwidget/.gitarchive/logs/refs/heads/master new file mode 100644 index 00000000..bd12ddeb --- /dev/null +++ b/widget/diagramwidget/.gitarchive/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 583b47634a43f829bce77cb7b9203108ba165d5d pbrown12303 1678465462 -0500 clone: from https://git.sr.ht/~charles/fynehax +583b47634a43f829bce77cb7b9203108ba165d5d ca7d6114760e08cee5a2e622b98c97213732c16c pbrown12303 1678546829 -0500 commit: Change terminology: graph to diagram diff --git a/widget/diagramwidget/.gitarchive/logs/refs/remotes/origin/HEAD b/widget/diagramwidget/.gitarchive/logs/refs/remotes/origin/HEAD new file mode 100644 index 00000000..f986a32f --- /dev/null +++ b/widget/diagramwidget/.gitarchive/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 583b47634a43f829bce77cb7b9203108ba165d5d pbrown12303 1678465462 -0500 clone: from https://git.sr.ht/~charles/fynehax diff --git a/widget/diagramwidget/.gitarchive/objects/17/405e5eeee11b3313baec5a5f30836880bcb6bb b/widget/diagramwidget/.gitarchive/objects/17/405e5eeee11b3313baec5a5f30836880bcb6bb new file mode 100644 index 0000000000000000000000000000000000000000..64609ade5a1bf80027a511dd65b87d0d9fc9c1ee GIT binary patch literal 391 zcmV;20eJp+0V^p=O;s>4G-5C`FfcPQQP4}zEXhpI%P&f0_}XAre4JZW%lB>Mq0o%o zC2iG}9Z*$1p3bg*!LAH7>)x{kE>HQ$bW>se6V`uGDZTeDL6ro#I=c9}>gA>|D2plu zKb-pdXr$AF4^n$~r*T`(yJBJh1PX~oMfv3!sfj5Jvt2yGzGTN&&pq1Gz&bn1?QXOD zTZo!upa~0;wI_;M)!j%gkbL~@N4oW2qg9y@*_6!0^rFOEh6zkHJa??l`zyC(`4w)G z;!aCkyc(h+JvBc!wWO$$!SSkk;OgcU{yL51npywnS4?-g#|3q3dcIz6ehR~@cBK>3 zUq4&V-4JAW#jt<(+qg$B5GsmGa~Y;iJZ7gS>Xg>t<~66*OZ9!n{OxT}Yl;#JG8p#G zu((w=iMe&no4OTqgrc4Qh)y^IkuOP1%1LFoy>>+i*Z!B+SIu~9eO4_)Ytp&8yAb)Z l%+&IN{Gt*D5u2L^k;%__pBcEX{OatfBZ~q> z$99YCxA%?|8O7;hVEGc=@pv!KJ-qX@Qd7uY|N8o;M$Yc!0wFKuLd#_wm&-={RZc11NKJ)5RdOtiGlMxym8v+``Kq2lqT#6P zG{^M7r0}7vugkBPq+mP1-we|x$I3_;pMVw&nQ$41&#Em=sp=#Z6L{qhT_#TsmH;X=rk&KtoH|a zgDrJ4BR<%e-197T(3*|dItpj&jxssFSjhv=HqlIW$VT$Xo+gD zYEDm2zY-tAzQ+n_>)r{Kk1I(050xkGQ5Vm>{oLxAN>Hup}FU{l~u|#0PnYXMWWt zHis}tjt6l3FxVL{R_Uima;Q`BNYqgIq5ki}{pbXp+1=mcCuukz&ir?+mX~$jC>mrf zl@TWE1@fd!(gC0d6V)pp-wFZnhn7uoNf0HAfW5=Y$OWVedx= zBi@MwG%OhPfG$ywmyEc5bxY XV~A&uzj6|!-(Q|+NN9ft=WS7rSbGwr literal 0 HcmV?d00001 diff --git a/widget/diagramwidget/.gitarchive/objects/90/027c0cdc3acf4f23846a4e71b21a0b6661a3ab b/widget/diagramwidget/.gitarchive/objects/90/027c0cdc3acf4f23846a4e71b21a0b6661a3ab new file mode 100644 index 0000000000000000000000000000000000000000..416f1183f8ddee8fab30909444d8acc8adf29483 GIT binary patch literal 52 zcmV-40L%Y)0V^p=O;s>9WiT`_Ff%bx$jQvh)=ST4$QSx;dc;!cnB=WakFskAC&VoI K+XDbA`VhJ<(HBDi literal 0 HcmV?d00001 diff --git a/widget/diagramwidget/.gitarchive/objects/ca/7d6114760e08cee5a2e622b98c97213732c16c b/widget/diagramwidget/.gitarchive/objects/ca/7d6114760e08cee5a2e622b98c97213732c16c new file mode 100644 index 00000000..333ca1ab --- /dev/null +++ b/widget/diagramwidget/.gitarchive/objects/ca/7d6114760e08cee5a2e622b98c97213732c16c @@ -0,0 +1,2 @@ +xŽAjÅ0 D»ö)t9²lÿRJ¡'‘ý$ðãRzûzÙug530)­ÖcÀÂü4ºømÊ{%ò¤b……ï„™bΨE£ª»¤Û9€3iH‘‚ºçå¦ÅR*šô¶ yÌ*>òÊ«“¯±·—öö}ú…àíOøت—Òê;ø˜2‡8iðŒŒèf;OûçÜ}îrnP³=Úöó +[—k‡Ñ`=dúê~°éOr \ No newline at end of file diff --git a/widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.idx b/widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.idx new file mode 100644 index 0000000000000000000000000000000000000000..4252462206a5d970ea2d6a160553ecf16e7f3652 GIT binary patch literal 5664 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 literal 0 HcmV?d00001 diff --git a/widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.pack b/widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.pack new file mode 100644 index 0000000000000000000000000000000000000000..22f58c7b278edf8b55160558003b3741b22fcc47 GIT binary patch literal 47997 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

O)$P_$Wqwf#JWj`y~M3D4OB1 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 1.2*targetLength { + return r2.V2(0, 0) + } + // non-adjacent nodes repel, at a rate falling of with distance. + return v.Scale(50 * math.Sqrt(1/(d+0.1))) + // return r2.V2(0, 0*math.Sqrt(1)) + } +} + +// StepForceLayout calculates one step of force directed graph layout, with +// the target distance between adjacent nodes being targetLength. +func (g *DiagramWidget) StepForceLayout(targetLength float64) { + deltas := make(map[string]r2.Vec2) + + // calculate all the deltas from the current state + for k, nk := range g.Nodes { + deltas[k] = r2.V2(0, 0) + + for j, nj := range g.Nodes { + if j == k { + continue + } + deltas[k] = deltas[k].Add(g.calculateForce(nk, nj, targetLength)) + } + } + + // flip into current state + for k, nk := range g.Nodes { + nk.Displace(fyne.Position{X: float32(deltas[k].X), Y: float32(deltas[k].Y)}) + } + +} diff --git a/widget/diagramwidget/geometry/geometry.go b/widget/diagramwidget/geometry/geometry.go new file mode 100644 index 00000000..7bd8bf26 --- /dev/null +++ b/widget/diagramwidget/geometry/geometry.go @@ -0,0 +1,2 @@ +// Package geometry implements various useful geometric primitives. +package geometry diff --git a/widget/diagramwidget/geometry/r2/box.go b/widget/diagramwidget/geometry/r2/box.go new file mode 100644 index 00000000..b1db5277 --- /dev/null +++ b/widget/diagramwidget/geometry/r2/box.go @@ -0,0 +1,175 @@ +package r2 + +// Box defines a box in R2 +// +// A +// | +// | +// | +// v +// (1) A.X,A.Y +------+ A.X+S.X,A.Y (2) +// |\ | +// | \ | +// | \ | +// | \S | +// | \ | +// | \| +// (3)A.X,A.Y+S.Y +------+ A.X+S.X,A.Y+S.Y (4) +// +type Box struct { + + // A defines the top-left corner of the box + A Vec2 + + // S defines the size of the box + S Vec2 +} + +func MakeBox(a, s Vec2) Box { + return Box{ + A: a, + S: s, + } +} + +func (b Box) Area() float64 { + return b.S.X * b.S.Y +} + +// GetCorner1 returns the top left corner of the box +func (b Box) GetCorner1() Vec2 { + return b.A +} + +// GetCorner2 returns the top right corner of the box +func (b Box) GetCorner2() Vec2 { + return b.A.Add(V2(b.S.X, 0)) +} + +// GetCorner3 returns the bottom left corner of the box. +func (b Box) GetCorner3() Vec2 { + return b.A.Add(V2(0, b.S.Y)) +} + +// GetCorner4 returns the bottom right corner of the box. +func (b Box) GetCorner4() Vec2 { + return b.A.Add(V2(b.S.X, b.S.Y)) +} + +// Returns the intersection of the box and the line, and a Boolean indicating +// if the box and vector intersect. If they do not collide, the zero vector is +// returned. +func (b Box) Intersect(l Line) (Vec2, bool) { + // This is transliterated in part from: + // + // https://github.com/JulNadeauCA/libagar/blob/master/gui/primitive.c + + faces := []Line{ + b.Top(), + b.Left(), + b.Right(), + b.Bottom(), + } + + dists := []float64{-1, -1, -1, -1} + intersects := []bool{false, false, false, false} + intersectPoints := make([]Vec2, 4) + + shortest_dist := float64(-1.0) + best := -1 + + for i := range faces { + in, ok := IntersectLines(faces[i], l) + if !ok { + continue + } + dists[i] = in.Length() + intersects[i] = ok + intersectPoints[i] = in + + if (dists[i] < shortest_dist) || (shortest_dist == float64(-1)) { + shortest_dist = dists[i] + best = i + } + } + + if shortest_dist < 0 { + return V2(0, 0), false + } + + return intersectPoints[best], true +} + +// Top returns the top face of the box. +func (b Box) Top() Line { + return MakeLineFromEndpoints(b.GetCorner1(), b.GetCorner2()) +} + +// Left returns the left face of the box. +func (b Box) Left() Line { + return MakeLineFromEndpoints(b.GetCorner1(), b.GetCorner3()) +} + +// Right returns the right face of the box. +func (b Box) Right() Line { + return MakeLineFromEndpoints(b.GetCorner2(), b.GetCorner4()) +} + +// Bottom returns the bottom face of the box. +func (b Box) Bottom() Line { + return MakeLineFromEndpoints(b.GetCorner3(), b.GetCorner4()) +} + +func (b Box) Center() Vec2 { + return b.A.Add(b.S.Scale(0.5)) +} + +// Contains returns true if the point v is within the box b. +func (b Box) Contains(v Vec2) bool { + if (v.X < b.GetCorner1().X) && (v.X > b.GetCorner2().X) { + return false + } + + if (v.Y < b.GetCorner1().Y) && (v.Y > b.GetCorner3().Y) { + return false + } + + return true +} + +// BoundingBox creates a minimum axis-aligned bounding box for the given list +// of points. +func BoundingBox(points []Vec2) Box { + if len(points) < 2 { + return MakeBox(V2(0, 0), V2(0, 0)) + } + + topleft := points[0] + bottomright := points[1] + points = points[1 : len(points)-1] + + for _, p := range points { + if p.X < topleft.X { + topleft.X = p.X + } + if p.Y < topleft.Y { + topleft.Y = p.Y + } + if p.X > bottomright.X { + bottomright.X = p.X + } + if p.Y > bottomright.Y { + bottomright.Y = p.Y + } + } + + return MakeBox(topleft, bottomright) +} + +func (b Box) Width() float64 { + return b.Top().Length() +} + +func (b Box) Height() float64 { + return b.Left().Length() +} diff --git a/widget/diagramwidget/geometry/r2/geometry.go b/widget/diagramwidget/geometry/r2/geometry.go new file mode 100644 index 00000000..68136a67 --- /dev/null +++ b/widget/diagramwidget/geometry/r2/geometry.go @@ -0,0 +1 @@ +package r2 diff --git a/widget/diagramwidget/geometry/r2/line.go b/widget/diagramwidget/geometry/r2/line.go new file mode 100644 index 00000000..aa4cde2d --- /dev/null +++ b/widget/diagramwidget/geometry/r2/line.go @@ -0,0 +1,129 @@ +package r2 + +// Line describes a line in R2 +// +// (1) A.X,A.Y + +// \ +// \ +// \ +// \ +// + A.X+S.X,A.Y+S.Y (2) +// +type Line struct { + // A defines the basis point of the line + A Vec2 + + // S defines the direction and length of the line + S Vec2 +} + +func MakeLine(a, s Vec2) Line { + return Line{ + A: a, + S: s, + } +} + +// Return a line which has endpoints a, b +func MakeLineFromEndpoints(a, b Vec2) Line { + s := b.Add(a.Scale(-1)) + + return MakeLine(a, s) +} + +func (l Line) Endpoint1() Vec2 { + return l.A +} + +func (l Line) Endpoint2() Vec2 { + return l.A.Add(l.S) +} + +func samesign(a, b float64) bool { + if (a < 0) && (b < 0) { + return true + } + + if (a > 0) && (b > 0) { + return true + } + + if a == b { + return true + } + + return false +} + +func (l Line) Length() float64 { + return l.S.Length() +} + +// This code is transliterated from here: +// +// https://github.com/JulNadeauCA/libagar/blob/master/gui/primitive.co +// +// Which is in turn based on Gem I.2 in Graphics Gems II by James Arvo. +func IntersectLines(l1, l2 Line) (Vec2, bool) { + x1 := l1.Endpoint1().X + y1 := l1.Endpoint1().Y + x2 := l1.Endpoint2().X + y2 := l1.Endpoint2().Y + x3 := l2.Endpoint1().X + y3 := l2.Endpoint1().Y + x4 := l2.Endpoint2().X + y4 := l2.Endpoint2().Y + + a1 := y2 - y1 + b1 := x1 - x2 + c1 := x2*y1 - x1*y2 + + r3 := a1*x3 + b1*y3 + c1 + r4 := a1*x4 + b1*y4 + c1 + + if (r3 != 0) && (r4 != 0) && samesign(r3, r4) { + return V2(0, 0), false + } + + a2 := y4 - y3 + b2 := x3 - x4 + c2 := x4*y3 - x3*y4 + + r1 := a2*x1 + b2*y1 + c2 + r2 := a2*x2 + b2*y2 + c2 + + if (r1 != 0) && (r2 != 0) && samesign(r1, r2) { + return V2(0, 0), false + } + + denom := a1*b2 - a2*b1 + if denom == 0 { + return V2(0, 0), false + } + + offset := 0.0 - denom/2.0 + if denom < 0 { + offset = denom / 2.0 + } + + num := b1*c2 - b2*c1 + xi := 0.0 + if num < 0 { + xi = num - offset + } else { + xi = num + offset + } + xi /= denom + + num = a2*c1 - a1*c2 + yi := 0.0 + if num < 0 { + yi = num - offset + } else { + yi = num + offset + } + yi /= denom + + return V2(xi, yi), true + +} diff --git a/widget/diagramwidget/geometry/r2/vec2.go b/widget/diagramwidget/geometry/r2/vec2.go new file mode 100644 index 00000000..bab0c3d3 --- /dev/null +++ b/widget/diagramwidget/geometry/r2/vec2.go @@ -0,0 +1,60 @@ +// Package r2 implements operations relating to objects in R2. +package r2 + +import ( + "math" +) + +// Vec2 implements a vector in R2 +type Vec2 struct { + // X magnitude of the vector + X float64 + + // Y magnitude of the vector + Y float64 +} + +// MakeVec2 creates a new vector inline +func MakeVec2(x, y float64) Vec2 { + return Vec2{X: x, Y: y} +} + +// V2 is a shortcut for MakeVec2 +func V2(x, y float64) Vec2 { + return MakeVec2(x, y) +} + +// Length return the vector length +func (v Vec2) Length() float64 { + return math.Sqrt(math.Pow(v.X, 2) + math.Pow(v.Y, 2)) +} + +// Dot returns the dot product of vector v and u +func (v Vec2) Dot(u Vec2) float64 { + return v.X*u.X + v.Y + u.Y +} + +// Add returns the sum of vector v and u +func (v Vec2) Add(u Vec2) Vec2 { + return Vec2{X: v.X + u.X, Y: v.Y + u.Y} +} + +// Scale returns the vector v scaled by the scalar s +func (v Vec2) Scale(s float64) Vec2 { + return Vec2{X: v.X * s, Y: v.Y * s} +} + +// Project returns the vector projection of v onto u +func (v Vec2) Project(u Vec2) Vec2 { + return u.Scale(u.Dot(v) / math.Pow(u.Length(), 2)) +} + +// Unit returns the vector scaled to length 1 +func (v Vec2) Unit() Vec2 { + return V2(v.X/v.Length(), v.Y/v.Length()) +} + +// ScaleToLength keeps the vector direction, but updates the length +func (v Vec2) ScaleToLength(l float64) Vec2 { + return v.Unit().Scale(l) +} diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go new file mode 100644 index 00000000..88589178 --- /dev/null +++ b/widget/diagramwidget/link.go @@ -0,0 +1,141 @@ +package diagramwidget + +import ( + "image/color" + + "fyne.io/x/fyne/widget/diagramwidget/arrowhead" + "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +type diagramLinkRenderer struct { + edge *DiagramLink + line *canvas.Line + arrow *arrowhead.Arrowhead +} + +type DiagramLink struct { + widget.BaseWidget + + Diagram *DiagramWidget + + LinkColor color.Color + + Width float32 + + Origin *DiagramNode + Target *DiagramNode + + Directed bool +} + +func (r *diagramLinkRenderer) MinSize() fyne.Size { + xdelta := r.edge.Origin.Position().X - r.edge.Target.Position().X + if xdelta < 0 { + xdelta *= -1 + } + + ydelta := r.edge.Origin.Position().Y - r.edge.Target.Position().Y + if ydelta < 0 { + ydelta *= -1 + } + + return fyne.Size{Width: xdelta, Height: ydelta} +} + +func (r *diagramLinkRenderer) Layout(size fyne.Size) { +} + +func (r *diagramLinkRenderer) ApplyTheme(size fyne.Size) { +} + +func (r *diagramLinkRenderer) Refresh() { + l := r.edge.R2Line() + b1 := r.edge.Origin.R2Box() + b2 := r.edge.Target.R2Box() + + p1, _ := b1.Intersect(l) + p2, _ := b2.Intersect(l) + + r.line.Position1 = fyne.Position{ + X: float32(p1.X), + Y: float32(p1.Y), + } + + r.line.Position2 = fyne.Position{ + X: float32(p2.X), + Y: float32(p2.Y), + } + + r.line.StrokeColor = r.edge.LinkColor + r.line.StrokeWidth = r.edge.Width + + if r.edge.Directed { + r.arrow.Show() + r.arrow.Tip = r.line.Position1 + r.arrow.Base = r.line.Position2 + r.arrow.StrokeColor = r.edge.LinkColor + r.arrow.StrokeWidth = r.edge.Width + } else { + r.arrow.Hide() + } + + canvas.Refresh(r.line) + canvas.Refresh(r.arrow) + r.arrow.Refresh() +} + +func (r *diagramLinkRenderer) BackgroundColor() color.Color { + return theme.BackgroundColor() +} + +func (r *diagramLinkRenderer) Destroy() { +} + +func (r *diagramLinkRenderer) Objects() []fyne.CanvasObject { + // obj := []fyne.CanvasObject{ + // r.line, + // // r.arrow, + // } + + // XXX: temporary hack because otherwise I can't get my canvas object + // to show up??? + obj := r.arrow.Objects() + obj = append(obj, r.line) + return obj +} + +func (e *DiagramLink) CreateRenderer() fyne.WidgetRenderer { + r := diagramLinkRenderer{ + edge: e, + line: canvas.NewLine(e.LinkColor), + arrow: arrowhead.MakeArrowhead(fyne.Position{X: 0, Y: 0}, fyne.Position{X: 0, Y: 0}), + } + + (&r).Refresh() + + return &r +} + +func (e *DiagramLink) R2Line() r2.Line { + return r2.MakeLineFromEndpoints(e.Origin.R2Center(), e.Target.R2Center()) +} + +func NewDiagramEdge(g *DiagramWidget, v, u *DiagramNode) *DiagramLink { + e := &DiagramLink{ + Diagram: g, + LinkColor: theme.TextColor(), + Width: 2, + Origin: v, + Target: u, + Directed: false, + } + + e.ExtendBaseWidget(e) + + return e +} diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go new file mode 100644 index 00000000..bf7fb8ff --- /dev/null +++ b/widget/diagramwidget/node.go @@ -0,0 +1,238 @@ +package diagramwidget + +import ( + "image/color" + + "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +const ( + // default inner size + defaultWidth float32 = 50 + defaultHeight float32 = 50 + + // default padding around the inner object in a node + defaultPadding float32 = 10 +) + +type diagramNodeRenderer struct { + node *DiagramNode + handle *canvas.Line + box *canvas.Rectangle +} + +// DiagramNode represents a node in the diagram widget. It contains an inner +// widget, and also draws a border, and a "handle" that can be used to drag it +// around. +type DiagramNode struct { + widget.BaseWidget + + Diagram *DiagramWidget + + // InnerSize stores size that the inner object should have, may not + // be respected if not large enough for the object. + InnerSize fyne.Size + + // InnerObject is the canvas object that should be drawn inside of + // the diagram node. + InnerObject fyne.CanvasObject + + // Padding is the distance between the inner object's drawing area + // and the box. + Padding float32 + + // BoxStrokeWidth is the stroke width of the box which delineates the + // node. Defaults to 1. + BoxStrokeWidth float32 + + // BoxFill is the fill color of the node, the inner object will be + // drawn on top of this. Defaults to the theme.BackgroundColor(). + BoxFillColor color.Color + + // BoxStrokeColor is the stroke color of the node rectangle. Defaults + // to theme.TextColor(). + BoxStrokeColor color.Color + + // HandleColor is the color of node handle. + HandleColor color.Color + + // HandleStrokeWidth is the stroke width of the node handle, defaults + // to 3. + HandleStroke float32 +} + +func (r *diagramNodeRenderer) MinSize() fyne.Size { + // space for the inner widget, plus padding on all sides. + inner := r.node.effectiveInnerSize() + return fyne.Size{ + Width: inner.Width + float32(2*r.node.Padding), + Height: inner.Height + float32(2*r.node.Padding), + } +} + +func (r *diagramNodeRenderer) Layout(size fyne.Size) { + r.node.Resize(r.MinSize()) + + r.node.InnerObject.Move(r.node.innerPos()) + r.node.InnerObject.Resize(r.node.effectiveInnerSize()) + + r.box.Resize(r.MinSize()) + + canvas.Refresh(r.node.InnerObject) +} + +func (r *diagramNodeRenderer) ApplyTheme(size fyne.Size) { +} + +func (r *diagramNodeRenderer) Refresh() { + // move and size the inner object appropriately + r.node.InnerObject.Move(r.node.innerPos()) + r.node.InnerObject.Resize(r.node.effectiveInnerSize()) + + // move the box and update it's colors + r.box.StrokeWidth = r.node.BoxStrokeWidth + r.box.FillColor = r.node.BoxFillColor + r.box.StrokeColor = r.node.BoxStrokeColor + r.box.Resize(r.MinSize()) + + // calculate the handle positions + r.handle.Position1 = fyne.Position{ + X: float32(r.node.Padding), + Y: float32(r.node.Padding / 2), + } + + r.handle.Position2 = fyne.Position{ + X: r.node.effectiveInnerSize().Width + float32(r.node.Padding), + Y: float32(r.node.Padding / 2), + } + + r.handle.StrokeWidth = r.node.HandleStroke + r.handle.StrokeColor = r.node.HandleColor + + for _, e := range r.node.Diagram.GetEdges(r.node) { + e.Refresh() + } + + canvas.Refresh(r.box) + canvas.Refresh(r.handle) + canvas.Refresh(r.node.InnerObject) +} + +func (r *diagramNodeRenderer) BackgroundColor() color.Color { + return theme.BackgroundColor() +} + +func (r *diagramNodeRenderer) Destroy() { +} + +func (r *diagramNodeRenderer) Objects() []fyne.CanvasObject { + return []fyne.CanvasObject{ + r.box, + r.handle, + r.node.InnerObject, + } +} + +func (n *DiagramNode) CreateRenderer() fyne.WidgetRenderer { + r := diagramNodeRenderer{ + node: n, + handle: canvas.NewLine(n.HandleColor), + box: canvas.NewRectangle(n.BoxStrokeColor), + } + + r.handle.StrokeWidth = n.HandleStroke + r.box.StrokeWidth = n.BoxStrokeWidth + r.box.FillColor = n.BoxFillColor + + (&r).Refresh() + + return &r +} + +func NewDiagramNode(d *DiagramWidget, obj fyne.CanvasObject) *DiagramNode { + w := &DiagramNode{ + Diagram: d, + InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, + InnerObject: obj, + Padding: defaultPadding, + BoxStrokeWidth: 1, + BoxFillColor: theme.BackgroundColor(), + BoxStrokeColor: theme.TextColor(), + HandleColor: theme.TextColor(), + HandleStroke: 3, + } + + w.ExtendBaseWidget(w) + + return w +} + +func (n *DiagramNode) innerPos() fyne.Position { + return fyne.Position{ + X: float32(n.Padding), + Y: float32(n.Padding), + } +} + +func (n *DiagramNode) effectiveInnerSize() fyne.Size { + return n.InnerSize.Max(n.InnerObject.MinSize()) +} + +func (n *DiagramNode) Cursor() desktop.Cursor { + return desktop.DefaultCursor +} + +func (n *DiagramNode) DragEnd() { + n.Refresh() +} + +func (n *DiagramNode) Dragged(event *fyne.DragEvent) { + delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} + n.Displace(delta) + n.Refresh() +} + +func (n *DiagramNode) MouseIn(event *desktop.MouseEvent) { + n.HandleColor = theme.FocusColor() + n.Refresh() +} + +func (n *DiagramNode) MouseOut() { + n.HandleColor = theme.TextColor() + n.Refresh() +} + +func (n *DiagramNode) MouseMoved(event *desktop.MouseEvent) { +} + +func (n *DiagramNode) Displace(delta fyne.Position) { + n.Move(n.Position().Add(delta)) +} + +func (n *DiagramNode) R2Position() r2.Vec2 { + return r2.V2(float64(n.Position().X), float64(n.Position().Y)) +} + +func (n *DiagramNode) R2Box() r2.Box { + inner := n.effectiveInnerSize() + s := r2.V2( + float64(inner.Width+float32(2*n.Padding)), + float64(inner.Height+float32(2*n.Padding)), + ) + + return r2.MakeBox(n.R2Position(), s) +} + +func (n *DiagramNode) R2Center() r2.Vec2 { + return n.R2Box().Center() +} + +func (n *DiagramNode) Center() fyne.Position { + return fyne.Position{X: float32(n.R2Center().X), Y: float32(n.R2Center().Y)} +} diff --git a/widget/diagramwidget/table/table.go b/widget/diagramwidget/table/table.go new file mode 100644 index 00000000..29ce49cb --- /dev/null +++ b/widget/diagramwidget/table/table.go @@ -0,0 +1,196 @@ +// package table implements a simple table widget. +// +// At present Fyne does not have it's own native table widget. This one intends +// to bridge the gap until a better one is available. +// +// Current limitations: +// - Not very pretty +// - No re-sizing +// - No editing cell contents +// - Very inefficient (re-generates all table cells each time the widget is +// refreshed) +// - No sorting +package table + +import ( + "fmt" + "image/color" + + "github.com/rocketlaunchr/dataframe-go" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +// TODO: should calculate row height based on font size. +const textHeight int = 20 + +const maxWidth int = 300 + +type tableRenderer struct { + table *TableWidget + objects []fyne.CanvasObject +} + +func (t *tableRenderer) MinSize() fyne.Size { + width := theme.Padding() * float32(2+len(t.table.df.Series)) + + t.table.updateColumnWidthsIfNeeded() + + for _, v := range t.table.columnWidths { + width += float32(v) + } + + return fyne.Size{Width: width, Height: float32(1+t.table.df.NRows()) * (float32(textHeight) + theme.Padding())} + +} + +func (t *tableRenderer) Layout(size fyne.Size) { + // TODO: in the future, it would be better for the table to report a + // minimum size smaller than the column widths would suggest, then + // shrink or grow the columns automatically when Layout is called. +} + +func (t *tableRenderer) ApplyTheme() { +} + +func (t *tableRenderer) BackgroundColor() color.Color { + return theme.BackgroundColor() +} + +func (t *tableRenderer) Refresh() { + // TODO: this is not efficient since it destroys and re-creates all the + // table cells. + + t.table.updateColumnWidthsIfNeeded() + + t.objects = make([]fyne.CanvasObject, 0) + + xpos := theme.Padding() + for col, v := range t.table.df.Names() { + cell := canvas.NewText(fmt.Sprintf("%v", v), theme.ForegroundColor()) + cell.Move(fyne.Position{X: xpos, Y: theme.Padding()}) + // TODO confirm the default values for the last two parameters (new) + cell.TextStyle = fyne.TextStyle{Bold: true, Italic: false, Monospace: false, Symbol: false, TabWidth: 10} + t.objects = append(t.objects, cell) + xpos += theme.Padding() + float32(t.table.columnWidths[col]) + } + + iterator := t.table.df.ValuesIterator(dataframe.ValuesOptions{InitialRow: 0, Step: 1, DontReadLock: true}) // Don't apply read lock because we are write locking from outside. + t.table.df.Lock() + for { + row, vals, _ := iterator() + if row == nil { + break + } + fmt.Println(*row, vals) + + xpos := theme.Padding() + ypos := theme.Padding() + (theme.Padding()+float32(textHeight))*float32(*row+1) + + for col := 0; col < len(t.table.df.Series); col++ { + v := vals[col] + + cell := canvas.NewText(fmt.Sprintf("%v", v), theme.ForegroundColor()) + cell.Move(fyne.Position{X: xpos, Y: ypos}) + + t.objects = append(t.objects, cell) + + xpos += theme.Padding() + float32(t.table.columnWidths[col]) + } + } + t.table.df.Unlock() + +} + +func (t *tableRenderer) Destroy() { +} + +func (t *tableRenderer) Objects() []fyne.CanvasObject { + return t.objects +} + +type TableWidget struct { + widget.BaseWidget + df *dataframe.DataFrame + columnWidths []int +} + +// updateColumnWidthsIfNeeded will guarantee that t.columnWidths is non-nil, +// and contains the same number of column widths as there are columns in t.df. +func (table *TableWidget) updateColumnWidthsIfNeeded() { + if (table.columnWidths == nil) || (len(table.columnWidths) != len(table.df.Series)) { + table.CalculateColumnWidths(maxWidth) + } +} + +// CalculateColumnWidths will replace t.columnWidths with appropriate widths +// that accommodate the full string ivied content of the largest element +// in a column. maxWidth is the widest that any single column can be. Specify +// a maxWidth of 0 for an unlimited maximum width. +func (table *TableWidget) CalculateColumnWidths(maxWidth int) { + table.columnWidths = make([]int, len(table.df.Series)) + + for col, v := range table.df.Names() { + strwidth := fyne.MeasureText(v, theme.TextSize(), fyne.TextStyle{Bold: true, Italic: false, Monospace: false, Symbol: false, TabWidth: 10}).Width + if strwidth > float32(maxWidth) { + strwidth = float32(maxWidth) + } + if table.columnWidths[col] < int(strwidth) { + table.columnWidths[col] = int(strwidth) + } + } + + iterator := table.df.ValuesIterator(dataframe.ValuesOptions{InitialRow: 0, Step: 1, DontReadLock: true}) // Don't apply read lock because we are write locking from outside. + table.df.Lock() + for { + row, vals, _ := iterator() + if row == nil { + break + } + + for col := 0; col < len(table.df.Series); col++ { + s := fmt.Sprintf("%v", vals[col]) + strwidth := fyne.MeasureText(s, theme.TextSize(), fyne.TextStyle{Bold: false, Italic: false, Monospace: false, Symbol: false, TabWidth: 10}).Width + if strwidth > float32(maxWidth) { + strwidth = float32(maxWidth) + } + if table.columnWidths[col] < int(strwidth) { + table.columnWidths[col] = int(strwidth) + } + } + } + table.df.Unlock() + +} + +func (table *TableWidget) Tapped(ev *fyne.PointEvent) { +} + +func (table *TableWidget) TappedSecondary(ev *fyne.PointEvent) { +} + +func (table *TableWidget) CreateRenderer() fyne.WidgetRenderer { + r := tableRenderer{ + table: table, + } + + r.Refresh() + + return &r +} + +func NewTableWidget(df *dataframe.DataFrame) *TableWidget { + table := &TableWidget{df: df} + table.CalculateColumnWidths(maxWidth) + table.ExtendBaseWidget(table) + return table +} + +func (table *TableWidget) ReplaceDataFrame(newdf *dataframe.DataFrame) { + table.df = newdf + table.CalculateColumnWidths(maxWidth) + table.Refresh() +} diff --git a/widget/diagramwidget/viewport/viewport.go b/widget/diagramwidget/viewport/viewport.go new file mode 100644 index 00000000..bd91c8e1 --- /dev/null +++ b/widget/diagramwidget/viewport/viewport.go @@ -0,0 +1,173 @@ +package viewport + +import ( + "fmt" + "image/color" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +type viewportRenderer struct { + viewport *ViewportWidget + statusText *canvas.Text +} + +func (r *viewportRenderer) MinSize() fyne.Size { + return fyne.Size{Width: float32(r.viewport.Width), Height: float32(r.viewport.Height)} +} + +func (r *viewportRenderer) Layout(size fyne.Size) { + r.Refresh() +} + +func (r *viewportRenderer) ApplyTheme(size fyne.Size) { +} + +func (r *viewportRenderer) Refresh() { + r.statusText.Move(fyne.Position{X: 0, Y: 0}) + r.statusText.Text = fmt.Sprintf("x=%f y=%f zoom=%f", r.viewport.XOffset, r.viewport.YOffset, r.viewport.Zoom) + + for _, viewportObj := range r.viewport.Objects { + viewportObj.Refresh(r.viewport) + } + + // XXX: I think this might be causing Fyne to refresh the whole canvas, + // since without this the ViewPort widgets don't seem to update + // themselves ??? Might need Refresh() to also call some kind of + // Refresh() function of the ViewportObjects. + r.statusText.Refresh() +} + +func (r *viewportRenderer) BackgroundColor() color.Color { + return theme.BackgroundColor() +} + +func (r *viewportRenderer) Destroy() { +} + +func (r *viewportRenderer) Objects() []fyne.CanvasObject { + objects := make([]fyne.CanvasObject, 0) + for _, viewportObj := range r.viewport.Objects { + objects = append(objects, viewportObj.CanvasObjects(r.viewport)...) + } + objects = append(objects, r.statusText) + return objects +} + +type ViewportWidget struct { + widget.BaseWidget + Width int + Height int + Zoom float64 + XOffset float64 + YOffset float64 + Objects []ViewportObject +} + +func (w *ViewportWidget) Tapped(ev *fyne.PointEvent) { +} + +func (w *ViewportWidget) TappedSecondary(ev *fyne.PointEvent) { +} + +func (w *ViewportWidget) CreateRenderer() fyne.WidgetRenderer { + r := viewportRenderer{ + viewport: w, + statusText: canvas.NewText("status", color.RGBA{255, 255, 255, 255}), + } + + r.Refresh() + return &r +} + +func NewViewportWidget(width, height int) *ViewportWidget { + vp := &ViewportWidget{ + Width: width, + Height: height, + Zoom: 1.0, + XOffset: 0, + YOffset: 0, + Objects: make([]ViewportObject, 0), + } + + vp.ExtendBaseWidget(vp) + return vp +} + +func (w *ViewportWidget) Cursor() desktop.Cursor { + return desktop.DefaultCursor +} + +func (w *ViewportWidget) DragEnd() { + w.Refresh() +} + +func (w *ViewportWidget) Dragged(event *fyne.DragEvent) { + w.XOffset += float64(event.Dragged.DX) / w.Zoom + w.YOffset += float64(event.Dragged.DY) / w.Zoom + w.Refresh() +} + +func (w *ViewportWidget) MouseIn(event *desktop.MouseEvent) { +} + +func (w *ViewportWidget) MouseOut() { +} + +func (w *ViewportWidget) MouseMoved(event *desktop.MouseEvent) { +} + +func (w *ViewportWidget) Scrolled(ev *fyne.ScrollEvent) { + if ev.Scrolled.DY > 0 { + w.Zoom *= 1.15 + } else { + w.Zoom *= 0.85 + } + w.Refresh() +} + +type ViewportObject interface { + CanvasObjects(viewport *ViewportWidget) []fyne.CanvasObject + Refresh(viewport *ViewportWidget) +} + +type ViewportLine struct { + obj *canvas.Line + X1 float64 + Y1 float64 + X2 float64 + Y2 float64 + StrokeColor color.Color + StrokeWidth float64 +} + +func setLineEndpoints(l *canvas.Line, X1, Y1, X2, Y2 float64) { + l.Move(fyne.NewPos(float32(X1), float32(Y1))) + l.Resize(fyne.NewSize(float32(X2)-float32(X1), float32(Y2)-float32(Y1))) +} + +func (l *ViewportLine) CanvasObjects(viewport *ViewportWidget) []fyne.CanvasObject { + if l.obj != nil { + return []fyne.CanvasObject{l.obj} + } + return []fyne.CanvasObject{} +} + +func (l *ViewportLine) Refresh(viewport *ViewportWidget) { + if l.obj == nil { + l.obj = canvas.NewLine(l.StrokeColor) + } + + setLineEndpoints(l.obj, + (l.X1+viewport.XOffset)*viewport.Zoom, + (l.Y1+viewport.YOffset)*viewport.Zoom, + (l.X2+viewport.XOffset)*viewport.Zoom, + (l.Y2+viewport.YOffset)*viewport.Zoom, + ) + l.obj.StrokeWidth = float32(l.StrokeWidth * viewport.Zoom) + l.obj.Hidden = false +} From 55d74454cf47c446508217e206d3b6de36c20fcd Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 15 Mar 2023 14:43:26 -0400 Subject: [PATCH 02/53] Fixed arrowhead direction --- AUTHORS | 1 + widget/diagramwidget/arrowhead/arrowhead.go | 40 +++++++++--- widget/diagramwidget/diagram.go | 16 +++-- widget/diagramwidget/geometry/r2/line.go | 13 ++-- widget/diagramwidget/geometry/r2/vec2.go | 30 +++++++++ widget/diagramwidget/geometry/r2/vec2_test.go | 63 +++++++++++++++++++ widget/diagramwidget/node.go | 20 +++--- 7 files changed, 151 insertions(+), 32 deletions(-) create mode 100644 widget/diagramwidget/geometry/r2/vec2_test.go diff --git a/AUTHORS b/AUTHORS index e69de29b..a76b8966 100644 --- a/AUTHORS +++ b/AUTHORS @@ -0,0 +1 @@ +Diagram Widget: Charles Daniels and Paul C. Brown \ No newline at end of file diff --git a/widget/diagramwidget/arrowhead/arrowhead.go b/widget/diagramwidget/arrowhead/arrowhead.go index 0c81155a..fff618b9 100644 --- a/widget/diagramwidget/arrowhead/arrowhead.go +++ b/widget/diagramwidget/arrowhead/arrowhead.go @@ -13,7 +13,7 @@ import ( ) const ( - defaultTheta float32 = 10 + defaultTheta float32 = 0.5235 defaultStrokeWidth float32 = 2 defaultLength int = 15 ) @@ -45,7 +45,7 @@ type Arrowhead struct { StrokeColor color.Color // Theta is the angle between the two "tails" that intersect at the - // tip. + // tip. This angle is in radians. Theta float32 // Length is the length of the two "tails" that intersect at the tip. @@ -107,17 +107,37 @@ func (a *Arrowhead) Refresh() { } func (a *Arrowhead) LeftPoint() fyne.Position { - return a.Tip.Add(fyne.Position{ - X: float32(float64(a.Length) * math.Cos(float64(a.Theta))), - Y: float32(float64(a.Length) * math.Sin(float64(a.Theta))), - }) + // Have to change the sign of Y because the window coordinated Y axis goes down rather than up + baseVector := r2.Vec2{ + X: float64(a.Base.X - a.Tip.X), + Y: -float64(a.Base.Y - a.Tip.Y), + } + baseAngle := baseVector.Angle() + leftAngle := r2.AddAngles(baseAngle, -float64(a.Theta)) + // We have to change the sign of Y because the window coordinate Y axis goes down rather than up + leftPosition := fyne.Position{ + X: float32(float64(a.Length) * math.Cos(leftAngle)), + Y: -float32(float64(a.Length) * math.Sin(leftAngle)), + } + leftPoint := a.Tip.Add(leftPosition) + return leftPoint } func (a *Arrowhead) RightPoint() fyne.Position { - return a.Tip.Add(fyne.Position{ - X: float32(float64(a.Length) * math.Cos(float64(a.Theta))), - Y: float32(float64(a.Length) * -1.0 * math.Sin(float64(a.Theta))), - }) + // Have to change the sign of Y because the window coordinated Y axis goes down rather than up + baseVector := r2.Vec2{ + X: float64(a.Base.X - a.Tip.X), + Y: -float64(a.Base.Y - a.Tip.Y), + } + baseAngle := baseVector.Angle() + rightAngle := r2.AddAngles(baseAngle, float64(a.Theta)) + // We have to change the sign of Y because the window coordinate Y axis goes down rather than up + rightPosition := fyne.Position{ + X: float32(float64(a.Length) * math.Cos(rightAngle)), + Y: -float32(float64(a.Length) * math.Sin(rightAngle)), + } + rightPoint := a.Tip.Add(rightPosition) + return rightPoint } func (a *Arrowhead) Size() fyne.Size { diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 0278f83e..7b1c99ef 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -16,7 +16,11 @@ type diagramRenderer struct { type DiagramWidget struct { widget.BaseWidget - Offset fyne.Position + // Diagrams may want to use a different theme and variant than the application. The default value is the + // applicaton's theme and variant + DiagramTheme fyne.Theme + ThemeVariant fyne.ThemeVariant + Offset fyne.Position // DesiredSize specifies the size which the graph widget should take // up, defaults to 800 x 600 @@ -99,10 +103,12 @@ func (g *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { func NewDiagram() *DiagramWidget { d := &DiagramWidget{ - DesiredSize: fyne.Size{Width: 800, Height: 600}, - Offset: fyne.Position{X: 0, Y: 0}, - Nodes: map[string]*DiagramNode{}, - Links: map[string]*DiagramLink{}, + DiagramTheme: fyne.CurrentApp().Settings().Theme(), + ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), + DesiredSize: fyne.Size{Width: 800, Height: 600}, + Offset: fyne.Position{X: 0, Y: 0}, + Nodes: map[string]*DiagramNode{}, + Links: map[string]*DiagramLink{}, } d.ExtendBaseWidget(d) diff --git a/widget/diagramwidget/geometry/r2/line.go b/widget/diagramwidget/geometry/r2/line.go index aa4cde2d..6d0f3c72 100644 --- a/widget/diagramwidget/geometry/r2/line.go +++ b/widget/diagramwidget/geometry/r2/line.go @@ -2,13 +2,12 @@ package r2 // Line describes a line in R2 // -// (1) A.X,A.Y + -// \ -// \ -// \ -// \ -// + A.X+S.X,A.Y+S.Y (2) -// +// (1) A.X,A.Y + +// \ +// \ +// \ +// \ +// + A.X+S.X,A.Y+S.Y (2) type Line struct { // A defines the basis point of the line A Vec2 diff --git a/widget/diagramwidget/geometry/r2/vec2.go b/widget/diagramwidget/geometry/r2/vec2.go index bab0c3d3..909c1971 100644 --- a/widget/diagramwidget/geometry/r2/vec2.go +++ b/widget/diagramwidget/geometry/r2/vec2.go @@ -39,6 +39,20 @@ func (v Vec2) Add(u Vec2) Vec2 { return Vec2{X: v.X + u.X, Y: v.Y + u.Y} } +// Add angle adds two angles in radians. The inputs are assumed to be in the +// range of +Pi to -Pi radians. The range of the result is +Pi to -Pi radians +func AddAngles(a1 float64, a2 float64) float64 { + angleSum := a1 + a2 + if math.Abs(angleSum) > math.Pi { + if angleSum > 0 { + angleSum = angleSum - 2*math.Pi + } else { + angleSum = angleSum + 2*math.Pi + } + } + return angleSum +} + // Scale returns the vector v scaled by the scalar s func (v Vec2) Scale(s float64) Vec2 { return Vec2{X: v.X * s, Y: v.Y * s} @@ -58,3 +72,19 @@ func (v Vec2) Unit() Vec2 { func (v Vec2) ScaleToLength(l float64) Vec2 { return v.Unit().Scale(l) } + +// Angle computes the angle of the vector respect to the origin. The result is in radians. +func (v Vec2) Angle() float64 { + length := v.Length() + yLength := v.Y + baseAngle := math.Asin(yLength / length) + // The base angle has range pi/2 to -pi/2. We must adjust if S.X is negative + if v.X < 0 { + if v.Y > 0 { + baseAngle = math.Pi - baseAngle + } else { + baseAngle = -math.Pi - baseAngle + } + } + return baseAngle +} diff --git a/widget/diagramwidget/geometry/r2/vec2_test.go b/widget/diagramwidget/geometry/r2/vec2_test.go new file mode 100644 index 00000000..836730d9 --- /dev/null +++ b/widget/diagramwidget/geometry/r2/vec2_test.go @@ -0,0 +1,63 @@ +package r2 + +import ( + "math" + "testing" +) + +func TestVec2(t *testing.T) { + // Test the Angle() function + v1 := V2(1, 0) + if v1.Angle() != 0 { + t.Errorf("Angle of {1,0} failed. Expected 0, got %f", v1.Angle()) + } + v1 = V2(1, 0.5) + tolerance := 0.000001 + if math.Abs(v1.Angle()-0.463647) > tolerance { + t.Errorf("Angle of {1,0.5} failed. Expected 0.463647, got %f", v1.Angle()) + } + v1 = V2(-1, 0.5) + if math.Abs(v1.Angle()-2.677945) > tolerance { + t.Errorf("Angle of {-1,0.5} failed. Expected 2.677945, got %f", v1.Angle()) + } + v1 = V2(-1, -0.5) + if math.Abs(v1.Angle()+2.677945) > tolerance { + t.Errorf("Angle of {1,0.5} failed. Expected -2.677945, got %f", v1.Angle()) + } + v1 = V2(1, -0.5) + if math.Abs(v1.Angle()+0.463647) > tolerance { + t.Errorf("Angle of {1,0.5} failed. Expected -0.463647, got %f", v1.Angle()) + } +} + +func TestAngleSum(t *testing.T) { + tolerance := 0.000001 + if math.Abs(AddAngles(0.0, 0.0)) > tolerance { + t.Error("AngleSum 0 failed") + } + if math.Abs(AddAngles(0.0, math.Pi)-math.Pi) > tolerance { + t.Error("AngleSum 0, Pi failed") + } + if math.Abs(AddAngles(math.Pi, math.Pi)) > tolerance { + t.Error("AngleSum Pi, Pi failed") + } + if math.Abs(AddAngles(-math.Pi, -math.Pi)) > tolerance { + t.Error("AngleSum -Pi, -Pi failed") + } + if math.Abs(AddAngles(0.0, math.Pi/3)-math.Pi/3) > tolerance { + t.Error("AngleSum 0, Pi/3 failed") + } + if math.Abs(AddAngles(math.Pi/3, math.Pi/3)-2*math.Pi/3) > tolerance { + t.Error("AngleSum Pi/3, Pi/3 failed") + } + // The following sum could return either +Pi or -Pi + if math.Abs(AddAngles(math.Pi/3, 2*math.Pi/3))-math.Pi > tolerance { + t.Error("AngleSum Pi/3, 2*Pi/3 failed") + } + if math.Abs(AddAngles(2*math.Pi/3, 2*math.Pi/3)+2*math.Pi/3) > tolerance { + t.Error("AngleSum 2*Pi/3, 2*Pi/3 failed") + } + if math.Abs(AddAngles(-2*math.Pi/3, -2*math.Pi/3)-2*math.Pi/3) > tolerance { + t.Error("AngleSum -2*Pi/3, -2*Pi/3 failed") + } +} diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index bf7fb8ff..5106c33e 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -15,7 +15,7 @@ import ( const ( // default inner size defaultWidth float32 = 50 - defaultHeight float32 = 50 + defaultHeight float32 = 25 // default padding around the inner object in a node defaultPadding float32 = 10 @@ -52,11 +52,11 @@ type DiagramNode struct { BoxStrokeWidth float32 // BoxFill is the fill color of the node, the inner object will be - // drawn on top of this. Defaults to the theme.BackgroundColor(). + // drawn on top of this. Defaults to the DiagramTheme's BackgroundColor. BoxFillColor color.Color // BoxStrokeColor is the stroke color of the node rectangle. Defaults - // to theme.TextColor(). + // to DiagramTheme's ForegroundColor BoxStrokeColor color.Color // HandleColor is the color of node handle. @@ -125,7 +125,7 @@ func (r *diagramNodeRenderer) Refresh() { } func (r *diagramNodeRenderer) BackgroundColor() color.Color { - return theme.BackgroundColor() + return r.node.Diagram.DiagramTheme.Color(theme.ColorNameBackground, r.node.Diagram.ThemeVariant) } func (r *diagramNodeRenderer) Destroy() { @@ -160,11 +160,11 @@ func NewDiagramNode(d *DiagramWidget, obj fyne.CanvasObject) *DiagramNode { Diagram: d, InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, InnerObject: obj, - Padding: defaultPadding, + Padding: d.DiagramTheme.Size(theme.SizeNamePadding), BoxStrokeWidth: 1, - BoxFillColor: theme.BackgroundColor(), - BoxStrokeColor: theme.TextColor(), - HandleColor: theme.TextColor(), + BoxFillColor: d.DiagramTheme.Color(theme.ColorNameBackground, d.ThemeVariant), + BoxStrokeColor: d.DiagramTheme.Color(theme.ColorNameForeground, d.ThemeVariant), + HandleColor: d.DiagramTheme.Color(theme.ColorNameForeground, d.ThemeVariant), HandleStroke: 3, } @@ -199,12 +199,12 @@ func (n *DiagramNode) Dragged(event *fyne.DragEvent) { } func (n *DiagramNode) MouseIn(event *desktop.MouseEvent) { - n.HandleColor = theme.FocusColor() + n.HandleColor = n.Diagram.DiagramTheme.Color(theme.ColorNameFocus, n.Diagram.ThemeVariant) n.Refresh() } func (n *DiagramNode) MouseOut() { - n.HandleColor = theme.TextColor() + n.HandleColor = n.Diagram.DiagramTheme.Color(theme.ColorNameForeground, n.Diagram.ThemeVariant) n.Refresh() } From daeaa673519b6c2332bffafd674c98ad986a1720 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 17 Mar 2023 10:16:02 -0400 Subject: [PATCH 03/53] Arrow decorations working at three locations --- cmd/diagramdemo/main.go | 18 +- widget/diagramwidget/arrowhead/arrowhead.go | 240 ++++++++++-------- widget/diagramwidget/decoration/Decoration.go | 31 +++ widget/diagramwidget/diagram.go | 15 ++ widget/diagramwidget/link.go | 161 ++++++------ 5 files changed, 273 insertions(+), 192 deletions(-) create mode 100644 widget/diagramwidget/decoration/Decoration.go diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index 0f3cf8dd..b2b63828 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -6,6 +6,7 @@ import ( "time" "fyne.io/x/fyne/widget/diagramwidget" + "fyne.io/x/fyne/widget/diagramwidget/arrowhead" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" @@ -100,13 +101,16 @@ func main() { globaldiagram = g - g.Links["edge0"] = diagramwidget.NewDiagramEdge(g, n1, n2) - g.Links["edge1"] = diagramwidget.NewDiagramEdge(g, n3, n2) - g.Links["edge1"].LinkColor = color.RGBA{255, 64, 64, 255} - g.Links["edge1"].Directed = true - g.Links["edge2"] = diagramwidget.NewDiagramEdge(g, n1, n4) - g.Links["edge3"] = diagramwidget.NewDiagramEdge(g, n3, n4) - g.Links["edge4"] = diagramwidget.NewDiagramEdge(g, n5, n4) + g.Links["edge0"] = diagramwidget.NewDiagramLink(g, n1, n2) + edge1 := diagramwidget.NewDiagramLink(g, n3, n2) + g.Links["edge1"] = edge1 + edge1.LinkColor = color.RGBA{255, 64, 64, 255} + edge1.TargetDecorations = append(g.Links["edge1"].TargetDecorations, arrowhead.NewArrowhead()) + edge1.MidpointDecorations = append(g.Links["edge1"].TargetDecorations, arrowhead.NewArrowhead()) + edge1.SourceDecorations = append(g.Links["edge1"].SourceDecorations, arrowhead.NewArrowhead()) + g.Links["edge2"] = diagramwidget.NewDiagramLink(g, n1, n4) + g.Links["edge3"] = diagramwidget.NewDiagramLink(g, n3, n4) + g.Links["edge4"] = diagramwidget.NewDiagramLink(g, n5, n4) w.SetContent(g) diff --git a/widget/diagramwidget/arrowhead/arrowhead.go b/widget/diagramwidget/arrowhead/arrowhead.go index fff618b9..e4904c25 100644 --- a/widget/diagramwidget/arrowhead/arrowhead.go +++ b/widget/diagramwidget/arrowhead/arrowhead.go @@ -10,134 +10,156 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" ) const ( - defaultTheta float32 = 0.5235 + defaultTheta float64 = 0.5235 // 30 degrees in radians defaultStrokeWidth float32 = 2 defaultLength int = 15 ) // Arrowhead defines a canvas object which renders an arrow pointing in -// a particular direction. +// a particular direction. The direction is indicated by the BaseAngle. +// The arrowhead is defined with respect to a nominal reference axis with the +// BaseAngle 0. The Tip is the reference point. // // Left // \ // \ Length // Theta \ -// Base ------- + Tip +// Axis ------- + Tip // / // / // / // Right type Arrowhead struct { - // Base is used to define the "base" of the arrow, which thus defines - // the direction which the arrow faces. - Base fyne.Position - + widget.BaseWidget + // BaseAngle is used to define direction in which the arrowhead points + // Base fyne.Position + BaseAngle float64 // Tip is the point at which the tip of the arrow will be placed. Tip fyne.Position - // StrokeWidth is the width of the arrowhead lines StrokeWidth float32 - // StrokeColor is the color of the arrowhead StrokeColor color.Color - - // Theta is the angle between the two "tails" that intersect at the - // tip. This angle is in radians. - Theta float32 - + // Theta is the angle between each of the tails and the nominal reference axis. + // This angle is in radians. + Theta float64 // Length is the length of the two "tails" that intersect at the tip. Length int - - central *canvas.Line + // central *canvas.Line left *canvas.Line right *canvas.Line visible bool } -func MakeArrowhead(base, tip fyne.Position) *Arrowhead { - return &Arrowhead{ - Base: base, - Tip: tip, +func NewArrowhead() *Arrowhead { + a := &Arrowhead{ + BaseAngle: 0.0, + Tip: fyne.Position{X: 0, Y: 0}, StrokeWidth: defaultStrokeWidth, - StrokeColor: theme.TextColor(), + StrokeColor: theme.ForegroundColor(), Theta: defaultTheta, Length: defaultLength, - central: canvas.NewLine(theme.TextColor()), - left: canvas.NewLine(theme.TextColor()), - right: canvas.NewLine(theme.TextColor()), - visible: true, + // central: canvas.NewLine(theme.TextColor()), + visible: true, } + a.ExtendBaseWidget(a) + return a } -func (a *Arrowhead) Refresh() { - a.central.StrokeWidth = a.StrokeWidth - a.left.StrokeWidth = a.StrokeWidth - a.right.StrokeWidth = a.StrokeWidth - - a.central.StrokeColor = a.StrokeColor - a.left.StrokeColor = a.StrokeColor - a.right.StrokeColor = a.StrokeColor - - a.central.Position1 = a.Tip - a.central.Position2 = a.Base - - a.left.Position1 = a.Tip - a.left.Position2 = a.LeftPoint() +func (a *Arrowhead) CreateRenderer() fyne.WidgetRenderer { + ar := arrowheadRenderer{ + widget: a, + left: canvas.NewLine(theme.ForegroundColor()), + right: canvas.NewLine(theme.ForegroundColor()), + } - a.right.Position1 = a.Tip - a.right.Position2 = a.RightPoint() + (&ar).Refresh() - if a.visible { - a.central.Show() - a.left.Show() - a.right.Show() - } else { - a.central.Hide() - a.left.Hide() - a.right.Hide() - } + return &ar +} - canvas.Refresh(a.central) - canvas.Refresh(a.left) - canvas.Refresh(a.right) +// GetReferenceLength returns the length of the decoration along the reference axis +func (a *Arrowhead) GetReferenceLength() float32 { + return float32(math.Abs(math.Cos(float64(a.Theta)) * float64(a.Length))) +} +func (a *Arrowhead) Hide() { + a.visible = false } func (a *Arrowhead) LeftPoint() fyne.Position { - // Have to change the sign of Y because the window coordinated Y axis goes down rather than up - baseVector := r2.Vec2{ - X: float64(a.Base.X - a.Tip.X), - Y: -float64(a.Base.Y - a.Tip.Y), - } - baseAngle := baseVector.Angle() - leftAngle := r2.AddAngles(baseAngle, -float64(a.Theta)) + leftAngle := r2.AddAngles(a.BaseAngle, -a.Theta) // We have to change the sign of Y because the window coordinate Y axis goes down rather than up leftPosition := fyne.Position{ X: float32(float64(a.Length) * math.Cos(leftAngle)), Y: -float32(float64(a.Length) * math.Sin(leftAngle)), } - leftPoint := a.Tip.Add(leftPosition) - return leftPoint + return leftPosition } -func (a *Arrowhead) RightPoint() fyne.Position { - // Have to change the sign of Y because the window coordinated Y axis goes down rather than up - baseVector := r2.Vec2{ - X: float64(a.Base.X - a.Tip.X), - Y: -float64(a.Base.Y - a.Tip.Y), +func (a *Arrowhead) MinSize() fyne.Size { + return a.Size() +} + +func (a *Arrowhead) Move(p fyne.Position) { + a.Tip = p +} + +func (a *Arrowhead) Objects() []fyne.CanvasObject { + return []fyne.CanvasObject{ + a.left, + a.right, } - baseAngle := baseVector.Angle() - rightAngle := r2.AddAngles(baseAngle, float64(a.Theta)) +} + +func (a *Arrowhead) Position() fyne.Position { + return a.Tip +} + +func (a *Arrowhead) Resize(s fyne.Size) { + // We get the current size and scale the length based on the difference between sizes + currentSize := a.Size() + currentLengthVector := r2.V2(float64(currentSize.Width), float64(currentSize.Height)) + currentLength := currentLengthVector.Length() + newLengthVector := r2.V2(float64(s.Width), float64(s.Height)) + newLength := newLengthVector.Length() + a.Length = int(float64(a.Length) * newLength / currentLength) +} + +func (a *Arrowhead) RightPoint() fyne.Position { + rightAngle := r2.AddAngles(a.BaseAngle, a.Theta) // We have to change the sign of Y because the window coordinate Y axis goes down rather than up rightPosition := fyne.Position{ X: float32(float64(a.Length) * math.Cos(rightAngle)), Y: -float32(float64(a.Length) * math.Sin(rightAngle)), } - rightPoint := a.Tip.Add(rightPosition) - return rightPoint + return rightPosition +} + +func (a *Arrowhead) SetStrokeColor(strokeColor color.Color) { + a.StrokeColor = strokeColor +} + +func (a *Arrowhead) SetStrokeWidth(strokeWidth float32) { + a.StrokeWidth = strokeWidth +} + +// SetReferencePoint sets the position of the decoration's reference point +func (a *Arrowhead) SetReferencePoint(point fyne.Position) { + a.Move(point) +} + +// SetReferenceAngle sets the angle (in radians) of the reference axis +func (a *Arrowhead) SetReferenceAngle(angle float64) { + a.BaseAngle = angle +} + +func (a *Arrowhead) Show() { + a.visible = true } func (a *Arrowhead) Size() fyne.Size { @@ -145,7 +167,7 @@ func (a *Arrowhead) Size() fyne.Size { rp := a.RightPoint() points := []r2.Vec2{ {X: float64(a.Tip.X), Y: float64(a.Tip.Y)}, - {X: float64(a.Base.X), Y: float64(a.Base.Y)}, + // {X: float64(a.Base.X), Y: float64(a.Base.Y)}, {X: float64(lp.X), Y: float64(lp.Y)}, {X: float64(rp.X), Y: float64(rp.Y)}, } @@ -157,55 +179,59 @@ func (a *Arrowhead) Size() fyne.Size { } } -func (a *Arrowhead) Resize(s fyne.Size) { - l := r2.V2(float64(s.Width), float64(s.Height)) - a.Length = int(l.Length()) - - tip := r2.V2(float64(a.Tip.X), float64(a.Tip.Y)) - base := r2.V2(float64(a.Base.X), float64(a.Base.Y)) - v := tip.Add(base.Scale(-1)) - v = v.ScaleToLength(l.Length()) - base = tip.Add(v) - - a.Base = fyne.Position{X: float32(base.X), Y: float32(base.Y)} +func (a *Arrowhead) Visible() bool { + return a.visible } -func (a *Arrowhead) Move(p fyne.Position) { - a.Tip = p - - tip := r2.V2(float64(a.Tip.X), float64(a.Tip.Y)) - base := r2.V2(float64(a.Base.X), float64(a.Base.Y)) - v := tip.Add(base.Scale(-1)) - base = tip.Add(v) +type arrowheadRenderer struct { + widget *Arrowhead + left *canvas.Line + right *canvas.Line +} - a.Base = fyne.Position{X: float32(base.X), Y: float32(base.Y)} +func (ar *arrowheadRenderer) MinSize() fyne.Size { + return ar.widget.Size() } -func (a *Arrowhead) MinSize() fyne.Size { - return a.Size() +func (ar *arrowheadRenderer) Layout(size fyne.Size) { } -func (a *Arrowhead) Visible() bool { - return a.visible +func (ar *arrowheadRenderer) ApplyTheme(size fyne.Size) { } -func (a *Arrowhead) Show() { - a.visible = true +func (ar *arrowheadRenderer) Refresh() { + // Coordinates for the lines are relative to the arrowhead tip position + // a.central.StrokeWidth = a.StrokeWidth + ar.left.StrokeWidth = ar.widget.StrokeWidth + ar.right.StrokeWidth = ar.widget.StrokeWidth + ar.left.StrokeColor = ar.widget.StrokeColor + ar.right.StrokeColor = ar.widget.StrokeColor + ar.left.Position1 = fyne.Position{X: 0, Y: 0} + ar.left.Position2 = ar.widget.LeftPoint() + ar.right.Position1 = fyne.Position{X: 0, Y: 0} + ar.right.Position2 = ar.widget.RightPoint() + if ar.widget.visible { + ar.left.Show() + ar.right.Show() + } else { + ar.left.Hide() + ar.right.Hide() + } + canvas.Refresh(ar.left) + canvas.Refresh(ar.right) } -func (a *Arrowhead) Hide() { - a.visible = false +func (ar *arrowheadRenderer) BackgroundColor() color.Color { + return theme.BackgroundColor() } -func (a *Arrowhead) Position() fyne.Position { - return a.Tip +func (ar *arrowheadRenderer) Destroy() { } -// temporary hack, don't do this -func (a *Arrowhead) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{ - a.central, - a.left, - a.right, +func (ar *arrowheadRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{ + ar.left, + ar.right, } + return obj } diff --git a/widget/diagramwidget/decoration/Decoration.go b/widget/diagramwidget/decoration/Decoration.go new file mode 100644 index 00000000..cc0f04ed --- /dev/null +++ b/widget/diagramwidget/decoration/Decoration.go @@ -0,0 +1,31 @@ +package decoration + +import ( + "image/color" + + "fyne.io/fyne/v2" +) + +// Decoration is a widget intended to be used as a decoration on a Link widget +// The graphical representation of the widget is defined along a reference axis with +// one point on that axis designated as the reference point (generally the origin). +// Depending on the Link widget's use of the decoration, the reference point will either +// be aligned with one of the endpoints of the link or with some intermediate point on the +// link. The Link will move the Decoration's reference point as the link itself is modified. +// The Link will also determine the slope of the Link's line at the reference point and +// direct the Decoration to rotate about the reference point to achieve the correct alignment +// of the decoration with respect to the Link's line. +// The Link may have more than one decoration stacked along the line at the reference point. +// To accomplish this, it needs to know the length of the decoration along the reference axis +// so that it can adjust the position of the next decoration appropriately. +type Decoration interface { + fyne.Widget + SetStrokeColor(color color.Color) + SetStrokeWidth(width float32) + // SetReferencePoint sets the position of the decoration's reference point + SetReferencePoint(point fyne.Position) + // SetReferenceAngle sets the angle of the reference axis + SetReferenceAngle(angle float64) // Angle in radians + // GetReferenceLength returns the length of the decoration along the reference axis + GetReferenceLength() float32 +} diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 7b1c99ef..7edbc7cd 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -63,6 +63,21 @@ func (r *diagramRenderer) Objects() []fyne.CanvasObject { } for _, e := range r.graph.Links { obj = append(obj, e) + for _, sourceDecoration := range e.SourceDecorations { + if sourceDecoration != nil { + obj = append(obj, sourceDecoration) + } + } + for _, midpointDecoration := range e.MidpointDecorations { + if midpointDecoration != nil { + obj = append(obj, midpointDecoration) + } + } + for _, targetDecoration := range e.TargetDecorations { + if targetDecoration != nil { + obj = append(obj, targetDecoration) + } + } } return obj diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 88589178..a8385065 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -2,8 +2,9 @@ package diagramwidget import ( "image/color" + "math" - "fyne.io/x/fyne/widget/diagramwidget/arrowhead" + "fyne.io/x/fyne/widget/diagramwidget/decoration" "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" "fyne.io/fyne/v2" @@ -12,25 +13,54 @@ import ( "fyne.io/fyne/v2/widget" ) -type diagramLinkRenderer struct { - edge *DiagramLink - line *canvas.Line - arrow *arrowhead.Arrowhead -} - type DiagramLink struct { widget.BaseWidget + Diagram *DiagramWidget + LinkColor color.Color + Width float32 + Origin *DiagramNode + Target *DiagramNode + SourceDecorations []decoration.Decoration + TargetDecorations []decoration.Decoration + MidpointDecorations []decoration.Decoration + // Directed bool +} - Diagram *DiagramWidget +func NewDiagramLink(g *DiagramWidget, v, u *DiagramNode) *DiagramLink { + e := &DiagramLink{ + Diagram: g, + LinkColor: theme.TextColor(), + Width: 2, + Origin: v, + Target: u, + // Directed: false, + } - LinkColor color.Color + e.ExtendBaseWidget(e) - Width float32 + return e +} - Origin *DiagramNode - Target *DiagramNode +func (e *DiagramLink) CreateRenderer() fyne.WidgetRenderer { + r := diagramLinkRenderer{ + edge: e, + line: canvas.NewLine(e.LinkColor), + // arrow: arrowhead.MakeArrowhead(fyne.Position{X: 0, Y: 0}, fyne.Position{X: 0, Y: 0}), + } - Directed bool + (&r).Refresh() + + return &r +} + +func (e *DiagramLink) R2Line() r2.Line { + return r2.MakeLineFromEndpoints(e.Origin.R2Center(), e.Target.R2Center()) +} + +type diagramLinkRenderer struct { + edge *DiagramLink + line *canvas.Line + // arrow *arrowhead.Arrowhead } func (r *diagramLinkRenderer) MinSize() fyne.Size { @@ -55,38 +85,50 @@ func (r *diagramLinkRenderer) ApplyTheme(size fyne.Size) { func (r *diagramLinkRenderer) Refresh() { l := r.edge.R2Line() - b1 := r.edge.Origin.R2Box() - b2 := r.edge.Target.R2Box() - - p1, _ := b1.Intersect(l) - p2, _ := b2.Intersect(l) - + sourceBox := r.edge.Origin.R2Box() + targetBox := r.edge.Target.R2Box() + sourcePoint, _ := sourceBox.Intersect(l) + targetPoint, _ := targetBox.Intersect(l) r.line.Position1 = fyne.Position{ - X: float32(p1.X), - Y: float32(p1.Y), + X: float32(sourcePoint.X), + Y: float32(sourcePoint.Y), } - r.line.Position2 = fyne.Position{ - X: float32(p2.X), - Y: float32(p2.Y), + X: float32(targetPoint.X), + Y: float32(targetPoint.Y), } - r.line.StrokeColor = r.edge.LinkColor r.line.StrokeWidth = r.edge.Width - - if r.edge.Directed { - r.arrow.Show() - r.arrow.Tip = r.line.Position1 - r.arrow.Base = r.line.Position2 - r.arrow.StrokeColor = r.edge.LinkColor - r.arrow.StrokeWidth = r.edge.Width - } else { - r.arrow.Hide() - } - canvas.Refresh(r.line) - canvas.Refresh(r.arrow) - r.arrow.Refresh() + // Have to change the sign of Y since the window inverts the Y axis + lineVector := r2.Vec2{X: float64(r.line.Position2.X - r.line.Position1.X), Y: -float64(r.line.Position2.Y - r.line.Position1.Y)} + sourceAngle := lineVector.Angle() + targetAngle := r2.AddAngles(sourceAngle, math.Pi) + for _, decoration := range r.edge.SourceDecorations { + decoration.SetStrokeColor(r.edge.LinkColor) + decoration.SetStrokeWidth(r.edge.Width) + decoration.SetReferencePoint(r.line.Position1) + decoration.SetReferenceAngle(sourceAngle) + decoration.Refresh() + } + midPosition := fyne.Position{ + X: float32((sourcePoint.X + targetPoint.X) / 2), + Y: float32((sourcePoint.Y + targetPoint.Y) / 2), + } + for _, decoration := range r.edge.MidpointDecorations { + decoration.SetStrokeColor(r.edge.LinkColor) + decoration.SetStrokeWidth(r.edge.Width) + decoration.SetReferencePoint(midPosition) + decoration.SetReferenceAngle(targetAngle) + decoration.Refresh() + } + for _, decoration := range r.edge.TargetDecorations { + decoration.SetStrokeColor(r.edge.LinkColor) + decoration.SetStrokeWidth(r.edge.Width) + decoration.SetReferencePoint(r.line.Position2) + decoration.SetReferenceAngle(targetAngle) + decoration.Refresh() + } } func (r *diagramLinkRenderer) BackgroundColor() color.Color { @@ -97,45 +139,8 @@ func (r *diagramLinkRenderer) Destroy() { } func (r *diagramLinkRenderer) Objects() []fyne.CanvasObject { - // obj := []fyne.CanvasObject{ - // r.line, - // // r.arrow, - // } - - // XXX: temporary hack because otherwise I can't get my canvas object - // to show up??? - obj := r.arrow.Objects() - obj = append(obj, r.line) - return obj -} - -func (e *DiagramLink) CreateRenderer() fyne.WidgetRenderer { - r := diagramLinkRenderer{ - edge: e, - line: canvas.NewLine(e.LinkColor), - arrow: arrowhead.MakeArrowhead(fyne.Position{X: 0, Y: 0}, fyne.Position{X: 0, Y: 0}), - } - - (&r).Refresh() - - return &r -} - -func (e *DiagramLink) R2Line() r2.Line { - return r2.MakeLineFromEndpoints(e.Origin.R2Center(), e.Target.R2Center()) -} - -func NewDiagramEdge(g *DiagramWidget, v, u *DiagramNode) *DiagramLink { - e := &DiagramLink{ - Diagram: g, - LinkColor: theme.TextColor(), - Width: 2, - Origin: v, - Target: u, - Directed: false, + obj := []fyne.CanvasObject{ + r.line, } - - e.ExtendBaseWidget(e) - - return e + return obj } From 21033d4be5fe7a04fd20b95213f3cbd38e24a8e0 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 17 Mar 2023 14:34:35 -0400 Subject: [PATCH 04/53] Stacked Decorations working --- cmd/diagramdemo/main.go | 5 +- widget/diagramwidget/arrowhead/arrowhead.go | 8 ++-- widget/diagramwidget/link.go | 52 +++++++++++++++------ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index b2b63828..32917a84 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -106,7 +106,10 @@ func main() { g.Links["edge1"] = edge1 edge1.LinkColor = color.RGBA{255, 64, 64, 255} edge1.TargetDecorations = append(g.Links["edge1"].TargetDecorations, arrowhead.NewArrowhead()) - edge1.MidpointDecorations = append(g.Links["edge1"].TargetDecorations, arrowhead.NewArrowhead()) + edge1.TargetDecorations = append(g.Links["edge1"].TargetDecorations, arrowhead.NewArrowhead()) + edge1.MidpointDecorations = append(g.Links["edge1"].MidpointDecorations, arrowhead.NewArrowhead()) + edge1.MidpointDecorations = append(g.Links["edge1"].MidpointDecorations, arrowhead.NewArrowhead()) + edge1.SourceDecorations = append(g.Links["edge1"].SourceDecorations, arrowhead.NewArrowhead()) edge1.SourceDecorations = append(g.Links["edge1"].SourceDecorations, arrowhead.NewArrowhead()) g.Links["edge2"] = diagramwidget.NewDiagramLink(g, n1, n4) g.Links["edge3"] = diagramwidget.NewDiagramLink(g, n3, n4) diff --git a/widget/diagramwidget/arrowhead/arrowhead.go b/widget/diagramwidget/arrowhead/arrowhead.go index e4904c25..8757f40c 100644 --- a/widget/diagramwidget/arrowhead/arrowhead.go +++ b/widget/diagramwidget/arrowhead/arrowhead.go @@ -194,6 +194,10 @@ func (ar *arrowheadRenderer) MinSize() fyne.Size { } func (ar *arrowheadRenderer) Layout(size fyne.Size) { + ar.left.Position1 = fyne.Position{X: 0, Y: 0} + ar.left.Position2 = ar.widget.LeftPoint() + ar.right.Position1 = fyne.Position{X: 0, Y: 0} + ar.right.Position2 = ar.widget.RightPoint() } func (ar *arrowheadRenderer) ApplyTheme(size fyne.Size) { @@ -206,10 +210,6 @@ func (ar *arrowheadRenderer) Refresh() { ar.right.StrokeWidth = ar.widget.StrokeWidth ar.left.StrokeColor = ar.widget.StrokeColor ar.right.StrokeColor = ar.widget.StrokeColor - ar.left.Position1 = fyne.Position{X: 0, Y: 0} - ar.left.Position2 = ar.widget.LeftPoint() - ar.right.Position1 = fyne.Position{X: 0, Y: 0} - ar.right.Position2 = ar.widget.RightPoint() if ar.widget.visible { ar.left.Show() ar.right.Show() diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index a8385065..20fcb78f 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -78,12 +78,6 @@ func (r *diagramLinkRenderer) MinSize() fyne.Size { } func (r *diagramLinkRenderer) Layout(size fyne.Size) { -} - -func (r *diagramLinkRenderer) ApplyTheme(size fyne.Size) { -} - -func (r *diagramLinkRenderer) Refresh() { l := r.edge.R2Line() sourceBox := r.edge.Origin.R2Box() targetBox := r.edge.Target.R2Box() @@ -104,29 +98,59 @@ func (r *diagramLinkRenderer) Refresh() { lineVector := r2.Vec2{X: float64(r.line.Position2.X - r.line.Position1.X), Y: -float64(r.line.Position2.Y - r.line.Position1.Y)} sourceAngle := lineVector.Angle() targetAngle := r2.AddAngles(sourceAngle, math.Pi) + sourceOffset := 0.0 for _, decoration := range r.edge.SourceDecorations { - decoration.SetStrokeColor(r.edge.LinkColor) - decoration.SetStrokeWidth(r.edge.Width) - decoration.SetReferencePoint(r.line.Position1) + decorationReferencePoint := fyne.Position{ + X: float32(float64(r.line.Position1.X) + math.Cos(sourceAngle)*sourceOffset), + Y: float32(float64(r.line.Position1.Y) - math.Sin(sourceAngle)*sourceOffset), + } + decoration.SetReferencePoint(decorationReferencePoint) decoration.SetReferenceAngle(sourceAngle) - decoration.Refresh() + sourceOffset = sourceOffset + float64(decoration.GetReferenceLength()) } midPosition := fyne.Position{ X: float32((sourcePoint.X + targetPoint.X) / 2), Y: float32((sourcePoint.Y + targetPoint.Y) / 2), } + midOffset := 0.0 + for _, decoration := range r.edge.MidpointDecorations { + decorationReferencePoint := fyne.Position{ + X: float32(float64(midPosition.X) + math.Cos(targetAngle)*midOffset), + Y: float32(float64(midPosition.Y) - math.Sin(targetAngle)*midOffset), + } + decoration.SetReferencePoint(decorationReferencePoint) + decoration.SetReferenceAngle(targetAngle) + midOffset = midOffset + float64(decoration.GetReferenceLength()) + } + targetOffset := 0.0 + for _, decoration := range r.edge.TargetDecorations { + decorationReferencePoint := fyne.Position{ + X: float32(float64(r.line.Position2.X) + math.Cos(targetAngle)*targetOffset), + Y: float32(float64(r.line.Position2.Y) - math.Sin(targetAngle)*targetOffset), + } + decoration.SetReferencePoint(decorationReferencePoint) + decoration.SetReferenceAngle(targetAngle) + targetOffset = targetOffset + float64(decoration.GetReferenceLength()) + } +} + +func (r *diagramLinkRenderer) ApplyTheme(size fyne.Size) { +} + +func (r *diagramLinkRenderer) Refresh() { + for _, decoration := range r.edge.SourceDecorations { + decoration.SetStrokeColor(r.edge.LinkColor) + decoration.SetStrokeWidth(r.edge.Width) + decoration.Refresh() + } for _, decoration := range r.edge.MidpointDecorations { decoration.SetStrokeColor(r.edge.LinkColor) decoration.SetStrokeWidth(r.edge.Width) - decoration.SetReferencePoint(midPosition) - decoration.SetReferenceAngle(targetAngle) decoration.Refresh() } for _, decoration := range r.edge.TargetDecorations { decoration.SetStrokeColor(r.edge.LinkColor) decoration.SetStrokeWidth(r.edge.Width) - decoration.SetReferencePoint(r.line.Position2) - decoration.SetReferenceAngle(targetAngle) decoration.Refresh() } } From d00df3b94cdd0c8ef3f9841d3e7dcd501640bbe3 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 21 Mar 2023 15:58:39 -0400 Subject: [PATCH 05/53] Anchored Text working --- cmd/diagramdemo/main.go | 129 +++++----- widget/diagramwidget/anchoredtext.go | 136 +++++++++++ widget/diagramwidget/arrowhead/arrowhead.go | 53 +--- widget/diagramwidget/decoration/Decoration.go | 2 - widget/diagramwidget/diagram.go | 155 ++++++------ widget/diagramwidget/link.go | 231 ++++++++++++------ widget/diagramwidget/node.go | 194 +++++++-------- 7 files changed, 523 insertions(+), 377 deletions(-) create mode 100644 widget/diagramwidget/anchoredtext.go diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index 32917a84..45ff2683 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -16,8 +16,6 @@ import ( var forceticks int = 0 -var globaldiagram *diagramwidget.DiagramWidget - func forceanim() { // XXX: very naughty -- accesses shared memory in potentially unsafe @@ -25,8 +23,8 @@ func forceanim() { for { if forceticks > 0 { - globaldiagram.StepForceLayout(300) - globaldiagram.Refresh() + diagramwidget.Globaldiagram.StepForceLayout(300) + diagramwidget.Globaldiagram.Refresh() forceticks-- fmt.Printf("forceticks=%d\n", forceticks) } @@ -41,81 +39,90 @@ func main() { w.SetMaster() - g := diagramwidget.NewDiagram() + diagramWidget := diagramwidget.NewDiagramWidget() + diagramwidget.Globaldiagram = diagramWidget go forceanim() - l := widget.NewLabel("teeexxttt") - n := diagramwidget.NewDiagramNode(g, l) - g.Nodes["node0"] = n - n1 := n - - b := widget.NewButton("button", func() { fmt.Printf("tapped!\n") }) - n = diagramwidget.NewDiagramNode(g, b) - n.Move(fyne.Position{X: 200, Y: 200}) - g.Nodes["node1"] = n - n2 := n - - n = diagramwidget.NewDiagramNode(g, nil) - c := container.NewVBox( - widget.NewLabel("Fancy node!"), + // Node 0 + node0Label := widget.NewLabel("Node0") + node0 := diagramwidget.NewDiagramNode(diagramWidget, node0Label) + diagramWidget.Nodes["node0"] = node0 + + // Node 1 + node1Button := widget.NewButton("Node1 Button", func() { fmt.Printf("tapped Node1!\n") }) + node1 := diagramwidget.NewDiagramNode(diagramWidget, node1Button) + node1.Move(fyne.Position{X: 200, Y: 200}) + diagramWidget.Nodes["node1"] = node1 + + // Node 2 + node2 := diagramwidget.NewDiagramNode(diagramWidget, nil) + node2Container := container.NewVBox( + widget.NewLabel("Node2 - with structure"), widget.NewButton("Up", func() { - n.Displace(fyne.Position{X: 0, Y: -10}) - n.Refresh() + node2.Displace(fyne.Position{X: 0, Y: -10}) + node2.Refresh() }), widget.NewButton("Down", func() { - n.Displace(fyne.Position{X: 0, Y: 10}) - n.Refresh() + node2.Displace(fyne.Position{X: 0, Y: 10}) + node2.Refresh() }), container.NewHBox( widget.NewButton("Left", func() { - n.Displace(fyne.Position{X: -10, Y: 0}) - n.Refresh() + node2.Displace(fyne.Position{X: -10, Y: 0}) + node2.Refresh() }), widget.NewButton("Right", func() { - n.Displace(fyne.Position{X: 10, Y: 0}) - n.Refresh() + node2.Displace(fyne.Position{X: 10, Y: 0}) + node2.Refresh() }), ), ) - n.InnerObject = c - n.Move(fyne.Position{X: 300, Y: 300}) - g.Nodes["node2"] = n - n3 := n - - n = diagramwidget.NewDiagramNode(g, widget.NewButton("force layout step", func() { - g.StepForceLayout(300) - g.Refresh() + node2.InnerObject = node2Container + node2.Move(fyne.Position{X: 300, Y: 300}) + diagramWidget.Nodes["node2"] = node2 + + // Node 3 + node3 := diagramwidget.NewDiagramNode(diagramWidget, widget.NewButton("Node3: Force layout step", func() { + diagramWidget.StepForceLayout(300) + diagramWidget.Refresh() })) - n.Move(fyne.Position{X: 400, Y: 200}) - g.Nodes["node4"] = n - n4 := n + node3.Move(fyne.Position{X: 400, Y: 200}) + diagramWidget.Nodes["node3"] = node3 - n = diagramwidget.NewDiagramNode(g, widget.NewButton("auto layout", func() { + // Node 4 + node4 := diagramwidget.NewDiagramNode(diagramWidget, widget.NewButton("Node4: auto layout", func() { forceticks += 100 - g.Refresh() + diagramWidget.Refresh() })) - n.Move(fyne.Position{X: 400, Y: 500}) - g.Nodes["node5"] = n - n5 := n - - globaldiagram = g - - g.Links["edge0"] = diagramwidget.NewDiagramLink(g, n1, n2) - edge1 := diagramwidget.NewDiagramLink(g, n3, n2) - g.Links["edge1"] = edge1 - edge1.LinkColor = color.RGBA{255, 64, 64, 255} - edge1.TargetDecorations = append(g.Links["edge1"].TargetDecorations, arrowhead.NewArrowhead()) - edge1.TargetDecorations = append(g.Links["edge1"].TargetDecorations, arrowhead.NewArrowhead()) - edge1.MidpointDecorations = append(g.Links["edge1"].MidpointDecorations, arrowhead.NewArrowhead()) - edge1.MidpointDecorations = append(g.Links["edge1"].MidpointDecorations, arrowhead.NewArrowhead()) - edge1.SourceDecorations = append(g.Links["edge1"].SourceDecorations, arrowhead.NewArrowhead()) - edge1.SourceDecorations = append(g.Links["edge1"].SourceDecorations, arrowhead.NewArrowhead()) - g.Links["edge2"] = diagramwidget.NewDiagramLink(g, n1, n4) - g.Links["edge3"] = diagramwidget.NewDiagramLink(g, n3, n4) - g.Links["edge4"] = diagramwidget.NewDiagramLink(g, n5, n4) - - w.SetContent(g) + node4.Move(fyne.Position{X: 400, Y: 500}) + diagramWidget.Nodes["node4"] = node4 + + link0 := diagramwidget.NewDiagramLink(diagramWidget, node0, node1) + diagramWidget.Links["link0"] = link0 + link0.AddSourceAnchoredText("sourceRole", "sourceRole") + + link1 := diagramwidget.NewDiagramLink(diagramWidget, node2, node1) + diagramWidget.Links["link1"] = link1 + link1.LinkColor = color.RGBA{255, 64, 64, 255} + link1.TargetDecorations = append(diagramWidget.Links["link1"].TargetDecorations, arrowhead.NewArrowhead()) + link1.TargetDecorations = append(diagramWidget.Links["link1"].TargetDecorations, arrowhead.NewArrowhead()) + link1.MidpointDecorations = append(diagramWidget.Links["link1"].MidpointDecorations, arrowhead.NewArrowhead()) + link1.MidpointDecorations = append(diagramWidget.Links["link1"].MidpointDecorations, arrowhead.NewArrowhead()) + link1.SourceDecorations = append(diagramWidget.Links["link1"].SourceDecorations, arrowhead.NewArrowhead()) + link1.SourceDecorations = append(diagramWidget.Links["link1"].SourceDecorations, arrowhead.NewArrowhead()) + + diagramWidget.Links["link2"] = diagramwidget.NewDiagramLink(diagramWidget, node0, node3) + + link3 := diagramwidget.NewDiagramLink(diagramWidget, node2, node3) + link3.AddSourceAnchoredText("sourceRole", "sourceRole") + link3.AddMidpointAnchoredText("linkName", "Link 3") + link3.AddTargetAnchoredText("targetRole", "targetRole") + diagramWidget.Links["link3"] = link3 + + diagramWidget.Links["link4"] = diagramwidget.NewDiagramLink(diagramWidget, node4, node3) + + w.SetContent(diagramWidget) w.ShowAndRun() } diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go new file mode 100644 index 00000000..90ae317d --- /dev/null +++ b/widget/diagramwidget/anchoredtext.go @@ -0,0 +1,136 @@ +package diagramwidget + +import ( + "image/color" + "log" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" +) + +// AnchoredText provides a text annotation for a path that is anchored to one +// of the path's reference points (e.g. end or middle). The anchored text may +// be moved independently, but it keeps track of its position relative to the +// reference point. If the reference point moves, the AnchoredText will also +// move by the same amount +type AnchoredText struct { + widget.BaseWidget + offset r2.Vec2 + referencePosition fyne.Position + displayedText string + ForegroundColor color.Color +} + +func NewAnchoredText(text string) *AnchoredText { + at := &AnchoredText{ + displayedText: text, + offset: r2.MakeVec2(0, 0), + ForegroundColor: theme.ForegroundColor(), + referencePosition: fyne.Position{X: 0, Y: 0}, + } + at.ExtendBaseWidget(at) + return at +} + +func (at *AnchoredText) CreateRenderer() fyne.WidgetRenderer { + atr := &anchoredTextRenderer{ + widget: at, + textObject: canvas.NewText(at.displayedText, color.Black), + } + + atr.Refresh() + + return atr +} + +func (at *AnchoredText) Displace(delta fyne.Position) { + at.Move(at.Position().Add(delta)) +} + +func (at *AnchoredText) DragEnd() { + log.Printf("DragEnd AnchoredText %p", at) + at.Refresh() +} + +func (at *AnchoredText) Dragged(event *fyne.DragEvent) { + log.Printf("Ancored Text Dragged %p", at) + delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} + at.Move(at.Position().Add(delta)) + at.Refresh() + ForceRefresh() +} + +func (at *AnchoredText) MinSize() fyne.Size { + minSize := fyne.Size{Height: 25, Width: 50} + // log.Printf("AnchoredText size: %v pointer: %p", minSize, at) + return minSize + // log.Printf("AnchoredText size: %v pointer: %p", at.textObject.Size(), at) + // return at.textObject.Size() +} + +func (at *AnchoredText) MouseIn(event *desktop.MouseEvent) { + // at.textObject.TextStyle.Bold = true + log.Printf("MouseIn Anchored Text %p", at) + at.Refresh() +} + +func (at *AnchoredText) MouseMoved(event *desktop.MouseEvent) { + +} + +func (at *AnchoredText) MouseOut() { + // at.textObject.TextStyle.Bold = false + log.Printf("MouseOut Anchored Text %p", at) + at.Refresh() +} + +func (at *AnchoredText) Move(position fyne.Position) { + delta := r2.MakeVec2(float64(position.X-at.Position().X), float64(position.Y-at.Position().Y)) + at.offset = at.offset.Add(delta) + at.BaseWidget.Move(position) +} + +func (at *AnchoredText) SetForegroundColor(fc color.Color) { + at.ForegroundColor = fc + at.Refresh() +} + +func (at *AnchoredText) SetReferencePosition(position fyne.Position) { + delta := fyne.Delta{DX: float32(position.X - at.referencePosition.X), DY: float32(position.Y - at.referencePosition.Y)} + // We don't want to change the offset here, so we call the BaseWidget.Move directly + at.BaseWidget.Move(at.Position().Add(delta)) + at.referencePosition = position +} + +// anchoredTextRenderer +type anchoredTextRenderer struct { + widget *AnchoredText + textObject *canvas.Text +} + +func (atr *anchoredTextRenderer) Destroy() { + +} + +func (atr *anchoredTextRenderer) Layout(size fyne.Size) { + atr.widget.Resize(atr.textObject.MinSize()) +} + +func (atr *anchoredTextRenderer) MinSize() fyne.Size { + return atr.textObject.MinSize() +} + +func (atr *anchoredTextRenderer) Objects() []fyne.CanvasObject { + canvasObjects := []fyne.CanvasObject{ + atr.textObject, + } + return canvasObjects +} + +func (atr *anchoredTextRenderer) Refresh() { + // atr.widget.textObject.Color = atr.widget.ForegroundColor +} diff --git a/widget/diagramwidget/arrowhead/arrowhead.go b/widget/diagramwidget/arrowhead/arrowhead.go index 8757f40c..515f10a6 100644 --- a/widget/diagramwidget/arrowhead/arrowhead.go +++ b/widget/diagramwidget/arrowhead/arrowhead.go @@ -22,13 +22,13 @@ const ( // Arrowhead defines a canvas object which renders an arrow pointing in // a particular direction. The direction is indicated by the BaseAngle. // The arrowhead is defined with respect to a nominal reference axis with the -// BaseAngle 0. The Tip is the reference point. +// BaseAngle 0. The Position() is the reference point. // // Left // \ // \ Length // Theta \ -// Axis ------- + Tip +// Axis ------- + Position() // / // / // / @@ -38,8 +38,7 @@ type Arrowhead struct { // BaseAngle is used to define direction in which the arrowhead points // Base fyne.Position BaseAngle float64 - // Tip is the point at which the tip of the arrow will be placed. - Tip fyne.Position + // Position() is the point at which the tip of the arrow will be placed. // StrokeWidth is the width of the arrowhead lines StrokeWidth float32 // StrokeColor is the color of the arrowhead @@ -58,13 +57,11 @@ type Arrowhead struct { func NewArrowhead() *Arrowhead { a := &Arrowhead{ BaseAngle: 0.0, - Tip: fyne.Position{X: 0, Y: 0}, StrokeWidth: defaultStrokeWidth, StrokeColor: theme.ForegroundColor(), Theta: defaultTheta, Length: defaultLength, - // central: canvas.NewLine(theme.TextColor()), - visible: true, + visible: true, } a.ExtendBaseWidget(a) return a @@ -76,9 +73,6 @@ func (a *Arrowhead) CreateRenderer() fyne.WidgetRenderer { left: canvas.NewLine(theme.ForegroundColor()), right: canvas.NewLine(theme.ForegroundColor()), } - - (&ar).Refresh() - return &ar } @@ -87,10 +81,6 @@ func (a *Arrowhead) GetReferenceLength() float32 { return float32(math.Abs(math.Cos(float64(a.Theta)) * float64(a.Length))) } -func (a *Arrowhead) Hide() { - a.visible = false -} - func (a *Arrowhead) LeftPoint() fyne.Position { leftAngle := r2.AddAngles(a.BaseAngle, -a.Theta) // We have to change the sign of Y because the window coordinate Y axis goes down rather than up @@ -105,21 +95,6 @@ func (a *Arrowhead) MinSize() fyne.Size { return a.Size() } -func (a *Arrowhead) Move(p fyne.Position) { - a.Tip = p -} - -func (a *Arrowhead) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{ - a.left, - a.right, - } -} - -func (a *Arrowhead) Position() fyne.Position { - return a.Tip -} - func (a *Arrowhead) Resize(s fyne.Size) { // We get the current size and scale the length based on the difference between sizes currentSize := a.Size() @@ -148,26 +123,16 @@ func (a *Arrowhead) SetStrokeWidth(strokeWidth float32) { a.StrokeWidth = strokeWidth } -// SetReferencePoint sets the position of the decoration's reference point -func (a *Arrowhead) SetReferencePoint(point fyne.Position) { - a.Move(point) -} - // SetReferenceAngle sets the angle (in radians) of the reference axis func (a *Arrowhead) SetReferenceAngle(angle float64) { a.BaseAngle = angle } -func (a *Arrowhead) Show() { - a.visible = true -} - func (a *Arrowhead) Size() fyne.Size { lp := a.LeftPoint() rp := a.RightPoint() points := []r2.Vec2{ - {X: float64(a.Tip.X), Y: float64(a.Tip.Y)}, - // {X: float64(a.Base.X), Y: float64(a.Base.Y)}, + {X: float64(a.Position().X), Y: float64(a.Position().Y)}, {X: float64(lp.X), Y: float64(lp.Y)}, {X: float64(rp.X), Y: float64(rp.Y)}, } @@ -179,10 +144,6 @@ func (a *Arrowhead) Size() fyne.Size { } } -func (a *Arrowhead) Visible() bool { - return a.visible -} - type arrowheadRenderer struct { widget *Arrowhead left *canvas.Line @@ -204,8 +165,6 @@ func (ar *arrowheadRenderer) ApplyTheme(size fyne.Size) { } func (ar *arrowheadRenderer) Refresh() { - // Coordinates for the lines are relative to the arrowhead tip position - // a.central.StrokeWidth = a.StrokeWidth ar.left.StrokeWidth = ar.widget.StrokeWidth ar.right.StrokeWidth = ar.widget.StrokeWidth ar.left.StrokeColor = ar.widget.StrokeColor @@ -217,8 +176,6 @@ func (ar *arrowheadRenderer) Refresh() { ar.left.Hide() ar.right.Hide() } - canvas.Refresh(ar.left) - canvas.Refresh(ar.right) } func (ar *arrowheadRenderer) BackgroundColor() color.Color { diff --git a/widget/diagramwidget/decoration/Decoration.go b/widget/diagramwidget/decoration/Decoration.go index cc0f04ed..6e14727b 100644 --- a/widget/diagramwidget/decoration/Decoration.go +++ b/widget/diagramwidget/decoration/Decoration.go @@ -22,8 +22,6 @@ type Decoration interface { fyne.Widget SetStrokeColor(color color.Color) SetStrokeWidth(width float32) - // SetReferencePoint sets the position of the decoration's reference point - SetReferencePoint(point fyne.Position) // SetReferenceAngle sets the angle of the reference axis SetReferenceAngle(angle float64) // Angle in radians // GetReferenceLength returns the length of the decoration along the reference axis diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 7edbc7cd..2558d343 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -4,13 +4,16 @@ import ( "image/color" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) -type diagramRenderer struct { - graph *DiagramWidget +var Globaldiagram *DiagramWidget + +func ForceRefresh() { + Globaldiagram.DummyBox.Refresh() } type DiagramWidget struct { @@ -26,109 +29,60 @@ type DiagramWidget struct { // up, defaults to 800 x 600 DesiredSize fyne.Size - Nodes map[string]*DiagramNode - Links map[string]*DiagramLink -} - -func (r *diagramRenderer) MinSize() fyne.Size { - return r.graph.DesiredSize -} - -func (r *diagramRenderer) Layout(size fyne.Size) { + Nodes map[string]*DiagramNode + Links map[string]*DiagramLink + DummyBox *canvas.Rectangle } -func (r *diagramRenderer) ApplyTheme(size fyne.Size) { -} - -func (r *diagramRenderer) Refresh() { - for _, e := range r.graph.Links { - e.Refresh() - } - for _, n := range r.graph.Nodes { - n.Refresh() +func NewDiagramWidget() *DiagramWidget { + d := &DiagramWidget{ + DiagramTheme: fyne.CurrentApp().Settings().Theme(), + ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), + DesiredSize: fyne.Size{Width: 800, Height: 600}, + Offset: fyne.Position{X: 0, Y: 0}, + Nodes: map[string]*DiagramNode{}, + Links: map[string]*DiagramLink{}, + DummyBox: canvas.NewRectangle(color.Transparent), } -} - -func (r *diagramRenderer) BackgroundColor() color.Color { - return theme.BackgroundColor() -} + d.DummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) + d.DummyBox.Move(fyne.Position{X: 50, Y: 50}) -func (r *diagramRenderer) Destroy() { -} - -func (r *diagramRenderer) Objects() []fyne.CanvasObject { - obj := make([]fyne.CanvasObject, 0) - for _, n := range r.graph.Nodes { - obj = append(obj, n) - } - for _, e := range r.graph.Links { - obj = append(obj, e) - for _, sourceDecoration := range e.SourceDecorations { - if sourceDecoration != nil { - obj = append(obj, sourceDecoration) - } - } - for _, midpointDecoration := range e.MidpointDecorations { - if midpointDecoration != nil { - obj = append(obj, midpointDecoration) - } - } - for _, targetDecoration := range e.TargetDecorations { - if targetDecoration != nil { - obj = append(obj, targetDecoration) - } - } - } + d.ExtendBaseWidget(d) - return obj + return d } -func (g *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { - r := diagramRenderer{ - graph: g, +func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { + r := diagramWidgetRenderer{ + diagramWidget: dw, } return &r } -func (g *DiagramWidget) Cursor() desktop.Cursor { +func (dw *DiagramWidget) Cursor() desktop.Cursor { return desktop.DefaultCursor } -func (g *DiagramWidget) DragEnd() { - g.Refresh() +func (dw *DiagramWidget) DragEnd() { + dw.Refresh() } -func (g *DiagramWidget) Dragged(event *fyne.DragEvent) { +func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} - for _, n := range g.Nodes { + for _, n := range dw.Nodes { n.Displace(delta) } - g.Refresh() + dw.Refresh() } -func (g *DiagramWidget) MouseIn(event *desktop.MouseEvent) { +func (dw *DiagramWidget) MouseIn(event *desktop.MouseEvent) { } -func (g *DiagramWidget) MouseOut() { +func (dw *DiagramWidget) MouseOut() { } -func (g *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { -} - -func NewDiagram() *DiagramWidget { - d := &DiagramWidget{ - DiagramTheme: fyne.CurrentApp().Settings().Theme(), - ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), - DesiredSize: fyne.Size{Width: 800, Height: 600}, - Offset: fyne.Position{X: 0, Y: 0}, - Nodes: map[string]*DiagramNode{}, - Links: map[string]*DiagramLink{}, - } - - d.ExtendBaseWidget(d) - - return d +func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { } func (d *DiagramWidget) GetEdges(n *DiagramNode) []*DiagramLink { @@ -144,3 +98,46 @@ func (d *DiagramWidget) GetEdges(n *DiagramNode) []*DiagramLink { return links } + +type diagramWidgetRenderer struct { + diagramWidget *DiagramWidget +} + +func (r *diagramWidgetRenderer) MinSize() fyne.Size { + return r.diagramWidget.DesiredSize +} + +func (r *diagramWidgetRenderer) Layout(size fyne.Size) { + // r.diagramWidget.at.Move(fyne.Position{X: 100, Y: 100}) +} + +func (r *diagramWidgetRenderer) ApplyTheme(size fyne.Size) { +} + +func (r *diagramWidgetRenderer) BackgroundColor() color.Color { + return theme.BackgroundColor() +} + +func (r *diagramWidgetRenderer) Destroy() { +} + +func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { + obj := make([]fyne.CanvasObject, 0) + for _, n := range r.diagramWidget.Nodes { + obj = append(obj, n) + } + for _, e := range r.diagramWidget.Links { + obj = append(obj, e) + } + obj = append(obj, r.diagramWidget.DummyBox) + return obj +} + +func (r *diagramWidgetRenderer) Refresh() { + for _, e := range r.diagramWidget.Links { + e.Refresh() + } + for _, n := range r.diagramWidget.Nodes { + n.Refresh() + } +} diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 20fcb78f..0ee59306 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -2,6 +2,7 @@ package diagramwidget import ( "image/color" + "log" "math" "fyne.io/x/fyne/widget/diagramwidget/decoration" @@ -9,162 +10,234 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) +var hoverable desktop.Hoverable + type DiagramLink struct { widget.BaseWidget - Diagram *DiagramWidget - LinkColor color.Color - Width float32 - Origin *DiagramNode - Target *DiagramNode - SourceDecorations []decoration.Decoration - TargetDecorations []decoration.Decoration - MidpointDecorations []decoration.Decoration - // Directed bool + Diagram *DiagramWidget + LinkColor color.Color + Width float32 + Origin *DiagramNode + sourcePoint r2.Vec2 + Target *DiagramNode + targetPoint r2.Vec2 + midPoint r2.Vec2 + SourceDecorations []decoration.Decoration + sourceAnchoredText map[string]*AnchoredText + TargetDecorations []decoration.Decoration + targetAnchoredText map[string]*AnchoredText + MidpointDecorations []decoration.Decoration + midpointAnchoredText map[string]*AnchoredText } func NewDiagramLink(g *DiagramWidget, v, u *DiagramNode) *DiagramLink { - e := &DiagramLink{ - Diagram: g, - LinkColor: theme.TextColor(), - Width: 2, - Origin: v, - Target: u, - // Directed: false, + dl := &DiagramLink{ + Diagram: g, + LinkColor: theme.TextColor(), + Width: 2, + Origin: v, + Target: u, + sourceAnchoredText: make(map[string]*AnchoredText), + midpointAnchoredText: make(map[string]*AnchoredText), + targetAnchoredText: make(map[string]*AnchoredText), } - e.ExtendBaseWidget(e) + dl.ExtendBaseWidget(dl) + + hoverable = dl + return dl +} + +func (dl *DiagramLink) AddSourceAnchoredText(key string, displayedText string) { + at := NewAnchoredText(displayedText) + dl.sourceAnchoredText[key] = at + at.Move(fyne.Position{X: float32(dl.sourcePoint.X), Y: float32(dl.sourcePoint.Y)}) +} + +func (dl *DiagramLink) AddMidpointAnchoredText(key string, displayedText string) { + at := NewAnchoredText(displayedText) + dl.midpointAnchoredText[key] = at + at.Move(fyne.Position{X: float32(dl.midPoint.X), Y: float32(dl.midPoint.Y)}) +} - return e +func (dl *DiagramLink) AddTargetAnchoredText(key string, displayedText string) { + at := NewAnchoredText(displayedText) + dl.targetAnchoredText[key] = at + at.Move(fyne.Position{X: float32(dl.targetPoint.X), Y: float32(dl.targetPoint.Y)}) } -func (e *DiagramLink) CreateRenderer() fyne.WidgetRenderer { - r := diagramLinkRenderer{ - edge: e, - line: canvas.NewLine(e.LinkColor), - // arrow: arrowhead.MakeArrowhead(fyne.Position{X: 0, Y: 0}, fyne.Position{X: 0, Y: 0}), +func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { + dlr := diagramLinkRenderer{ + link: dl, + line: canvas.NewLine(dl.LinkColor), } - (&r).Refresh() + (&dlr).Refresh() + + return &dlr +} + +func (dl *DiagramLink) R2Line() r2.Line { + return r2.MakeLineFromEndpoints(dl.Origin.R2Center(), dl.Target.R2Center()) +} + +func (dl *DiagramLink) MouseIn(event *desktop.MouseEvent) { + log.Printf("MouseIn DiagramLink Text %p", dl) +} + +func (dl *DiagramLink) MouseMoved(event *desktop.MouseEvent) { - return &r } -func (e *DiagramLink) R2Line() r2.Line { - return r2.MakeLineFromEndpoints(e.Origin.R2Center(), e.Target.R2Center()) +func (dl *DiagramLink) MouseOut() { + log.Printf("MouseOut DiagramLink Text %p", dl) } type diagramLinkRenderer struct { - edge *DiagramLink + link *DiagramLink line *canvas.Line - // arrow *arrowhead.Arrowhead } -func (r *diagramLinkRenderer) MinSize() fyne.Size { - xdelta := r.edge.Origin.Position().X - r.edge.Target.Position().X +func (dlr *diagramLinkRenderer) MinSize() fyne.Size { + xdelta := dlr.link.Origin.Position().X - dlr.link.Target.Position().X if xdelta < 0 { xdelta *= -1 } - ydelta := r.edge.Origin.Position().Y - r.edge.Target.Position().Y + ydelta := dlr.link.Origin.Position().Y - dlr.link.Target.Position().Y if ydelta < 0 { ydelta *= -1 } - return fyne.Size{Width: xdelta, Height: ydelta} } -func (r *diagramLinkRenderer) Layout(size fyne.Size) { - l := r.edge.R2Line() - sourceBox := r.edge.Origin.R2Box() - targetBox := r.edge.Target.R2Box() - sourcePoint, _ := sourceBox.Intersect(l) - targetPoint, _ := targetBox.Intersect(l) - r.line.Position1 = fyne.Position{ - X: float32(sourcePoint.X), - Y: float32(sourcePoint.Y), +func (dlr *diagramLinkRenderer) Layout(size fyne.Size) { + l := dlr.link.R2Line() + sourceBox := dlr.link.Origin.R2Box() + targetBox := dlr.link.Target.R2Box() + dlr.link.sourcePoint, _ = sourceBox.Intersect(l) + dlr.link.targetPoint, _ = targetBox.Intersect(l) + dlr.line.Position1 = fyne.Position{ + X: float32(dlr.link.sourcePoint.X), + Y: float32(dlr.link.sourcePoint.Y), } - r.line.Position2 = fyne.Position{ - X: float32(targetPoint.X), - Y: float32(targetPoint.Y), + dlr.line.Position2 = fyne.Position{ + X: float32(dlr.link.targetPoint.X), + Y: float32(dlr.link.targetPoint.Y), } - r.line.StrokeColor = r.edge.LinkColor - r.line.StrokeWidth = r.edge.Width - canvas.Refresh(r.line) + dlr.line.StrokeColor = dlr.link.LinkColor + dlr.line.StrokeWidth = dlr.link.Width + canvas.Refresh(dlr.line) // Have to change the sign of Y since the window inverts the Y axis - lineVector := r2.Vec2{X: float64(r.line.Position2.X - r.line.Position1.X), Y: -float64(r.line.Position2.Y - r.line.Position1.Y)} + lineVector := r2.Vec2{X: float64(dlr.line.Position2.X - dlr.line.Position1.X), Y: -float64(dlr.line.Position2.Y - dlr.line.Position1.Y)} sourceAngle := lineVector.Angle() targetAngle := r2.AddAngles(sourceAngle, math.Pi) sourceOffset := 0.0 - for _, decoration := range r.edge.SourceDecorations { + for _, decoration := range dlr.link.SourceDecorations { decorationReferencePoint := fyne.Position{ - X: float32(float64(r.line.Position1.X) + math.Cos(sourceAngle)*sourceOffset), - Y: float32(float64(r.line.Position1.Y) - math.Sin(sourceAngle)*sourceOffset), + X: float32(float64(dlr.line.Position1.X) + math.Cos(sourceAngle)*sourceOffset), + Y: float32(float64(dlr.line.Position1.Y) - math.Sin(sourceAngle)*sourceOffset), } - decoration.SetReferencePoint(decorationReferencePoint) + decoration.Move(decorationReferencePoint) decoration.SetReferenceAngle(sourceAngle) sourceOffset = sourceOffset + float64(decoration.GetReferenceLength()) } - midPosition := fyne.Position{ - X: float32((sourcePoint.X + targetPoint.X) / 2), - Y: float32((sourcePoint.Y + targetPoint.Y) / 2), + dlr.link.midPoint = r2.Vec2{ + X: float64((dlr.link.sourcePoint.X + dlr.link.targetPoint.X) / 2), + Y: float64((dlr.link.sourcePoint.Y + dlr.link.targetPoint.Y) / 2), } midOffset := 0.0 - for _, decoration := range r.edge.MidpointDecorations { + for _, decoration := range dlr.link.MidpointDecorations { decorationReferencePoint := fyne.Position{ - X: float32(float64(midPosition.X) + math.Cos(targetAngle)*midOffset), - Y: float32(float64(midPosition.Y) - math.Sin(targetAngle)*midOffset), + X: float32(float64(dlr.link.midPoint.X) + math.Cos(targetAngle)*midOffset), + Y: float32(float64(dlr.link.midPoint.Y) - math.Sin(targetAngle)*midOffset), } - decoration.SetReferencePoint(decorationReferencePoint) + decoration.Move(decorationReferencePoint) decoration.SetReferenceAngle(targetAngle) midOffset = midOffset + float64(decoration.GetReferenceLength()) } targetOffset := 0.0 - for _, decoration := range r.edge.TargetDecorations { + for _, decoration := range dlr.link.TargetDecorations { decorationReferencePoint := fyne.Position{ - X: float32(float64(r.line.Position2.X) + math.Cos(targetAngle)*targetOffset), - Y: float32(float64(r.line.Position2.Y) - math.Sin(targetAngle)*targetOffset), + X: float32(float64(dlr.line.Position2.X) + math.Cos(targetAngle)*targetOffset), + Y: float32(float64(dlr.line.Position2.Y) - math.Sin(targetAngle)*targetOffset), } - decoration.SetReferencePoint(decorationReferencePoint) + decoration.Move(decorationReferencePoint) decoration.SetReferenceAngle(targetAngle) targetOffset = targetOffset + float64(decoration.GetReferenceLength()) } + for _, anchoredText := range dlr.link.sourceAnchoredText { + anchoredText.SetReferencePosition(fyne.Position{X: float32(dlr.link.sourcePoint.X), Y: float32(dlr.link.sourcePoint.Y)}) + } + for _, anchoredText := range dlr.link.midpointAnchoredText { + anchoredText.SetReferencePosition(fyne.Position{X: float32(dlr.link.midPoint.X), Y: float32(dlr.link.midPoint.Y)}) + } + for _, anchoredText := range dlr.link.targetAnchoredText { + anchoredText.SetReferencePosition(fyne.Position{X: float32(dlr.link.targetPoint.X), Y: float32(dlr.link.targetPoint.Y)}) + } } -func (r *diagramLinkRenderer) ApplyTheme(size fyne.Size) { +func (dlr *diagramLinkRenderer) ApplyTheme(size fyne.Size) { } -func (r *diagramLinkRenderer) Refresh() { - for _, decoration := range r.edge.SourceDecorations { - decoration.SetStrokeColor(r.edge.LinkColor) - decoration.SetStrokeWidth(r.edge.Width) +func (dlr *diagramLinkRenderer) Refresh() { + for _, decoration := range dlr.link.SourceDecorations { + decoration.SetStrokeColor(dlr.link.LinkColor) + decoration.SetStrokeWidth(dlr.link.Width) decoration.Refresh() } - for _, decoration := range r.edge.MidpointDecorations { - decoration.SetStrokeColor(r.edge.LinkColor) - decoration.SetStrokeWidth(r.edge.Width) + for _, decoration := range dlr.link.MidpointDecorations { + decoration.SetStrokeColor(dlr.link.LinkColor) + decoration.SetStrokeWidth(dlr.link.Width) decoration.Refresh() } - for _, decoration := range r.edge.TargetDecorations { - decoration.SetStrokeColor(r.edge.LinkColor) - decoration.SetStrokeWidth(r.edge.Width) + for _, decoration := range dlr.link.TargetDecorations { + decoration.SetStrokeColor(dlr.link.LinkColor) + decoration.SetStrokeWidth(dlr.link.Width) decoration.Refresh() } } -func (r *diagramLinkRenderer) BackgroundColor() color.Color { +func (dlr *diagramLinkRenderer) BackgroundColor() color.Color { return theme.BackgroundColor() } -func (r *diagramLinkRenderer) Destroy() { +func (dlr *diagramLinkRenderer) Destroy() { } -func (r *diagramLinkRenderer) Objects() []fyne.CanvasObject { +func (dlr *diagramLinkRenderer) Objects() []fyne.CanvasObject { obj := []fyne.CanvasObject{ - r.line, + dlr.line, + } + for _, sourceDecoration := range dlr.link.SourceDecorations { + if sourceDecoration != nil { + obj = append(obj, sourceDecoration) + } + } + for _, sourceAnchoredText := range dlr.link.sourceAnchoredText { + obj = append(obj, sourceAnchoredText) } + for _, midpointDecoration := range dlr.link.MidpointDecorations { + if midpointDecoration != nil { + obj = append(obj, midpointDecoration) + } + } + for _, midpointAnchoredText := range dlr.link.midpointAnchoredText { + obj = append(obj, midpointAnchoredText) + } + for _, targetDecoration := range dlr.link.TargetDecorations { + if targetDecoration != nil { + obj = append(obj, targetDecoration) + } + } + for _, targetAnchoredText := range dlr.link.targetAnchoredText { + obj = append(obj, targetAnchoredText) + } + return obj } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 5106c33e..71c39bc8 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -21,122 +21,53 @@ const ( defaultPadding float32 = 10 ) -type diagramNodeRenderer struct { - node *DiagramNode - handle *canvas.Line - box *canvas.Rectangle -} - // DiagramNode represents a node in the diagram widget. It contains an inner // widget, and also draws a border, and a "handle" that can be used to drag it // around. type DiagramNode struct { widget.BaseWidget - Diagram *DiagramWidget - // InnerSize stores size that the inner object should have, may not // be respected if not large enough for the object. InnerSize fyne.Size - // InnerObject is the canvas object that should be drawn inside of // the diagram node. InnerObject fyne.CanvasObject - // Padding is the distance between the inner object's drawing area // and the box. Padding float32 - // BoxStrokeWidth is the stroke width of the box which delineates the // node. Defaults to 1. BoxStrokeWidth float32 - // BoxFill is the fill color of the node, the inner object will be // drawn on top of this. Defaults to the DiagramTheme's BackgroundColor. BoxFillColor color.Color - // BoxStrokeColor is the stroke color of the node rectangle. Defaults // to DiagramTheme's ForegroundColor BoxStrokeColor color.Color - // HandleColor is the color of node handle. HandleColor color.Color - // HandleStrokeWidth is the stroke width of the node handle, defaults // to 3. HandleStroke float32 } -func (r *diagramNodeRenderer) MinSize() fyne.Size { - // space for the inner widget, plus padding on all sides. - inner := r.node.effectiveInnerSize() - return fyne.Size{ - Width: inner.Width + float32(2*r.node.Padding), - Height: inner.Height + float32(2*r.node.Padding), - } -} - -func (r *diagramNodeRenderer) Layout(size fyne.Size) { - r.node.Resize(r.MinSize()) - - r.node.InnerObject.Move(r.node.innerPos()) - r.node.InnerObject.Resize(r.node.effectiveInnerSize()) - - r.box.Resize(r.MinSize()) - - canvas.Refresh(r.node.InnerObject) -} - -func (r *diagramNodeRenderer) ApplyTheme(size fyne.Size) { -} - -func (r *diagramNodeRenderer) Refresh() { - // move and size the inner object appropriately - r.node.InnerObject.Move(r.node.innerPos()) - r.node.InnerObject.Resize(r.node.effectiveInnerSize()) - - // move the box and update it's colors - r.box.StrokeWidth = r.node.BoxStrokeWidth - r.box.FillColor = r.node.BoxFillColor - r.box.StrokeColor = r.node.BoxStrokeColor - r.box.Resize(r.MinSize()) - - // calculate the handle positions - r.handle.Position1 = fyne.Position{ - X: float32(r.node.Padding), - Y: float32(r.node.Padding / 2), - } - - r.handle.Position2 = fyne.Position{ - X: r.node.effectiveInnerSize().Width + float32(r.node.Padding), - Y: float32(r.node.Padding / 2), - } - - r.handle.StrokeWidth = r.node.HandleStroke - r.handle.StrokeColor = r.node.HandleColor - - for _, e := range r.node.Diagram.GetEdges(r.node) { - e.Refresh() +func NewDiagramNode(d *DiagramWidget, obj fyne.CanvasObject) *DiagramNode { + w := &DiagramNode{ + Diagram: d, + InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, + InnerObject: obj, + Padding: d.DiagramTheme.Size(theme.SizeNamePadding), + BoxStrokeWidth: 1, + BoxFillColor: d.DiagramTheme.Color(theme.ColorNameBackground, d.ThemeVariant), + BoxStrokeColor: d.DiagramTheme.Color(theme.ColorNameForeground, d.ThemeVariant), + HandleColor: d.DiagramTheme.Color(theme.ColorNameForeground, d.ThemeVariant), + HandleStroke: 3, } - canvas.Refresh(r.box) - canvas.Refresh(r.handle) - canvas.Refresh(r.node.InnerObject) -} - -func (r *diagramNodeRenderer) BackgroundColor() color.Color { - return r.node.Diagram.DiagramTheme.Color(theme.ColorNameBackground, r.node.Diagram.ThemeVariant) -} - -func (r *diagramNodeRenderer) Destroy() { -} + w.ExtendBaseWidget(w) -func (r *diagramNodeRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{ - r.box, - r.handle, - r.node.InnerObject, - } + return w } func (n *DiagramNode) CreateRenderer() fyne.WidgetRenderer { @@ -155,24 +86,6 @@ func (n *DiagramNode) CreateRenderer() fyne.WidgetRenderer { return &r } -func NewDiagramNode(d *DiagramWidget, obj fyne.CanvasObject) *DiagramNode { - w := &DiagramNode{ - Diagram: d, - InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, - InnerObject: obj, - Padding: d.DiagramTheme.Size(theme.SizeNamePadding), - BoxStrokeWidth: 1, - BoxFillColor: d.DiagramTheme.Color(theme.ColorNameBackground, d.ThemeVariant), - BoxStrokeColor: d.DiagramTheme.Color(theme.ColorNameForeground, d.ThemeVariant), - HandleColor: d.DiagramTheme.Color(theme.ColorNameForeground, d.ThemeVariant), - HandleStroke: 3, - } - - w.ExtendBaseWidget(w) - - return w -} - func (n *DiagramNode) innerPos() fyne.Position { return fyne.Position{ X: float32(n.Padding), @@ -188,33 +101,32 @@ func (n *DiagramNode) Cursor() desktop.Cursor { return desktop.DefaultCursor } +func (n *DiagramNode) Displace(delta fyne.Position) { + n.Move(n.Position().Add(delta)) +} + func (n *DiagramNode) DragEnd() { - n.Refresh() } func (n *DiagramNode) Dragged(event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} n.Displace(delta) - n.Refresh() + ForceRefresh() } func (n *DiagramNode) MouseIn(event *desktop.MouseEvent) { n.HandleColor = n.Diagram.DiagramTheme.Color(theme.ColorNameFocus, n.Diagram.ThemeVariant) - n.Refresh() + ForceRefresh() } func (n *DiagramNode) MouseOut() { n.HandleColor = n.Diagram.DiagramTheme.Color(theme.ColorNameForeground, n.Diagram.ThemeVariant) - n.Refresh() + ForceRefresh() } func (n *DiagramNode) MouseMoved(event *desktop.MouseEvent) { } -func (n *DiagramNode) Displace(delta fyne.Position) { - n.Move(n.Position().Add(delta)) -} - func (n *DiagramNode) R2Position() r2.Vec2 { return r2.V2(float64(n.Position().X), float64(n.Position().Y)) } @@ -236,3 +148,69 @@ func (n *DiagramNode) R2Center() r2.Vec2 { func (n *DiagramNode) Center() fyne.Position { return fyne.Position{X: float32(n.R2Center().X), Y: float32(n.R2Center().Y)} } + +// diagramNodeRenderer +type diagramNodeRenderer struct { + node *DiagramNode + handle *canvas.Line + box *canvas.Rectangle +} + +func (r *diagramNodeRenderer) ApplyTheme(size fyne.Size) { +} + +func (r *diagramNodeRenderer) BackgroundColor() color.Color { + return r.node.Diagram.DiagramTheme.Color(theme.ColorNameBackground, r.node.Diagram.ThemeVariant) +} + +func (r *diagramNodeRenderer) Destroy() { +} + +func (r *diagramNodeRenderer) MinSize() fyne.Size { + // space for the inner widget, plus padding on all sides. + inner := r.node.effectiveInnerSize() + return fyne.Size{ + Width: inner.Width + float32(2*r.node.Padding), + Height: inner.Height + float32(2*r.node.Padding), + } +} + +func (r *diagramNodeRenderer) Layout(size fyne.Size) { + r.node.Resize(r.MinSize()) + + r.node.InnerObject.Move(r.node.innerPos()) + r.node.InnerObject.Resize(r.node.effectiveInnerSize()) + + r.box.Resize(r.MinSize()) + + // calculate the handle positions + r.handle.Position1 = fyne.Position{ + X: float32(r.node.Padding), + Y: float32(r.node.Padding / 2), + } + + r.handle.Position2 = fyne.Position{ + X: r.node.effectiveInnerSize().Width + float32(r.node.Padding), + Y: float32(r.node.Padding / 2), + } + +} + +func (r *diagramNodeRenderer) Objects() []fyne.CanvasObject { + obj := make([]fyne.CanvasObject, 0) + obj = append(obj, r.box) + obj = append(obj, r.handle) + obj = append(obj, r.node.InnerObject) + return obj +} + +func (r *diagramNodeRenderer) Refresh() { + r.box.StrokeWidth = r.node.BoxStrokeWidth + r.box.FillColor = r.node.BoxFillColor + r.box.StrokeColor = r.node.BoxStrokeColor + r.handle.StrokeWidth = r.node.HandleStroke + r.handle.StrokeColor = r.node.HandleColor + for _, e := range r.node.Diagram.GetEdges(r.node) { + e.Refresh() + } +} From 3e632404ee823eb729cbb14eeba5601f56d9fcb2 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 23 Mar 2023 12:48:49 -0400 Subject: [PATCH 06/53] Node handles working --- cmd/diagramdemo/main.go | 14 +- .../{arrowhead => decoration}/arrowhead.go | 63 +++-- widget/diagramwidget/diagramElement.go | 19 ++ widget/diagramwidget/handle.go | 116 +++++++++ widget/diagramwidget/link.go | 13 +- widget/diagramwidget/node.go | 223 ++++++++++++------ 6 files changed, 326 insertions(+), 122 deletions(-) rename widget/diagramwidget/{arrowhead => decoration}/arrowhead.go (89%) create mode 100644 widget/diagramwidget/diagramElement.go create mode 100644 widget/diagramwidget/handle.go diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index 45ff2683..52022e69 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -6,7 +6,7 @@ import ( "time" "fyne.io/x/fyne/widget/diagramwidget" - "fyne.io/x/fyne/widget/diagramwidget/arrowhead" + "fyne.io/x/fyne/widget/diagramwidget/decoration" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" @@ -105,12 +105,12 @@ func main() { link1 := diagramwidget.NewDiagramLink(diagramWidget, node2, node1) diagramWidget.Links["link1"] = link1 link1.LinkColor = color.RGBA{255, 64, 64, 255} - link1.TargetDecorations = append(diagramWidget.Links["link1"].TargetDecorations, arrowhead.NewArrowhead()) - link1.TargetDecorations = append(diagramWidget.Links["link1"].TargetDecorations, arrowhead.NewArrowhead()) - link1.MidpointDecorations = append(diagramWidget.Links["link1"].MidpointDecorations, arrowhead.NewArrowhead()) - link1.MidpointDecorations = append(diagramWidget.Links["link1"].MidpointDecorations, arrowhead.NewArrowhead()) - link1.SourceDecorations = append(diagramWidget.Links["link1"].SourceDecorations, arrowhead.NewArrowhead()) - link1.SourceDecorations = append(diagramWidget.Links["link1"].SourceDecorations, arrowhead.NewArrowhead()) + link1.TargetDecorations = append(diagramWidget.Links["link1"].TargetDecorations, decoration.NewArrowhead()) + link1.TargetDecorations = append(diagramWidget.Links["link1"].TargetDecorations, decoration.NewArrowhead()) + link1.MidpointDecorations = append(diagramWidget.Links["link1"].MidpointDecorations, decoration.NewArrowhead()) + link1.MidpointDecorations = append(diagramWidget.Links["link1"].MidpointDecorations, decoration.NewArrowhead()) + link1.SourceDecorations = append(diagramWidget.Links["link1"].SourceDecorations, decoration.NewArrowhead()) + link1.SourceDecorations = append(diagramWidget.Links["link1"].SourceDecorations, decoration.NewArrowhead()) diagramWidget.Links["link2"] = diagramwidget.NewDiagramLink(diagramWidget, node0, node3) diff --git a/widget/diagramwidget/arrowhead/arrowhead.go b/widget/diagramwidget/decoration/arrowhead.go similarity index 89% rename from widget/diagramwidget/arrowhead/arrowhead.go rename to widget/diagramwidget/decoration/arrowhead.go index 515f10a6..b55bb8e3 100644 --- a/widget/diagramwidget/arrowhead/arrowhead.go +++ b/widget/diagramwidget/decoration/arrowhead.go @@ -1,5 +1,4 @@ -// Package arrowhead implements an arrowhead canvas object. -package arrowhead +package decoration import ( "image/color" @@ -69,9 +68,9 @@ func NewArrowhead() *Arrowhead { func (a *Arrowhead) CreateRenderer() fyne.WidgetRenderer { ar := arrowheadRenderer{ - widget: a, - left: canvas.NewLine(theme.ForegroundColor()), - right: canvas.NewLine(theme.ForegroundColor()), + arrowhead: a, + left: canvas.NewLine(theme.ForegroundColor()), + right: canvas.NewLine(theme.ForegroundColor()), } return &ar } @@ -145,31 +144,46 @@ func (a *Arrowhead) Size() fyne.Size { } type arrowheadRenderer struct { - widget *Arrowhead - left *canvas.Line - right *canvas.Line + arrowhead *Arrowhead + left *canvas.Line + right *canvas.Line +} + +func (ar *arrowheadRenderer) ApplyTheme(size fyne.Size) { +} + +func (ar *arrowheadRenderer) BackgroundColor() color.Color { + return theme.BackgroundColor() +} + +func (ar *arrowheadRenderer) Destroy() { } func (ar *arrowheadRenderer) MinSize() fyne.Size { - return ar.widget.Size() + return ar.arrowhead.Size() } func (ar *arrowheadRenderer) Layout(size fyne.Size) { ar.left.Position1 = fyne.Position{X: 0, Y: 0} - ar.left.Position2 = ar.widget.LeftPoint() + ar.left.Position2 = ar.arrowhead.LeftPoint() ar.right.Position1 = fyne.Position{X: 0, Y: 0} - ar.right.Position2 = ar.widget.RightPoint() + ar.right.Position2 = ar.arrowhead.RightPoint() } -func (ar *arrowheadRenderer) ApplyTheme(size fyne.Size) { +func (ar *arrowheadRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{ + ar.left, + ar.right, + } + return obj } func (ar *arrowheadRenderer) Refresh() { - ar.left.StrokeWidth = ar.widget.StrokeWidth - ar.right.StrokeWidth = ar.widget.StrokeWidth - ar.left.StrokeColor = ar.widget.StrokeColor - ar.right.StrokeColor = ar.widget.StrokeColor - if ar.widget.visible { + ar.left.StrokeWidth = ar.arrowhead.StrokeWidth + ar.right.StrokeWidth = ar.arrowhead.StrokeWidth + ar.left.StrokeColor = ar.arrowhead.StrokeColor + ar.right.StrokeColor = ar.arrowhead.StrokeColor + if ar.arrowhead.visible { ar.left.Show() ar.right.Show() } else { @@ -177,18 +191,3 @@ func (ar *arrowheadRenderer) Refresh() { ar.right.Hide() } } - -func (ar *arrowheadRenderer) BackgroundColor() color.Color { - return theme.BackgroundColor() -} - -func (ar *arrowheadRenderer) Destroy() { -} - -func (ar *arrowheadRenderer) Objects() []fyne.CanvasObject { - obj := []fyne.CanvasObject{ - ar.left, - ar.right, - } - return obj -} diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go new file mode 100644 index 00000000..57141611 --- /dev/null +++ b/widget/diagramwidget/diagramElement.go @@ -0,0 +1,19 @@ +package diagramwidget + +import "fyne.io/fyne/v2" + +// A DiagramElement is a widget that can be placed directly in a diagram. The most common +// elements are Node and Link widgets. +type DiagramElement interface { + fyne.Widget + GetDiagram() *DiagramWidget + handleDragged(handle *Handle, event *fyne.DragEvent) +} + +type diagramElement struct { + diagram *DiagramWidget +} + +func (de *diagramElement) GetDiagram() *DiagramWidget { + return de.diagram +} diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go new file mode 100644 index 00000000..91df77c0 --- /dev/null +++ b/widget/diagramwidget/handle.go @@ -0,0 +1,116 @@ +package diagramwidget + +import ( + "image/color" + "log" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +// draggable is used to verify that objects implement the Draggable interface. An assignment to +// draggable will fail at compile time if the interface is not fully implemented. +var draggable fyne.Draggable +var hoverable desktop.Hoverable + +var defaultHandleSize float32 = 10.0 + +type Handle struct { + widget.BaseWidget + handleSize float32 + de DiagramElement +} + +func NewHandle(diagramElement DiagramElement) *Handle { + handle := &Handle{ + de: diagramElement, + handleSize: defaultHandleSize, + } + // This assignment verifies that handle fully implements the Draggable interface + draggable = handle + hoverable = handle + handle.BaseWidget.ExtendBaseWidget(handle) + return handle +} + +func (h *Handle) CreateRenderer() fyne.WidgetRenderer { + hr := &handleRenderer{ + handle: h, + rect: canvas.NewRectangle(h.getStrokeColor()), + } + hr.Refresh() + ForceRefresh() + return hr +} + +func (h *Handle) Dragged(event *fyne.DragEvent) { + h.de.handleDragged(h, event) + log.Print("Handle dragged") +} + +func (h *Handle) DragEnd() { + +} + +func (h *Handle) getStrokeColor() color.Color { + variant := h.de.GetDiagram().ThemeVariant + return h.de.GetDiagram().DiagramTheme.Color(theme.ColorNameForeground, variant) +} + +func (h *Handle) getStrokeWidth() float32 { + return 1.0 +} + +func (h *Handle) MouseIn(*desktop.MouseEvent) { + log.Print("Mouse in handle") +} + +// MouseMoved is a hook that is called if the mouse pointer moved over the element. +func (h *Handle) MouseMoved(*desktop.MouseEvent) { + log.Print("Mouse moved in handle") +} + +// MouseOut is a hook that is called if the mouse pointer leaves the element. +func (h *Handle) MouseOut() { + log.Print("Mouse out of handle") +} + +func (h *Handle) Move(position fyne.Position) { + delta := fyne.Position{X: -h.handleSize / 2, Y: -h.handleSize / 2} + h.BaseWidget.Move(position.Add(delta)) +} + +// handleRenderer +type handleRenderer struct { + handle *Handle + rect *canvas.Rectangle +} + +func (hr *handleRenderer) Destroy() { + +} + +func (hr *handleRenderer) MinSize() fyne.Size { + return fyne.Size{Height: hr.handle.handleSize, Width: hr.handle.handleSize} +} + +func (hr *handleRenderer) Layout(size fyne.Size) { + hr.rect.Resize(hr.MinSize()) + hr.handle.Resize(hr.MinSize()) +} + +func (hr *handleRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{ + hr.rect, + } + return obj +} + +func (hr *handleRenderer) Refresh() { + hr.rect.StrokeColor = hr.handle.getStrokeColor() + hr.rect.StrokeWidth = hr.handle.getStrokeWidth() + ForceRefresh() +} diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 0ee59306..c9878935 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -15,11 +15,13 @@ import ( "fyne.io/fyne/v2/widget" ) -var hoverable desktop.Hoverable +// hoverable used during testing to determine whether DiagramLink fully implements the Hoverable interface. +// Assign an instance of DiagramLink to hoverable: if you don't get a compiler error, it is fully implemented +// var hoverable desktop.Hoverable type DiagramLink struct { widget.BaseWidget - Diagram *DiagramWidget + diagramElement LinkColor color.Color Width float32 Origin *DiagramNode @@ -35,9 +37,8 @@ type DiagramLink struct { midpointAnchoredText map[string]*AnchoredText } -func NewDiagramLink(g *DiagramWidget, v, u *DiagramNode) *DiagramLink { +func NewDiagramLink(diagram *DiagramWidget, v, u *DiagramNode) *DiagramLink { dl := &DiagramLink{ - Diagram: g, LinkColor: theme.TextColor(), Width: 2, Origin: v, @@ -46,10 +47,12 @@ func NewDiagramLink(g *DiagramWidget, v, u *DiagramNode) *DiagramLink { midpointAnchoredText: make(map[string]*AnchoredText), targetAnchoredText: make(map[string]*AnchoredText), } + dl.diagramElement.diagram = diagram dl.ExtendBaseWidget(dl) - hoverable = dl + // Use during testing to ensure that the instance fully implements Hoverable + // hoverable = dl return dl } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 71c39bc8..5d3ae783 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -12,6 +12,9 @@ import ( "fyne.io/fyne/v2/widget" ) +// Validate that DiagramNode is a DiagramElement +var _ DiagramElement = (*DiagramNode)(nil) + const ( // default inner size defaultWidth float32 = 50 @@ -26,7 +29,7 @@ const ( // around. type DiagramNode struct { widget.BaseWidget - Diagram *DiagramWidget + diagramElement // InnerSize stores size that the inner object should have, may not // be respected if not large enough for the object. InnerSize fyne.Size @@ -50,117 +53,168 @@ type DiagramNode struct { // HandleStrokeWidth is the stroke width of the node handle, defaults // to 3. HandleStroke float32 + handles map[string]*Handle } -func NewDiagramNode(d *DiagramWidget, obj fyne.CanvasObject) *DiagramNode { - w := &DiagramNode{ - Diagram: d, +func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject) *DiagramNode { + dn := &DiagramNode{ InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, InnerObject: obj, - Padding: d.DiagramTheme.Size(theme.SizeNamePadding), + Padding: diagram.DiagramTheme.Size(theme.SizeNamePadding), BoxStrokeWidth: 1, - BoxFillColor: d.DiagramTheme.Color(theme.ColorNameBackground, d.ThemeVariant), - BoxStrokeColor: d.DiagramTheme.Color(theme.ColorNameForeground, d.ThemeVariant), - HandleColor: d.DiagramTheme.Color(theme.ColorNameForeground, d.ThemeVariant), + BoxFillColor: diagram.DiagramTheme.Color(theme.ColorNameBackground, diagram.ThemeVariant), + BoxStrokeColor: diagram.DiagramTheme.Color(theme.ColorNameForeground, diagram.ThemeVariant), + HandleColor: diagram.DiagramTheme.Color(theme.ColorNameForeground, diagram.ThemeVariant), HandleStroke: 3, + handles: make(map[string]*Handle), } - - w.ExtendBaseWidget(w) - - return w + dn.diagramElement.diagram = diagram + for _, handleKey := range []string{"upperLeft", "upperMiddle", "upperRight", "leftMiddle", "rightMiddle", "lowerLeft", "lowerMiddle", "lowerRight"} { + newHandle := NewHandle(dn) + dn.handles[handleKey] = newHandle + } + dn.ExtendBaseWidget(dn) + return dn } -func (n *DiagramNode) CreateRenderer() fyne.WidgetRenderer { - r := diagramNodeRenderer{ - node: n, - handle: canvas.NewLine(n.HandleColor), - box: canvas.NewRectangle(n.BoxStrokeColor), +func (dn *DiagramNode) CreateRenderer() fyne.WidgetRenderer { + dnr := diagramNodeRenderer{ + node: dn, + box: canvas.NewRectangle(dn.BoxStrokeColor), } - r.handle.StrokeWidth = n.HandleStroke - r.box.StrokeWidth = n.BoxStrokeWidth - r.box.FillColor = n.BoxFillColor + dnr.box.StrokeWidth = dn.BoxStrokeWidth + dnr.box.FillColor = dn.BoxFillColor - (&r).Refresh() + (&dnr).Refresh() - return &r + return &dnr } -func (n *DiagramNode) innerPos() fyne.Position { - return fyne.Position{ - X: float32(n.Padding), - Y: float32(n.Padding), - } +func (dn *DiagramNode) Cursor() desktop.Cursor { + return desktop.DefaultCursor } -func (n *DiagramNode) effectiveInnerSize() fyne.Size { - return n.InnerSize.Max(n.InnerObject.MinSize()) +func (dn *DiagramNode) Displace(delta fyne.Position) { + dn.Move(dn.Position().Add(delta)) } -func (n *DiagramNode) Cursor() desktop.Cursor { - return desktop.DefaultCursor +func (dn *DiagramNode) DragEnd() { } -func (n *DiagramNode) Displace(delta fyne.Position) { - n.Move(n.Position().Add(delta)) +func (dn *DiagramNode) Dragged(event *fyne.DragEvent) { + delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} + dn.Displace(delta) + ForceRefresh() } -func (n *DiagramNode) DragEnd() { +func (dn *DiagramNode) effectiveInnerSize() fyne.Size { + return dn.InnerSize.Max(dn.InnerObject.MinSize()) } -func (n *DiagramNode) Dragged(event *fyne.DragEvent) { - delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} - n.Displace(delta) +func (dn *DiagramNode) findKeyForHandle(handle *Handle) string { + for k, v := range dn.handles { + if v == handle { + return k + } + } + return "" +} + +func (dn *DiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) { + // determine which handle it is + handleKey := dn.findKeyForHandle(handle) + positionChange := fyne.Position{X: 0, Y: 0} + sizeChange := fyne.Size{Height: 0, Width: 0} + switch handleKey { + case "upperLeft": + positionChange.X = event.Dragged.DX + positionChange.Y = event.Dragged.DY + sizeChange.Height = -event.Dragged.DY + sizeChange.Width = -event.Dragged.DY + case "upperMiddle": + positionChange.Y = event.Dragged.DY + sizeChange.Height = -event.Dragged.DY + case "upperRight": + positionChange.Y = event.Dragged.DY + sizeChange.Height = -event.Dragged.DY + sizeChange.Width = event.Dragged.DX + case "leftMiddle": + positionChange.X = event.Dragged.DX + sizeChange.Width = -event.Dragged.DX + case "rightMiddle": + sizeChange.Width = event.Dragged.DX + case "lowerLeft": + positionChange.X = event.Dragged.DX + sizeChange.Height = event.Dragged.DY + sizeChange.Width = -event.Dragged.DX + case "lowerMiddle": + sizeChange.Height = event.Dragged.DY + case "lowerRight": + sizeChange.Height = event.Dragged.DY + sizeChange.Width = event.Dragged.DX + } + dn.Move(dn.Position().Add(positionChange)) + trialInnerSize := dn.InnerSize.Add(sizeChange) + dn.InnerSize = dn.InnerObject.MinSize().Max(trialInnerSize) + dn.Resize(dn.Size().Add(sizeChange)) + dn.Refresh() ForceRefresh() } -func (n *DiagramNode) MouseIn(event *desktop.MouseEvent) { - n.HandleColor = n.Diagram.DiagramTheme.Color(theme.ColorNameFocus, n.Diagram.ThemeVariant) +func (dn *DiagramNode) innerPos() fyne.Position { + return fyne.Position{ + X: float32(dn.Padding), + Y: float32(dn.Padding), + } +} + +func (dn *DiagramNode) MouseIn(event *desktop.MouseEvent) { + dn.HandleColor = dn.diagram.DiagramTheme.Color(theme.ColorNameFocus, dn.diagram.ThemeVariant) ForceRefresh() } -func (n *DiagramNode) MouseOut() { - n.HandleColor = n.Diagram.DiagramTheme.Color(theme.ColorNameForeground, n.Diagram.ThemeVariant) +func (dn *DiagramNode) MouseOut() { + dn.HandleColor = dn.diagram.DiagramTheme.Color(theme.ColorNameForeground, dn.diagram.ThemeVariant) ForceRefresh() } -func (n *DiagramNode) MouseMoved(event *desktop.MouseEvent) { +func (dn *DiagramNode) MouseMoved(event *desktop.MouseEvent) { } -func (n *DiagramNode) R2Position() r2.Vec2 { - return r2.V2(float64(n.Position().X), float64(n.Position().Y)) +func (dn *DiagramNode) R2Position() r2.Vec2 { + return r2.V2(float64(dn.Position().X), float64(dn.Position().Y)) } -func (n *DiagramNode) R2Box() r2.Box { - inner := n.effectiveInnerSize() +func (dn *DiagramNode) R2Box() r2.Box { + inner := dn.effectiveInnerSize() s := r2.V2( - float64(inner.Width+float32(2*n.Padding)), - float64(inner.Height+float32(2*n.Padding)), + float64(inner.Width+float32(2*dn.Padding)), + float64(inner.Height+float32(2*dn.Padding)), ) - return r2.MakeBox(n.R2Position(), s) + return r2.MakeBox(dn.R2Position(), s) } -func (n *DiagramNode) R2Center() r2.Vec2 { - return n.R2Box().Center() +func (dn *DiagramNode) R2Center() r2.Vec2 { + return dn.R2Box().Center() } -func (n *DiagramNode) Center() fyne.Position { - return fyne.Position{X: float32(n.R2Center().X), Y: float32(n.R2Center().Y)} +func (dn *DiagramNode) Center() fyne.Position { + return fyne.Position{X: float32(dn.R2Center().X), Y: float32(dn.R2Center().Y)} } // diagramNodeRenderer type diagramNodeRenderer struct { - node *DiagramNode - handle *canvas.Line - box *canvas.Rectangle + node *DiagramNode + box *canvas.Rectangle } func (r *diagramNodeRenderer) ApplyTheme(size fyne.Size) { } func (r *diagramNodeRenderer) BackgroundColor() color.Color { - return r.node.Diagram.DiagramTheme.Color(theme.ColorNameBackground, r.node.Diagram.ThemeVariant) + return r.node.diagram.DiagramTheme.Color(theme.ColorNameBackground, r.node.diagram.ThemeVariant) } func (r *diagramNodeRenderer) Destroy() { @@ -175,32 +229,47 @@ func (r *diagramNodeRenderer) MinSize() fyne.Size { } } -func (r *diagramNodeRenderer) Layout(size fyne.Size) { - r.node.Resize(r.MinSize()) +func (dnr *diagramNodeRenderer) Layout(size fyne.Size) { + nodeSize := dnr.MinSize().Max(size) + dnr.node.Resize(nodeSize) - r.node.InnerObject.Move(r.node.innerPos()) - r.node.InnerObject.Resize(r.node.effectiveInnerSize()) + dnr.node.InnerObject.Move(dnr.node.innerPos()) + dnr.node.InnerObject.Resize(dnr.node.effectiveInnerSize()) - r.box.Resize(r.MinSize()) + dnr.box.Resize(nodeSize) // calculate the handle positions - r.handle.Position1 = fyne.Position{ - X: float32(r.node.Padding), - Y: float32(r.node.Padding / 2), - } - - r.handle.Position2 = fyne.Position{ - X: r.node.effectiveInnerSize().Width + float32(r.node.Padding), - Y: float32(r.node.Padding / 2), + width := nodeSize.Width + height := nodeSize.Height + for key, handle := range dnr.node.handles { + switch key { + case "upperLeft": + handle.Move(fyne.NewPos(0, 0)) + case "upperMiddle": + handle.Move(fyne.Position{X: width / 2, Y: 0}) + case "upperRight": + handle.Move(fyne.Position{X: width, Y: 0}) + case "leftMiddle": + handle.Move(fyne.Position{X: 0, Y: height / 2}) + case "rightMiddle": + handle.Move(fyne.Position{X: width, Y: height / 2}) + case "lowerLeft": + handle.Move(fyne.Position{X: 0, Y: height}) + case "lowerMiddle": + handle.Move(fyne.Position{X: width / 2, Y: height}) + case "lowerRight": + handle.Move(fyne.Position{X: width, Y: height}) + } } - } -func (r *diagramNodeRenderer) Objects() []fyne.CanvasObject { +func (dnr *diagramNodeRenderer) Objects() []fyne.CanvasObject { obj := make([]fyne.CanvasObject, 0) - obj = append(obj, r.box) - obj = append(obj, r.handle) - obj = append(obj, r.node.InnerObject) + obj = append(obj, dnr.box) + obj = append(obj, dnr.node.InnerObject) + for _, handle := range dnr.node.handles { + obj = append(obj, handle) + } return obj } @@ -208,9 +277,7 @@ func (r *diagramNodeRenderer) Refresh() { r.box.StrokeWidth = r.node.BoxStrokeWidth r.box.FillColor = r.node.BoxFillColor r.box.StrokeColor = r.node.BoxStrokeColor - r.handle.StrokeWidth = r.node.HandleStroke - r.handle.StrokeColor = r.node.HandleColor - for _, e := range r.node.Diagram.GetEdges(r.node) { + for _, e := range r.node.diagram.GetEdges(r.node) { e.Refresh() } } From 4e99290f8c8c9507cadf687e51506f76004dcbf3 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 24 Mar 2023 15:07:35 -0400 Subject: [PATCH 07/53] Added connectionPads, identifiers --- .gitignore | 1 + cmd/diagramdemo/main.go | 51 ++++--- widget/diagramwidget/connectionpad.go | 183 +++++++++++++++++++++++++ widget/diagramwidget/diagram.go | 93 +++++++++---- widget/diagramwidget/diagramElement.go | 27 ++++ widget/diagramwidget/handle.go | 21 +-- widget/diagramwidget/link.go | 89 ++++++------ widget/diagramwidget/node.go | 62 +++++---- 8 files changed, 387 insertions(+), 140 deletions(-) create mode 100644 widget/diagramwidget/connectionpad.go diff --git a/.gitignore b/.gitignore index 9234e8e2..32c291a4 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ Session.vim tags # Persistent undo [._]*.un~ +cmd/diagramdemo/__debug_bin.exe diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index 52022e69..fb9e5dde 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -39,24 +39,22 @@ func main() { w.SetMaster() - diagramWidget := diagramwidget.NewDiagramWidget() + diagramWidget := diagramwidget.NewDiagramWidget("Diagram1") diagramwidget.Globaldiagram = diagramWidget go forceanim() // Node 0 node0Label := widget.NewLabel("Node0") - node0 := diagramwidget.NewDiagramNode(diagramWidget, node0Label) - diagramWidget.Nodes["node0"] = node0 + node0 := diagramwidget.NewDiagramNode(diagramWidget, node0Label, "Node0") // Node 1 node1Button := widget.NewButton("Node1 Button", func() { fmt.Printf("tapped Node1!\n") }) - node1 := diagramwidget.NewDiagramNode(diagramWidget, node1Button) + node1 := diagramwidget.NewDiagramNode(diagramWidget, node1Button, "Node1") node1.Move(fyne.Position{X: 200, Y: 200}) - diagramWidget.Nodes["node1"] = node1 // Node 2 - node2 := diagramwidget.NewDiagramNode(diagramWidget, nil) + node2 := diagramwidget.NewDiagramNode(diagramWidget, nil, "Node2") node2Container := container.NewVBox( widget.NewLabel("Node2 - with structure"), widget.NewButton("Up", func() { @@ -80,47 +78,46 @@ func main() { ) node2.InnerObject = node2Container node2.Move(fyne.Position{X: 300, Y: 300}) - diagramWidget.Nodes["node2"] = node2 // Node 3 node3 := diagramwidget.NewDiagramNode(diagramWidget, widget.NewButton("Node3: Force layout step", func() { diagramWidget.StepForceLayout(300) diagramWidget.Refresh() - })) + }), "Node3") node3.Move(fyne.Position{X: 400, Y: 200}) - diagramWidget.Nodes["node3"] = node3 // Node 4 node4 := diagramwidget.NewDiagramNode(diagramWidget, widget.NewButton("Node4: auto layout", func() { forceticks += 100 diagramWidget.Refresh() - })) + }), "Node4") node4.Move(fyne.Position{X: 400, Y: 500}) - diagramWidget.Nodes["node4"] = node4 - link0 := diagramwidget.NewDiagramLink(diagramWidget, node0, node1) - diagramWidget.Links["link0"] = link0 + // Link0 + link0 := diagramwidget.NewDiagramLink(diagramWidget, node0, node1, "Link0") link0.AddSourceAnchoredText("sourceRole", "sourceRole") - link1 := diagramwidget.NewDiagramLink(diagramWidget, node2, node1) - diagramWidget.Links["link1"] = link1 + // Link1 + link1 := diagramwidget.NewDiagramLink(diagramWidget, node2, node1, "Link1") link1.LinkColor = color.RGBA{255, 64, 64, 255} - link1.TargetDecorations = append(diagramWidget.Links["link1"].TargetDecorations, decoration.NewArrowhead()) - link1.TargetDecorations = append(diagramWidget.Links["link1"].TargetDecorations, decoration.NewArrowhead()) - link1.MidpointDecorations = append(diagramWidget.Links["link1"].MidpointDecorations, decoration.NewArrowhead()) - link1.MidpointDecorations = append(diagramWidget.Links["link1"].MidpointDecorations, decoration.NewArrowhead()) - link1.SourceDecorations = append(diagramWidget.Links["link1"].SourceDecorations, decoration.NewArrowhead()) - link1.SourceDecorations = append(diagramWidget.Links["link1"].SourceDecorations, decoration.NewArrowhead()) - - diagramWidget.Links["link2"] = diagramwidget.NewDiagramLink(diagramWidget, node0, node3) - - link3 := diagramwidget.NewDiagramLink(diagramWidget, node2, node3) + link1.TargetDecorations = append(link1.TargetDecorations, decoration.NewArrowhead()) + link1.TargetDecorations = append(link1.TargetDecorations, decoration.NewArrowhead()) + link1.MidpointDecorations = append(link1.MidpointDecorations, decoration.NewArrowhead()) + link1.MidpointDecorations = append(link1.MidpointDecorations, decoration.NewArrowhead()) + link1.SourceDecorations = append(link1.SourceDecorations, decoration.NewArrowhead()) + link1.SourceDecorations = append(link1.SourceDecorations, decoration.NewArrowhead()) + + // Link2 + diagramwidget.NewDiagramLink(diagramWidget, node0, node3, "Link2") + + // Link3 + link3 := diagramwidget.NewDiagramLink(diagramWidget, node2, node3, "Link3") link3.AddSourceAnchoredText("sourceRole", "sourceRole") link3.AddMidpointAnchoredText("linkName", "Link 3") link3.AddTargetAnchoredText("targetRole", "targetRole") - diagramWidget.Links["link3"] = link3 - diagramWidget.Links["link4"] = diagramwidget.NewDiagramLink(diagramWidget, node4, node3) + // Link4 + diagramwidget.NewDiagramLink(diagramWidget, node4, node3, "Link4") w.SetContent(diagramWidget) diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go new file mode 100644 index 00000000..8a8a2716 --- /dev/null +++ b/widget/diagramwidget/connectionpad.go @@ -0,0 +1,183 @@ +package diagramwidget + +import ( + "image/color" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/widget" +) + +const ( + pointPadSize float32 = 10 + padLineWidth float32 = 3 +) + +// ConnectionPad is an interface to a connection area on a DiagramElement. +type ConnectionPad interface { + fyne.Widget + desktop.Hoverable + GetPadOwner() DiagramElement +} + +type connectionPad struct { + padOwner DiagramElement +} + +func (cp *connectionPad) GetPadOwner() DiagramElement { + return cp.padOwner +} + +/****************************** + PointPad +*******************************/ + +// Validate that PointPad implements ConnectionPad +var _ ConnectionPad = (*PointPad)(nil) + +type PointPad struct { + widget.BaseWidget + connectionPad +} + +func NewPointPad(padOwner DiagramElement) *PointPad { + pp := &PointPad{} + pp.connectionPad.padOwner = padOwner + pp.BaseWidget.ExtendBaseWidget(pp) + return pp +} + +func (pp *PointPad) CreateRenderer() fyne.WidgetRenderer { + ppr := &pointPadRenderer{ + pp: pp, + l1: canvas.NewLine(pp.padOwner.GetDiagram().GetHoverColor()), + l2: canvas.NewLine(pp.padOwner.GetDiagram().GetHoverColor()), + } + ppr.l1.StrokeWidth = padLineWidth + ppr.l2.StrokeWidth = padLineWidth + return ppr +} + +func (pp *PointPad) MouseIn(event *desktop.MouseEvent) { + +} + +func (pp *PointPad) MouseMoved(event *desktop.MouseEvent) { + +} + +func (pp *PointPad) MouseOut() { + +} + +// pointPadRenderer +type pointPadRenderer struct { + pp *PointPad + l1 *canvas.Line + l2 *canvas.Line +} + +func (ppr *pointPadRenderer) Destroy() { + +} + +func (ppr *pointPadRenderer) Layout(size fyne.Size) { + ppr.l1.Position1 = fyne.NewPos(0, 0) + ppr.l1.Position2 = fyne.NewPos(pointPadSize, pointPadSize) + ppr.l2.Position1 = fyne.NewPos(pointPadSize, 0) + ppr.l2.Position2 = fyne.NewPos(0, pointPadSize) +} + +func (ppr *pointPadRenderer) MinSize() fyne.Size { + return fyne.Size{Height: pointPadSize, Width: pointPadSize} +} + +func (ppr *pointPadRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{ + ppr.l1, + ppr.l2, + } + return obj +} + +func (ppr *pointPadRenderer) Refresh() { + ppr.l1.StrokeColor = ppr.pp.padOwner.GetDiagram().GetHoverColor() + ppr.l1.StrokeWidth = padLineWidth + ppr.l2.StrokeColor = ppr.pp.padOwner.GetDiagram().GetHoverColor() + ppr.l2.StrokeWidth = padLineWidth +} + +/*********************************** + RectanglePad +*************************************/ + +// Validate that RectanglePad implements ConnectionPad +var _ ConnectionPad = (*RectanglePad)(nil) + +type RectanglePad struct { + widget.BaseWidget + connectionPad +} + +func NewRectanglePad(padOwner DiagramElement) *RectanglePad { + rp := &RectanglePad{} + rp.connectionPad.padOwner = padOwner + rp.BaseWidget.ExtendBaseWidget(rp) + return rp +} + +func (rp *RectanglePad) CreateRenderer() fyne.WidgetRenderer { + rpr := &rectanglePadRenderer{ + rp: rp, + rect: *canvas.NewRectangle(rp.padOwner.GetDiagram().GetForegroundColor()), + } + // rpr.rect.FillColor = color.Transparent + rpr.rect.StrokeWidth = padLineWidth + return rpr +} + +func (rp *RectanglePad) MouseIn(event *desktop.MouseEvent) { + +} + +func (rp *RectanglePad) MouseMoved(event *desktop.MouseEvent) { + +} + +func (rp *RectanglePad) MouseOut() { + +} + +// rectanglePadRenderer +type rectanglePadRenderer struct { + rp *RectanglePad + rect canvas.Rectangle +} + +func (rpr *rectanglePadRenderer) Destroy() { + +} + +func (rpr *rectanglePadRenderer) Layout(size fyne.Size) { + rpr.rp.Resize(rpr.rp.padOwner.Size()) + rpr.rect.Resize(rpr.rp.padOwner.Size()) +} + +func (rpr *rectanglePadRenderer) MinSize() fyne.Size { + return rpr.rp.padOwner.Size() +} + +func (rpr *rectanglePadRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{ + &rpr.rect, + } + return obj +} + +func (rpr *rectanglePadRenderer) Refresh() { + rpr.rect.StrokeColor = rpr.rp.padOwner.GetDiagram().GetForegroundColor() + rpr.rect.FillColor = color.Transparent + rpr.rect.StrokeWidth = padLineWidth + ForceRefresh() +} diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 2558d343..c863d9b1 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -16,9 +16,15 @@ func ForceRefresh() { Globaldiagram.DummyBox.Refresh() } +// Verify that interfaces are fully implemented +var _ fyne.Tappable = (*DiagramWidget)(nil) + type DiagramWidget struct { widget.BaseWidget + // ID is expected to be unique across all DiagramWidgets in the application. + ID string + // Diagrams may want to use a different theme and variant than the application. The default value is the // applicaton's theme and variant DiagramTheme fyne.Theme @@ -29,13 +35,17 @@ type DiagramWidget struct { // up, defaults to 800 x 600 DesiredSize fyne.Size - Nodes map[string]*DiagramNode - Links map[string]*DiagramLink + Nodes map[string]*DiagramNode + Links map[string]*DiagramLink + selection map[string]DiagramElement + + // TODO Remove DummyBox when fyne rendering issue is resolved DummyBox *canvas.Rectangle } -func NewDiagramWidget() *DiagramWidget { +func NewDiagramWidget(id string) *DiagramWidget { d := &DiagramWidget{ + ID: id, DiagramTheme: fyne.CurrentApp().Settings().Theme(), ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), DesiredSize: fyne.Size{Width: 800, Height: 600}, @@ -43,6 +53,7 @@ func NewDiagramWidget() *DiagramWidget { Nodes: map[string]*DiagramNode{}, Links: map[string]*DiagramLink{}, DummyBox: canvas.NewRectangle(color.Transparent), + selection: map[string]DiagramElement{}, } d.DummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) d.DummyBox.Move(fyne.Position{X: 50, Y: 50}) @@ -60,35 +71,52 @@ func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { return &r } +func (dw *DiagramWidget) addElementToSelection(de DiagramElement) { + if !dw.IsSelected(de) { + dw.selection[de.GetDiagramElementID()] = de + de.ShowHandles() + } +} + func (dw *DiagramWidget) Cursor() desktop.Cursor { return desktop.DefaultCursor } +func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.PointEvent) { + if !dw.IsSelected(de) { + dw.addElementToSelection(de) + } + ForceRefresh() +} + func (dw *DiagramWidget) DragEnd() { dw.Refresh() } -func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { - delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} - for _, n := range dw.Nodes { - n.Displace(delta) - } - dw.Refresh() +func (dw *DiagramWidget) GetBackgroundColor() color.Color { + return dw.DiagramTheme.Color(theme.ColorNameBackground, dw.ThemeVariant) } -func (dw *DiagramWidget) MouseIn(event *desktop.MouseEvent) { +func (dw *DiagramWidget) GetForegroundColor() color.Color { + return dw.DiagramTheme.Color(theme.ColorNameForeground, dw.ThemeVariant) } -func (dw *DiagramWidget) MouseOut() { +func (dw *DiagramWidget) GetHoverColor() color.Color { + return dw.DiagramTheme.Color(theme.ColorNameHover, dw.ThemeVariant) } -func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { +func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { + delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} + for _, n := range dw.Nodes { + n.Displace(delta) + } + dw.Refresh() } -func (d *DiagramWidget) GetEdges(n *DiagramNode) []*DiagramLink { +func (dw *DiagramWidget) GetEdges(n *DiagramNode) []*DiagramLink { links := []*DiagramLink{} - for _, link := range d.Links { + for _, link := range dw.Links { if link.Origin == n { links = append(links, link) } else if link.Target == n { @@ -99,28 +127,47 @@ func (d *DiagramWidget) GetEdges(n *DiagramNode) []*DiagramLink { return links } -type diagramWidgetRenderer struct { - diagramWidget *DiagramWidget +func (dw *DiagramWidget) IsSelected(de DiagramElement) bool { + return dw.selection[de.GetDiagramElementID()] != nil } -func (r *diagramWidgetRenderer) MinSize() fyne.Size { - return r.diagramWidget.DesiredSize +func (dw *DiagramWidget) MouseIn(event *desktop.MouseEvent) { } -func (r *diagramWidgetRenderer) Layout(size fyne.Size) { - // r.diagramWidget.at.Move(fyne.Position{X: 100, Y: 100}) +func (dw *DiagramWidget) MouseOut() { +} + +func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { +} + +func (dw *DiagramWidget) removeElementFromSelection(de DiagramElement) { + delete(dw.selection, de.GetDiagramElementID()) + de.HideHandles() } -func (r *diagramWidgetRenderer) ApplyTheme(size fyne.Size) { +func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { + for _, de := range dw.selection { + dw.removeElementFromSelection(de) + } + ForceRefresh() } -func (r *diagramWidgetRenderer) BackgroundColor() color.Color { - return theme.BackgroundColor() +// diagramWidgetRenderer +type diagramWidgetRenderer struct { + diagramWidget *DiagramWidget } func (r *diagramWidgetRenderer) Destroy() { } +func (r *diagramWidgetRenderer) Layout(size fyne.Size) { + // r.diagramWidget.at.Move(fyne.Position{X: 100, Y: 100}) +} + +func (r *diagramWidgetRenderer) MinSize() fyne.Size { + return r.diagramWidget.DesiredSize +} + func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { obj := make([]fyne.CanvasObject, 0) for _, n := range r.diagramWidget.Nodes { diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index 57141611..7a59cc64 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -7,13 +7,40 @@ import "fyne.io/fyne/v2" type DiagramElement interface { fyne.Widget GetDiagram() *DiagramWidget + GetDiagramElementID() string handleDragged(handle *Handle, event *fyne.DragEvent) + HideHandles() + ShowHandles() } type diagramElement struct { diagram *DiagramWidget + id string + handles map[string]*Handle } func (de *diagramElement) GetDiagram() *DiagramWidget { return de.diagram } + +func (de *diagramElement) GetDiagramElementID() string { + return de.id +} + +func (de *diagramElement) HideHandles() { + for _, handle := range de.handles { + handle.Hide() + } +} + +func (de *diagramElement) initialize(diagram *DiagramWidget, id string) { + de.diagram = diagram + de.id = id + de.handles = make(map[string]*Handle) +} + +func (de *diagramElement) ShowHandles() { + for _, handle := range de.handles { + handle.Show() + } +} diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go index 91df77c0..63dabab9 100644 --- a/widget/diagramwidget/handle.go +++ b/widget/diagramwidget/handle.go @@ -2,19 +2,16 @@ package diagramwidget import ( "image/color" - "log" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" - "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) -// draggable is used to verify that objects implement the Draggable interface. An assignment to -// draggable will fail at compile time if the interface is not fully implemented. -var draggable fyne.Draggable -var hoverable desktop.Hoverable +// Validate implementation of Draggable and Hoverable +var _ fyne.Draggable = (*Handle)(nil) +var _ desktop.Hoverable = (*Handle)(nil) var defaultHandleSize float32 = 10.0 @@ -29,9 +26,6 @@ func NewHandle(diagramElement DiagramElement) *Handle { de: diagramElement, handleSize: defaultHandleSize, } - // This assignment verifies that handle fully implements the Draggable interface - draggable = handle - hoverable = handle handle.BaseWidget.ExtendBaseWidget(handle) return handle } @@ -41,6 +35,7 @@ func (h *Handle) CreateRenderer() fyne.WidgetRenderer { handle: h, rect: canvas.NewRectangle(h.getStrokeColor()), } + hr.rect.FillColor = color.Transparent hr.Refresh() ForceRefresh() return hr @@ -48,7 +43,6 @@ func (h *Handle) CreateRenderer() fyne.WidgetRenderer { func (h *Handle) Dragged(event *fyne.DragEvent) { h.de.handleDragged(h, event) - log.Print("Handle dragged") } func (h *Handle) DragEnd() { @@ -56,8 +50,7 @@ func (h *Handle) DragEnd() { } func (h *Handle) getStrokeColor() color.Color { - variant := h.de.GetDiagram().ThemeVariant - return h.de.GetDiagram().DiagramTheme.Color(theme.ColorNameForeground, variant) + return h.de.GetDiagram().GetForegroundColor() } func (h *Handle) getStrokeWidth() float32 { @@ -65,17 +58,14 @@ func (h *Handle) getStrokeWidth() float32 { } func (h *Handle) MouseIn(*desktop.MouseEvent) { - log.Print("Mouse in handle") } // MouseMoved is a hook that is called if the mouse pointer moved over the element. func (h *Handle) MouseMoved(*desktop.MouseEvent) { - log.Print("Mouse moved in handle") } // MouseOut is a hook that is called if the mouse pointer leaves the element. func (h *Handle) MouseOut() { - log.Print("Mouse out of handle") } func (h *Handle) Move(position fyne.Position) { @@ -111,6 +101,7 @@ func (hr *handleRenderer) Objects() []fyne.CanvasObject { func (hr *handleRenderer) Refresh() { hr.rect.StrokeColor = hr.handle.getStrokeColor() + hr.rect.FillColor = color.Transparent hr.rect.StrokeWidth = hr.handle.getStrokeWidth() ForceRefresh() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index c9878935..e1dffe3c 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -11,13 +11,11 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" - "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) -// hoverable used during testing to determine whether DiagramLink fully implements the Hoverable interface. -// Assign an instance of DiagramLink to hoverable: if you don't get a compiler error, it is fully implemented -// var hoverable desktop.Hoverable +// Validate Hoverable Implementation +var _ desktop.Hoverable = (*DiagramLink)(nil) type DiagramLink struct { widget.BaseWidget @@ -37,9 +35,9 @@ type DiagramLink struct { midpointAnchoredText map[string]*AnchoredText } -func NewDiagramLink(diagram *DiagramWidget, v, u *DiagramNode) *DiagramLink { +func NewDiagramLink(diagram *DiagramWidget, v, u *DiagramNode, linkID string) *DiagramLink { dl := &DiagramLink{ - LinkColor: theme.TextColor(), + LinkColor: diagram.GetForegroundColor(), Width: 2, Origin: v, Target: u, @@ -47,15 +45,25 @@ func NewDiagramLink(diagram *DiagramWidget, v, u *DiagramNode) *DiagramLink { midpointAnchoredText: make(map[string]*AnchoredText), targetAnchoredText: make(map[string]*AnchoredText), } - dl.diagramElement.diagram = diagram + dl.diagramElement.initialize(diagram, linkID) dl.ExtendBaseWidget(dl) - // Use during testing to ensure that the instance fully implements Hoverable - // hoverable = dl + dl.diagram.Links[linkID] = dl return dl } +func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { + dlr := diagramLinkRenderer{ + link: dl, + line: canvas.NewLine(dl.LinkColor), + } + + (&dlr).Refresh() + + return &dlr +} + func (dl *DiagramLink) AddSourceAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) dl.sourceAnchoredText[key] = at @@ -74,15 +82,8 @@ func (dl *DiagramLink) AddTargetAnchoredText(key string, displayedText string) { at.Move(fyne.Position{X: float32(dl.targetPoint.X), Y: float32(dl.targetPoint.Y)}) } -func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { - dlr := diagramLinkRenderer{ - link: dl, - line: canvas.NewLine(dl.LinkColor), - } - - (&dlr).Refresh() +func (dl *DiagramLink) HideHandles() { - return &dlr } func (dl *DiagramLink) R2Line() r2.Line { @@ -101,11 +102,19 @@ func (dl *DiagramLink) MouseOut() { log.Printf("MouseOut DiagramLink Text %p", dl) } +func (dl *DiagramLink) ShowHandles() { + +} + +// diagramLinkRenderer type diagramLinkRenderer struct { link *DiagramLink line *canvas.Line } +func (dlr *diagramLinkRenderer) Destroy() { +} + func (dlr *diagramLinkRenderer) MinSize() fyne.Size { xdelta := dlr.link.Origin.Position().X - dlr.link.Target.Position().X if xdelta < 0 { @@ -185,34 +194,6 @@ func (dlr *diagramLinkRenderer) Layout(size fyne.Size) { } } -func (dlr *diagramLinkRenderer) ApplyTheme(size fyne.Size) { -} - -func (dlr *diagramLinkRenderer) Refresh() { - for _, decoration := range dlr.link.SourceDecorations { - decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.Width) - decoration.Refresh() - } - for _, decoration := range dlr.link.MidpointDecorations { - decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.Width) - decoration.Refresh() - } - for _, decoration := range dlr.link.TargetDecorations { - decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.Width) - decoration.Refresh() - } -} - -func (dlr *diagramLinkRenderer) BackgroundColor() color.Color { - return theme.BackgroundColor() -} - -func (dlr *diagramLinkRenderer) Destroy() { -} - func (dlr *diagramLinkRenderer) Objects() []fyne.CanvasObject { obj := []fyne.CanvasObject{ dlr.line, @@ -244,3 +225,21 @@ func (dlr *diagramLinkRenderer) Objects() []fyne.CanvasObject { return obj } + +func (dlr *diagramLinkRenderer) Refresh() { + for _, decoration := range dlr.link.SourceDecorations { + decoration.SetStrokeColor(dlr.link.LinkColor) + decoration.SetStrokeWidth(dlr.link.Width) + decoration.Refresh() + } + for _, decoration := range dlr.link.MidpointDecorations { + decoration.SetStrokeColor(dlr.link.LinkColor) + decoration.SetStrokeWidth(dlr.link.Width) + decoration.Refresh() + } + for _, decoration := range dlr.link.TargetDecorations { + decoration.SetStrokeColor(dlr.link.LinkColor) + decoration.SetStrokeWidth(dlr.link.Width) + decoration.Refresh() + } +} diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 5d3ae783..75fd5f36 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -12,16 +12,14 @@ import ( "fyne.io/fyne/v2/widget" ) -// Validate that DiagramNode is a DiagramElement +// Validate that DiagramNode implements DiagramElement and Tappable var _ DiagramElement = (*DiagramNode)(nil) +var _ fyne.Tappable = (*DiagramNode)(nil) const ( // default inner size defaultWidth float32 = 50 defaultHeight float32 = 25 - - // default padding around the inner object in a node - defaultPadding float32 = 10 ) // DiagramNode represents a node in the diagram widget. It contains an inner @@ -44,47 +42,43 @@ type DiagramNode struct { BoxStrokeWidth float32 // BoxFill is the fill color of the node, the inner object will be // drawn on top of this. Defaults to the DiagramTheme's BackgroundColor. - BoxFillColor color.Color - // BoxStrokeColor is the stroke color of the node rectangle. Defaults - // to DiagramTheme's ForegroundColor - BoxStrokeColor color.Color - // HandleColor is the color of node handle. HandleColor color.Color // HandleStrokeWidth is the stroke width of the node handle, defaults // to 3. HandleStroke float32 - handles map[string]*Handle + edgePad *RectanglePad } -func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject) *DiagramNode { +func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string) *DiagramNode { dn := &DiagramNode{ InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, InnerObject: obj, Padding: diagram.DiagramTheme.Size(theme.SizeNamePadding), BoxStrokeWidth: 1, - BoxFillColor: diagram.DiagramTheme.Color(theme.ColorNameBackground, diagram.ThemeVariant), - BoxStrokeColor: diagram.DiagramTheme.Color(theme.ColorNameForeground, diagram.ThemeVariant), HandleColor: diagram.DiagramTheme.Color(theme.ColorNameForeground, diagram.ThemeVariant), HandleStroke: 3, - handles: make(map[string]*Handle), } - dn.diagramElement.diagram = diagram + dn.diagramElement.initialize(diagram, nodeID) + dn.edgePad = NewRectanglePad(dn) + dn.edgePad.Hide() for _, handleKey := range []string{"upperLeft", "upperMiddle", "upperRight", "leftMiddle", "rightMiddle", "lowerLeft", "lowerMiddle", "lowerRight"} { newHandle := NewHandle(dn) dn.handles[handleKey] = newHandle + newHandle.Hide() } dn.ExtendBaseWidget(dn) + dn.diagram.Nodes[nodeID] = dn return dn } func (dn *DiagramNode) CreateRenderer() fyne.WidgetRenderer { dnr := diagramNodeRenderer{ node: dn, - box: canvas.NewRectangle(dn.BoxStrokeColor), + box: canvas.NewRectangle(dn.diagram.GetForegroundColor()), } dnr.box.StrokeWidth = dn.BoxStrokeWidth - dnr.box.FillColor = dn.BoxFillColor + dnr.box.FillColor = dn.diagram.GetBackgroundColor() (&dnr).Refresh() @@ -204,34 +198,40 @@ func (dn *DiagramNode) Center() fyne.Position { return fyne.Position{X: float32(dn.R2Center().X), Y: float32(dn.R2Center().Y)} } +func (dn *DiagramNode) Tapped(event *fyne.PointEvent) { + dn.diagram.DiagramElementTapped(dn, event) +} + // diagramNodeRenderer type diagramNodeRenderer struct { node *DiagramNode box *canvas.Rectangle } -func (r *diagramNodeRenderer) ApplyTheme(size fyne.Size) { +func (dnr *diagramNodeRenderer) ApplyTheme(size fyne.Size) { } -func (r *diagramNodeRenderer) BackgroundColor() color.Color { - return r.node.diagram.DiagramTheme.Color(theme.ColorNameBackground, r.node.diagram.ThemeVariant) +func (dnr *diagramNodeRenderer) BackgroundColor() color.Color { + return dnr.node.diagram.DiagramTheme.Color(theme.ColorNameBackground, dnr.node.diagram.ThemeVariant) } -func (r *diagramNodeRenderer) Destroy() { +func (dnr *diagramNodeRenderer) Destroy() { } -func (r *diagramNodeRenderer) MinSize() fyne.Size { +func (dnr *diagramNodeRenderer) MinSize() fyne.Size { // space for the inner widget, plus padding on all sides. - inner := r.node.effectiveInnerSize() + inner := dnr.node.effectiveInnerSize() return fyne.Size{ - Width: inner.Width + float32(2*r.node.Padding), - Height: inner.Height + float32(2*r.node.Padding), + Width: inner.Width + float32(2*dnr.node.Padding), + Height: inner.Height + float32(2*dnr.node.Padding), } } func (dnr *diagramNodeRenderer) Layout(size fyne.Size) { nodeSize := dnr.MinSize().Max(size) dnr.node.Resize(nodeSize) + dnr.node.edgePad.Resize(nodeSize) + dnr.node.edgePad.Move(fyne.NewPos(0, 0)) dnr.node.InnerObject.Move(dnr.node.innerPos()) dnr.node.InnerObject.Resize(dnr.node.effectiveInnerSize()) @@ -266,6 +266,7 @@ func (dnr *diagramNodeRenderer) Layout(size fyne.Size) { func (dnr *diagramNodeRenderer) Objects() []fyne.CanvasObject { obj := make([]fyne.CanvasObject, 0) obj = append(obj, dnr.box) + obj = append(obj, dnr.node.edgePad) obj = append(obj, dnr.node.InnerObject) for _, handle := range dnr.node.handles { obj = append(obj, handle) @@ -273,11 +274,12 @@ func (dnr *diagramNodeRenderer) Objects() []fyne.CanvasObject { return obj } -func (r *diagramNodeRenderer) Refresh() { - r.box.StrokeWidth = r.node.BoxStrokeWidth - r.box.FillColor = r.node.BoxFillColor - r.box.StrokeColor = r.node.BoxStrokeColor - for _, e := range r.node.diagram.GetEdges(r.node) { +func (dnr *diagramNodeRenderer) Refresh() { + dnr.box.StrokeWidth = dnr.node.BoxStrokeWidth + dnr.box.FillColor = color.Transparent + dnr.box.StrokeColor = dnr.node.diagram.GetForegroundColor() + for _, e := range dnr.node.diagram.GetEdges(dnr.node) { e.Refresh() } + dnr.node.edgePad.Refresh() } From 6b3f33717934288afb95a450c92d4bfd8b9d30e4 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 29 Mar 2023 11:08:38 -0400 Subject: [PATCH 08/53] All basic functionality in place Added point pad to middle of links, links can now connect to links. All connections are now based on pads --- cmd/diagramdemo/main.go | 54 ++-- widget/diagramwidget/anchoredtext.go | 10 +- .../{decoration => }/arrowhead.go | 15 +- widget/diagramwidget/connectionpad.go | 49 +++- .../Decoration.go => decoration.go} | 3 +- widget/diagramwidget/diagram.go | 125 ++++++--- widget/diagramwidget/forcelayout.go | 22 +- widget/diagramwidget/handle.go | 4 +- widget/diagramwidget/link.go | 260 +++++++++++------- widget/diagramwidget/linkpoint.go | 47 ++++ widget/diagramwidget/linksegment.go | 72 +++++ widget/diagramwidget/node.go | 93 ++++--- 12 files changed, 529 insertions(+), 225 deletions(-) rename widget/diagramwidget/{decoration => }/arrowhead.go (95%) rename widget/diagramwidget/{decoration/Decoration.go => decoration.go} (96%) create mode 100644 widget/diagramwidget/linkpoint.go create mode 100644 widget/diagramwidget/linksegment.go diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index fb9e5dde..cdd68671 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -6,7 +6,6 @@ import ( "time" "fyne.io/x/fyne/widget/diagramwidget" - "fyne.io/x/fyne/widget/diagramwidget/decoration" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" @@ -47,77 +46,90 @@ func main() { // Node 0 node0Label := widget.NewLabel("Node0") node0 := diagramwidget.NewDiagramNode(diagramWidget, node0Label, "Node0") + node0.Move(fyne.NewPos(300, 0)) // Node 1 node1Button := widget.NewButton("Node1 Button", func() { fmt.Printf("tapped Node1!\n") }) node1 := diagramwidget.NewDiagramNode(diagramWidget, node1Button, "Node1") - node1.Move(fyne.Position{X: 200, Y: 200}) + node1.Move(fyne.Position{X: 100, Y: 100}) // Node 2 node2 := diagramwidget.NewDiagramNode(diagramWidget, nil, "Node2") node2Container := container.NewVBox( widget.NewLabel("Node2 - with structure"), widget.NewButton("Up", func() { - node2.Displace(fyne.Position{X: 0, Y: -10}) + node2.GetDiagram().DisplaceNode(node2, fyne.Position{X: 0, Y: -10}) node2.Refresh() }), widget.NewButton("Down", func() { - node2.Displace(fyne.Position{X: 0, Y: 10}) + node2.GetDiagram().DisplaceNode(node2, fyne.Position{X: 0, Y: 10}) node2.Refresh() }), container.NewHBox( widget.NewButton("Left", func() { - node2.Displace(fyne.Position{X: -10, Y: 0}) + node2.GetDiagram().DisplaceNode(node2, fyne.Position{X: -10, Y: 0}) node2.Refresh() }), widget.NewButton("Right", func() { - node2.Displace(fyne.Position{X: 10, Y: 0}) + node2.GetDiagram().DisplaceNode(node2, fyne.Position{X: 10, Y: 0}) node2.Refresh() }), ), ) - node2.InnerObject = node2Container - node2.Move(fyne.Position{X: 300, Y: 300}) + node2.SetInnerObject(node2Container) + node2.Move(fyne.Position{X: 100, Y: 300}) // Node 3 node3 := diagramwidget.NewDiagramNode(diagramWidget, widget.NewButton("Node3: Force layout step", func() { diagramWidget.StepForceLayout(300) diagramWidget.Refresh() }), "Node3") - node3.Move(fyne.Position{X: 400, Y: 200}) + node3.Move(fyne.Position{X: 400, Y: 100}) // Node 4 node4 := diagramwidget.NewDiagramNode(diagramWidget, widget.NewButton("Node4: auto layout", func() { forceticks += 100 diagramWidget.Refresh() }), "Node4") - node4.Move(fyne.Position{X: 400, Y: 500}) + node4.Move(fyne.Position{X: 400, Y: 400}) + + node5 := diagramwidget.NewDiagramNode(diagramWidget, widget.NewLabel("Node5"), "Node5") + node5.Move(fyne.NewPos(600, 200)) // Link0 - link0 := diagramwidget.NewDiagramLink(diagramWidget, node0, node1, "Link0") + link0 := diagramwidget.NewDiagramLink(diagramWidget, node0.GetEdgePad(), node1.GetEdgePad(), "Link0") link0.AddSourceAnchoredText("sourceRole", "sourceRole") + link0.AddMidpointAnchoredText("linkName", "Link 0") // Link1 - link1 := diagramwidget.NewDiagramLink(diagramWidget, node2, node1, "Link1") + link1 := diagramwidget.NewDiagramLink(diagramWidget, node2.GetEdgePad(), node1.GetEdgePad(), "Link1") link1.LinkColor = color.RGBA{255, 64, 64, 255} - link1.TargetDecorations = append(link1.TargetDecorations, decoration.NewArrowhead()) - link1.TargetDecorations = append(link1.TargetDecorations, decoration.NewArrowhead()) - link1.MidpointDecorations = append(link1.MidpointDecorations, decoration.NewArrowhead()) - link1.MidpointDecorations = append(link1.MidpointDecorations, decoration.NewArrowhead()) - link1.SourceDecorations = append(link1.SourceDecorations, decoration.NewArrowhead()) - link1.SourceDecorations = append(link1.SourceDecorations, decoration.NewArrowhead()) + link1.AddTargetDecoration(diagramwidget.NewArrowhead()) + link1.AddTargetDecoration(diagramwidget.NewArrowhead()) + link1.AddMidpointDecoration(diagramwidget.NewArrowhead()) + link1.AddMidpointDecoration(diagramwidget.NewArrowhead()) + link1.AddMidpointAnchoredText("linkName", "Link 1") + link1.AddSourceDecoration(diagramwidget.NewArrowhead()) + link1.AddSourceDecoration(diagramwidget.NewArrowhead()) // Link2 - diagramwidget.NewDiagramLink(diagramWidget, node0, node3, "Link2") + link2 := diagramwidget.NewDiagramLink(diagramWidget, node0.GetEdgePad(), node3.GetEdgePad(), "Link2") + link2.AddMidpointAnchoredText("linkName", "Link 2") // Link3 - link3 := diagramwidget.NewDiagramLink(diagramWidget, node2, node3, "Link3") + link3 := diagramwidget.NewDiagramLink(diagramWidget, node2.GetEdgePad(), node3.GetEdgePad(), "Link3") link3.AddSourceAnchoredText("sourceRole", "sourceRole") link3.AddMidpointAnchoredText("linkName", "Link 3") link3.AddTargetAnchoredText("targetRole", "targetRole") // Link4 - diagramwidget.NewDiagramLink(diagramWidget, node4, node3, "Link4") + link4 := diagramwidget.NewDiagramLink(diagramWidget, node4.GetEdgePad(), node3.GetEdgePad(), "Link4") + link4.AddMidpointAnchoredText("linkName", "Link 4") + + // Link5 + link5 := diagramwidget.NewDiagramLink(diagramWidget, link4.GetMidPad(), node5.GetEdgePad(), "Link5") + link5.AddMidpointAnchoredText("linkName", "Link 5") + link5.AddTargetDecoration(diagramwidget.NewArrowhead()) w.SetContent(diagramWidget) diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index 90ae317d..fd6e8ce6 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -2,7 +2,6 @@ package diagramwidget import ( "image/color" - "log" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" @@ -52,29 +51,23 @@ func (at *AnchoredText) Displace(delta fyne.Position) { } func (at *AnchoredText) DragEnd() { - log.Printf("DragEnd AnchoredText %p", at) at.Refresh() } func (at *AnchoredText) Dragged(event *fyne.DragEvent) { - log.Printf("Ancored Text Dragged %p", at) delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} at.Move(at.Position().Add(delta)) at.Refresh() - ForceRefresh() + ForceRepaint() } func (at *AnchoredText) MinSize() fyne.Size { minSize := fyne.Size{Height: 25, Width: 50} - // log.Printf("AnchoredText size: %v pointer: %p", minSize, at) return minSize - // log.Printf("AnchoredText size: %v pointer: %p", at.textObject.Size(), at) - // return at.textObject.Size() } func (at *AnchoredText) MouseIn(event *desktop.MouseEvent) { // at.textObject.TextStyle.Bold = true - log.Printf("MouseIn Anchored Text %p", at) at.Refresh() } @@ -84,7 +77,6 @@ func (at *AnchoredText) MouseMoved(event *desktop.MouseEvent) { func (at *AnchoredText) MouseOut() { // at.textObject.TextStyle.Bold = false - log.Printf("MouseOut Anchored Text %p", at) at.Refresh() } diff --git a/widget/diagramwidget/decoration/arrowhead.go b/widget/diagramwidget/arrowhead.go similarity index 95% rename from widget/diagramwidget/decoration/arrowhead.go rename to widget/diagramwidget/arrowhead.go index b55bb8e3..a4f20986 100644 --- a/widget/diagramwidget/decoration/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -1,4 +1,4 @@ -package decoration +package diagramwidget import ( "image/color" @@ -34,6 +34,7 @@ const ( // Right type Arrowhead struct { widget.BaseWidget + link *DiagramLink // BaseAngle is used to define direction in which the arrowhead points // Base fyne.Position BaseAngle float64 @@ -48,8 +49,8 @@ type Arrowhead struct { // Length is the length of the two "tails" that intersect at the tip. Length int // central *canvas.Line - left *canvas.Line - right *canvas.Line + // left *canvas.Line + // right *canvas.Line visible bool } @@ -69,8 +70,8 @@ func NewArrowhead() *Arrowhead { func (a *Arrowhead) CreateRenderer() fyne.WidgetRenderer { ar := arrowheadRenderer{ arrowhead: a, - left: canvas.NewLine(theme.ForegroundColor()), - right: canvas.NewLine(theme.ForegroundColor()), + left: canvas.NewLine(a.link.LinkColor), + right: canvas.NewLine(a.link.LinkColor), } return &ar } @@ -114,6 +115,10 @@ func (a *Arrowhead) RightPoint() fyne.Position { return rightPosition } +func (a *Arrowhead) setLink(link *DiagramLink) { + a.link = link +} + func (a *Arrowhead) SetStrokeColor(strokeColor color.Color) { a.StrokeColor = strokeColor } diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index 8a8a2716..52210cc0 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -3,6 +3,8 @@ package diagramwidget import ( "image/color" + "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" @@ -19,6 +21,8 @@ type ConnectionPad interface { fyne.Widget desktop.Hoverable GetPadOwner() DiagramElement + GetCenter() fyne.Position + GetConnectionPoint(referencePoint fyne.Position) fyne.Position } type connectionPad struct { @@ -59,6 +63,15 @@ func (pp *PointPad) CreateRenderer() fyne.WidgetRenderer { return ppr } +// GetCenter returns the position in diagram coordinates +func (pp *PointPad) GetCenter() fyne.Position { + return pp.padOwner.Position().Add(pp.Position()) +} + +func (pp *PointPad) GetConnectionPoint(referencePoint fyne.Position) fyne.Position { + return pp.GetCenter() +} + func (pp *PointPad) MouseIn(event *desktop.MouseEvent) { } @@ -118,6 +131,8 @@ var _ ConnectionPad = (*RectanglePad)(nil) type RectanglePad struct { widget.BaseWidget connectionPad + // box is the pad shape in diagram coordinates + // box r2.Box } func NewRectanglePad(padOwner DiagramElement) *RectanglePad { @@ -137,6 +152,33 @@ func (rp *RectanglePad) CreateRenderer() fyne.WidgetRenderer { return rpr } +// GetCenter() returns the center of the pad in the diagram's coordinate system +func (rp *RectanglePad) GetCenter() fyne.Position { + box := rp.makeBox() + r2Center := box.Center() + return fyne.NewPos(float32(r2Center.X), float32(r2Center.Y)) +} + +func (rp *RectanglePad) GetConnectionPoint(referencePoint fyne.Position) fyne.Position { + box := rp.makeBox() + r2ReferencePoint := r2.MakeVec2(float64(referencePoint.X), float64(referencePoint.Y)) + linkLine := r2.MakeLineFromEndpoints(box.Center(), r2ReferencePoint) + r2Intersection, _ := box.Intersect(linkLine) + return fyne.NewPos(float32(r2Intersection.X), float32(r2Intersection.Y)) +} + +// makeBox returns an r2 box representing the rectangle pad's position and size in the +// diagram's coorinate system +func (rp *RectanglePad) makeBox() r2.Box { + diagramCoordinatePosition := rp.padOwner.Position().Add(rp.Position()) + r2Position := r2.V2(float64(diagramCoordinatePosition.X), float64(diagramCoordinatePosition.Y)) + s := r2.V2( + float64(rp.Size().Width), + float64(rp.Size().Height), + ) + return r2.MakeBox(r2Position, s) +} + func (rp *RectanglePad) MouseIn(event *desktop.MouseEvent) { } @@ -160,8 +202,9 @@ func (rpr *rectanglePadRenderer) Destroy() { } func (rpr *rectanglePadRenderer) Layout(size fyne.Size) { - rpr.rp.Resize(rpr.rp.padOwner.Size()) - rpr.rect.Resize(rpr.rp.padOwner.Size()) + padOwnerSize := rpr.rp.padOwner.Size() + rpr.rp.Resize(padOwnerSize) + rpr.rect.Resize(padOwnerSize) } func (rpr *rectanglePadRenderer) MinSize() fyne.Size { @@ -179,5 +222,5 @@ func (rpr *rectanglePadRenderer) Refresh() { rpr.rect.StrokeColor = rpr.rp.padOwner.GetDiagram().GetForegroundColor() rpr.rect.FillColor = color.Transparent rpr.rect.StrokeWidth = padLineWidth - ForceRefresh() + ForceRepaint() } diff --git a/widget/diagramwidget/decoration/Decoration.go b/widget/diagramwidget/decoration.go similarity index 96% rename from widget/diagramwidget/decoration/Decoration.go rename to widget/diagramwidget/decoration.go index 6e14727b..a9bd6c1a 100644 --- a/widget/diagramwidget/decoration/Decoration.go +++ b/widget/diagramwidget/decoration.go @@ -1,4 +1,4 @@ -package decoration +package diagramwidget import ( "image/color" @@ -20,6 +20,7 @@ import ( // so that it can adjust the position of the next decoration appropriately. type Decoration interface { fyne.Widget + setLink(link *DiagramLink) SetStrokeColor(color color.Color) SetStrokeWidth(width float32) // SetReferenceAngle sets the angle of the reference axis diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index c863d9b1..651ce5fe 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -12,13 +12,18 @@ import ( var Globaldiagram *DiagramWidget -func ForceRefresh() { +func ForceRepaint() { Globaldiagram.DummyBox.Refresh() } // Verify that interfaces are fully implemented var _ fyne.Tappable = (*DiagramWidget)(nil) +type linkPinPair struct { + link *DiagramLink + pin *LinkPoint +} + type DiagramWidget struct { widget.BaseWidget @@ -35,39 +40,68 @@ type DiagramWidget struct { // up, defaults to 800 x 600 DesiredSize fyne.Size - Nodes map[string]*DiagramNode - Links map[string]*DiagramLink - selection map[string]DiagramElement + Nodes map[string]*DiagramNode + Links map[string]*DiagramLink + selection map[string]DiagramElement + diagramElementLinkDependencies map[string][]linkPinPair // TODO Remove DummyBox when fyne rendering issue is resolved DummyBox *canvas.Rectangle } func NewDiagramWidget(id string) *DiagramWidget { - d := &DiagramWidget{ - ID: id, - DiagramTheme: fyne.CurrentApp().Settings().Theme(), - ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), - DesiredSize: fyne.Size{Width: 800, Height: 600}, - Offset: fyne.Position{X: 0, Y: 0}, - Nodes: map[string]*DiagramNode{}, - Links: map[string]*DiagramLink{}, - DummyBox: canvas.NewRectangle(color.Transparent), - selection: map[string]DiagramElement{}, + dw := &DiagramWidget{ + ID: id, + DiagramTheme: fyne.CurrentApp().Settings().Theme(), + ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), + DesiredSize: fyne.Size{Width: 800, Height: 600}, + Offset: fyne.Position{X: 0, Y: 0}, + Nodes: map[string]*DiagramNode{}, + Links: map[string]*DiagramLink{}, + DummyBox: canvas.NewRectangle(color.Transparent), + selection: map[string]DiagramElement{}, + diagramElementLinkDependencies: map[string][]linkPinPair{}, } - d.DummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) - d.DummyBox.Move(fyne.Position{X: 50, Y: 50}) + dw.DummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) + dw.DummyBox.Move(fyne.Position{X: 50, Y: 50}) + + dw.ExtendBaseWidget(dw) + + return dw +} - d.ExtendBaseWidget(d) +func (dw *DiagramWidget) AddLink(link *DiagramLink) { + dw.Links[link.id] = link + link.Refresh() + // TODO add logic to rezise diagram if necessary +} - return d +func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link *DiagramLink, pin *LinkPoint) { + deID := diagramElement.GetDiagramElementID() + currentDependencies := dw.diagramElementLinkDependencies[deID] + if currentDependencies == nil { + dw.diagramElementLinkDependencies[deID] = []linkPinPair{{link, pin}} + } else { + for _, pair := range currentDependencies { + if pair.link == link && pair.pin == pin { + // it's already there + return + } + } + dw.diagramElementLinkDependencies[deID] = append(currentDependencies, linkPinPair{link, pin}) + } +} + +func (dw *DiagramWidget) AddNode(node *DiagramNode) { + dw.Nodes[node.id] = node + node.Refresh() + // TODO add logic to rezise diagram if necessary } func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { r := diagramWidgetRenderer{ diagramWidget: dw, } - return &r } @@ -86,7 +120,7 @@ func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.Poi if !dw.IsSelected(de) { dw.addElementToSelection(de) } - ForceRefresh() + ForceRepaint() } func (dw *DiagramWidget) DragEnd() { @@ -105,26 +139,24 @@ func (dw *DiagramWidget) GetHoverColor() color.Color { return dw.DiagramTheme.Color(theme.ColorNameHover, dw.ThemeVariant) } -func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { +func (dw *DiagramWidget) DiagramNodeDragged(node *DiagramNode, event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} - for _, n := range dw.Nodes { - n.Displace(delta) - } - dw.Refresh() + dw.DisplaceNode(node, delta) + ForceRepaint() } -func (dw *DiagramWidget) GetEdges(n *DiagramNode) []*DiagramLink { - links := []*DiagramLink{} +func (dw *DiagramWidget) DisplaceNode(node *DiagramNode, delta fyne.Position) { + node.Move(node.Position().Add(delta)) + dw.refreshDependentLinks(node) + ForceRepaint() +} - for _, link := range dw.Links { - if link.Origin == n { - links = append(links, link) - } else if link.Target == n { - links = append(links, link) - } +func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { + delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} + for _, n := range dw.Nodes { + dw.DisplaceNode(n, delta) } - - return links + dw.Refresh() } func (dw *DiagramWidget) IsSelected(de DiagramElement) bool { @@ -145,11 +177,32 @@ func (dw *DiagramWidget) removeElementFromSelection(de DiagramElement) { de.HideHandles() } +func (dw *DiagramWidget) removeLinkDependency(diagramElement DiagramElement, link *DiagramLink, pin *LinkPoint) { + deID := diagramElement.GetDiagramElementID() + currentDependencies := dw.diagramElementLinkDependencies[deID] + if currentDependencies == nil { + return + } + for i, pair := range currentDependencies { + if pair.link == link && pair.pin == pin { + dw.diagramElementLinkDependencies[deID] = append(currentDependencies[:i], currentDependencies[i+1:]...) + return + } + } +} + +func (dw *DiagramWidget) refreshDependentLinks(de DiagramElement) { + dependencies := dw.diagramElementLinkDependencies[de.GetDiagramElementID()] + for _, pair := range dependencies { + pair.link.Refresh() + } +} + func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { for _, de := range dw.selection { dw.removeElementFromSelection(de) } - ForceRefresh() + ForceRepaint() } // diagramWidgetRenderer diff --git a/widget/diagramwidget/forcelayout.go b/widget/diagramwidget/forcelayout.go index f31a1cf9..df8b754a 100644 --- a/widget/diagramwidget/forcelayout.go +++ b/widget/diagramwidget/forcelayout.go @@ -9,10 +9,10 @@ import ( ) // adjacent returns true if there is at least one edge between n1 and n2 -func (g *DiagramWidget) adjacent(n1, n2 *DiagramNode) bool { +func (dw *DiagramWidget) adjacent(n1, n2 *DiagramNode) bool { // TODO: expensive, may be worth caching? - for _, e := range g.Links { - if ((e.Origin == n1) && (e.Target == n2)) || ((e.Origin == n2) && (e.Target == n1)) { + for _, e := range dw.Links { + if ((e.sourcePad.GetPadOwner() == n1) && (e.targetPad.GetPadOwner() == n2)) || ((e.sourcePad.GetPadOwner() == n2) && (e.targetPad.GetPadOwner() == n1)) { return true } } @@ -27,14 +27,14 @@ func calculateDistance(n1, n2 *DiagramNode) float64 { // calculateForce calculates the force between the given pair of nodes. // // The force is calculated at n1. -func (g *DiagramWidget) calculateForce(n1, n2 *DiagramNode, targetLength float64) r2.Vec2 { +func (dw *DiagramWidget) calculateForce(n1, n2 *DiagramNode, targetLength float64) r2.Vec2 { // spring constant for linear spring k := float64(0.01) d := calculateDistance(n1, n2) v := n2.R2Center().Add(n1.R2Center().Scale(-1)).Unit().Scale(-1) - if g.adjacent(n1, n2) { + if dw.adjacent(n1, n2) { // adjacent nodes act like springs, and want to be close to the given // length. @@ -61,24 +61,24 @@ func (g *DiagramWidget) calculateForce(n1, n2 *DiagramNode, targetLength float64 // StepForceLayout calculates one step of force directed graph layout, with // the target distance between adjacent nodes being targetLength. -func (g *DiagramWidget) StepForceLayout(targetLength float64) { +func (dw *DiagramWidget) StepForceLayout(targetLength float64) { deltas := make(map[string]r2.Vec2) // calculate all the deltas from the current state - for k, nk := range g.Nodes { + for k, nk := range dw.Nodes { deltas[k] = r2.V2(0, 0) - for j, nj := range g.Nodes { + for j, nj := range dw.Nodes { if j == k { continue } - deltas[k] = deltas[k].Add(g.calculateForce(nk, nj, targetLength)) + deltas[k] = deltas[k].Add(dw.calculateForce(nk, nj, targetLength)) } } // flip into current state - for k, nk := range g.Nodes { - nk.Displace(fyne.Position{X: float32(deltas[k].X), Y: float32(deltas[k].Y)}) + for k, nk := range dw.Nodes { + dw.DisplaceNode(nk, fyne.Position{X: float32(deltas[k].X), Y: float32(deltas[k].Y)}) } } diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go index 63dabab9..70af5383 100644 --- a/widget/diagramwidget/handle.go +++ b/widget/diagramwidget/handle.go @@ -37,7 +37,7 @@ func (h *Handle) CreateRenderer() fyne.WidgetRenderer { } hr.rect.FillColor = color.Transparent hr.Refresh() - ForceRefresh() + ForceRepaint() return hr } @@ -103,5 +103,5 @@ func (hr *handleRenderer) Refresh() { hr.rect.StrokeColor = hr.handle.getStrokeColor() hr.rect.FillColor = color.Transparent hr.rect.StrokeWidth = hr.handle.getStrokeWidth() - ForceRefresh() + ForceRepaint() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index e1dffe3c..11e23ddd 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -2,61 +2,67 @@ package diagramwidget import ( "image/color" - "log" "math" - "fyne.io/x/fyne/widget/diagramwidget/decoration" "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" ) // Validate Hoverable Implementation var _ desktop.Hoverable = (*DiagramLink)(nil) +var _ DiagramElement = (*DiagramLink)(nil) type DiagramLink struct { widget.BaseWidget diagramElement + linkPoints []*LinkPoint + linkSegments []*LinkSegment LinkColor color.Color Width float32 - Origin *DiagramNode - sourcePoint r2.Vec2 - Target *DiagramNode - targetPoint r2.Vec2 - midPoint r2.Vec2 - SourceDecorations []decoration.Decoration + midPad *PointPad + sourcePad ConnectionPad + targetPad ConnectionPad + SourceDecorations []Decoration sourceAnchoredText map[string]*AnchoredText - TargetDecorations []decoration.Decoration + TargetDecorations []Decoration targetAnchoredText map[string]*AnchoredText - MidpointDecorations []decoration.Decoration + MidpointDecorations []Decoration midpointAnchoredText map[string]*AnchoredText } -func NewDiagramLink(diagram *DiagramWidget, v, u *DiagramNode, linkID string) *DiagramLink { +func NewDiagramLink(diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, linkID string) *DiagramLink { dl := &DiagramLink{ + linkPoints: []*LinkPoint{}, + linkSegments: []*LinkSegment{}, LinkColor: diagram.GetForegroundColor(), Width: 2, - Origin: v, - Target: u, + sourcePad: sourcePad, + targetPad: targetPad, sourceAnchoredText: make(map[string]*AnchoredText), midpointAnchoredText: make(map[string]*AnchoredText), targetAnchoredText: make(map[string]*AnchoredText), } dl.diagramElement.initialize(diagram, linkID) - + dl.linkPoints = append(dl.linkPoints, NewLinkPoint(dl)) + dl.linkPoints = append(dl.linkPoints, NewLinkPoint(dl)) + dl.linkSegments = append(dl.linkSegments, NewLinkSegment(dl, dl.linkPoints[0].Position(), dl.linkPoints[1].Position())) + dl.midPad = NewPointPad(dl) + dl.midPad.Move(dl.GetMidPosition()) dl.ExtendBaseWidget(dl) - dl.diagram.Links[linkID] = dl + dl.diagram.AddLink(dl) + dl.diagram.addLinkDependency(dl.sourcePad.GetPadOwner(), dl, dl.linkPoints[0]) + dl.diagram.addLinkDependency(dl.targetPad.GetPadOwner(), dl, dl.linkPoints[1]) + dl.Refresh() return dl } func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { dlr := diagramLinkRenderer{ link: dl, - line: canvas.NewLine(dl.LinkColor), } (&dlr).Refresh() @@ -67,31 +73,74 @@ func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { func (dl *DiagramLink) AddSourceAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) dl.sourceAnchoredText[key] = at - at.Move(fyne.Position{X: float32(dl.sourcePoint.X), Y: float32(dl.sourcePoint.Y)}) + at.SetReferencePosition(dl.GetSourcePosition()) + at.Move(dl.GetSourcePosition()) + dl.Refresh() +} + +func (dl *DiagramLink) AddSourceDecoration(decoration Decoration) { + decoration.setLink(dl) + dl.SourceDecorations = append(dl.SourceDecorations, decoration) + dl.Refresh() } func (dl *DiagramLink) AddMidpointAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) dl.midpointAnchoredText[key] = at - at.Move(fyne.Position{X: float32(dl.midPoint.X), Y: float32(dl.midPoint.Y)}) + at.SetReferencePosition(dl.GetMidPosition()) + at.Move(dl.GetMidPosition()) + dl.Refresh() +} + +func (dl *DiagramLink) AddMidpointDecoration(decoration Decoration) { + decoration.setLink(dl) + dl.MidpointDecorations = append(dl.MidpointDecorations, decoration) + dl.Refresh() } func (dl *DiagramLink) AddTargetAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) dl.targetAnchoredText[key] = at - at.Move(fyne.Position{X: float32(dl.targetPoint.X), Y: float32(dl.targetPoint.Y)}) + at.SetReferencePosition(dl.GetTargetPosition()) + at.Move(dl.GetTargetPosition()) + dl.Refresh() } -func (dl *DiagramLink) HideHandles() { +func (dl *DiagramLink) AddTargetDecoration(decoration Decoration) { + decoration.setLink(dl) + dl.TargetDecorations = append(dl.TargetDecorations, decoration) + dl.Refresh() +} + +func (dl *DiagramLink) GetSourcePosition() fyne.Position { + return dl.linkPoints[0].Position() +} +func (dl *DiagramLink) GetMidPad() ConnectionPad { + return dl.midPad } -func (dl *DiagramLink) R2Line() r2.Line { - return r2.MakeLineFromEndpoints(dl.Origin.R2Center(), dl.Target.R2Center()) +func (dl *DiagramLink) GetMidPosition() fyne.Position { + // TODO update when additional points are introduced + sourcePoint := dl.linkPoints[0].Position() + targetPoint := dl.linkPoints[len(dl.linkPoints)-1].Position() + midPoint := fyne.NewPos((sourcePoint.X+targetPoint.X)/2, (sourcePoint.Y+targetPoint.Y)/2) + return midPoint +} + +func (dl *DiagramLink) GetTargetPosition() fyne.Position { + return dl.linkPoints[len(dl.linkPoints)-1].Position() +} + +func (dl *DiagramLink) handleDragged(handle *Handle, event *fyne.DragEvent) { + // TODO implement this +} + +func (dl *DiagramLink) HideHandles() { + } func (dl *DiagramLink) MouseIn(event *desktop.MouseEvent) { - log.Printf("MouseIn DiagramLink Text %p", dl) } func (dl *DiagramLink) MouseMoved(event *desktop.MouseEvent) { @@ -99,7 +148,6 @@ func (dl *DiagramLink) MouseMoved(event *desktop.MouseEvent) { } func (dl *DiagramLink) MouseOut() { - log.Printf("MouseOut DiagramLink Text %p", dl) } func (dl *DiagramLink) ShowHandles() { @@ -109,124 +157,138 @@ func (dl *DiagramLink) ShowHandles() { // diagramLinkRenderer type diagramLinkRenderer struct { link *DiagramLink - line *canvas.Line } func (dlr *diagramLinkRenderer) Destroy() { } func (dlr *diagramLinkRenderer) MinSize() fyne.Size { - xdelta := dlr.link.Origin.Position().X - dlr.link.Target.Position().X - if xdelta < 0 { - xdelta *= -1 + var xMin, xMax, yMin, yMax float32 + for i, point := range dlr.link.linkPoints { + if i == 0 { + xMin = point.Position().X + xMax = point.Position().X + yMin = point.Position().Y + yMax = point.Position().Y + } else { + xMin = float32(math.Min(float64(xMin), float64(point.Position().X))) + xMax = float32(math.Max(float64(xMax), float64(point.Position().X))) + yMin = float32(math.Min(float64(yMin), float64(point.Position().Y))) + yMax = float32(math.Max(float64(yMax), float64(point.Position().Y))) + } } + return fyne.Size{Width: float32(math.Abs(float64(xMax - xMin))), Height: float32(math.Abs(float64(yMax - yMin)))} +} - ydelta := dlr.link.Origin.Position().Y - dlr.link.Target.Position().Y - if ydelta < 0 { - ydelta *= -1 +func (dlr *diagramLinkRenderer) Layout(size fyne.Size) { +} + +func (dlr *diagramLinkRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{} + for i := 0; i < len(dlr.link.linkSegments); i++ { + obj = append(obj, dlr.link.linkSegments[i]) + } + for _, sourceDecoration := range dlr.link.SourceDecorations { + if sourceDecoration != nil { + obj = append(obj, sourceDecoration) + } + } + for _, sourceAnchoredText := range dlr.link.sourceAnchoredText { + obj = append(obj, sourceAnchoredText) + } + for _, midpointDecoration := range dlr.link.MidpointDecorations { + if midpointDecoration != nil { + obj = append(obj, midpointDecoration) + } + } + for _, midpointAnchoredText := range dlr.link.midpointAnchoredText { + obj = append(obj, midpointAnchoredText) + } + for _, targetDecoration := range dlr.link.TargetDecorations { + if targetDecoration != nil { + obj = append(obj, targetDecoration) + } + } + for _, targetAnchoredText := range dlr.link.targetAnchoredText { + obj = append(obj, targetAnchoredText) } - return fyne.Size{Width: xdelta, Height: ydelta} + obj = append(obj, dlr.link.midPad) + return obj } -func (dlr *diagramLinkRenderer) Layout(size fyne.Size) { - l := dlr.link.R2Line() - sourceBox := dlr.link.Origin.R2Box() - targetBox := dlr.link.Target.R2Box() - dlr.link.sourcePoint, _ = sourceBox.Intersect(l) - dlr.link.targetPoint, _ = targetBox.Intersect(l) - dlr.line.Position1 = fyne.Position{ - X: float32(dlr.link.sourcePoint.X), - Y: float32(dlr.link.sourcePoint.Y), - } - dlr.line.Position2 = fyne.Position{ - X: float32(dlr.link.targetPoint.X), - Y: float32(dlr.link.targetPoint.Y), - } - dlr.line.StrokeColor = dlr.link.LinkColor - dlr.line.StrokeWidth = dlr.link.Width - canvas.Refresh(dlr.line) +func (dlr *diagramLinkRenderer) Refresh() { + padBasedSourceReferencePoint := dlr.link.sourcePad.GetCenter() + padBasedTargetReferencePoint := dlr.link.targetPad.GetCenter() + padBasedSourcePosition := dlr.link.sourcePad.GetConnectionPoint(padBasedTargetReferencePoint) + padBasedTargetPosition := dlr.link.targetPad.GetConnectionPoint(padBasedSourceReferencePoint) + // The Position of the link is the upper left hand corner of a bounding box surrounding the source and target positions + linkPosition := fyne.NewPos(float32(math.Min(float64(padBasedSourcePosition.X), float64(padBasedTargetPosition.X))), + float32(math.Min(float64(padBasedSourcePosition.Y), float64(padBasedTargetPosition.Y)))) + dlr.link.Move(linkPosition) + + dlr.link.linkPoints[0].Move(padBasedSourcePosition.Subtract(linkPosition)) + dlr.link.linkPoints[len(dlr.link.linkPoints)-1].Move(padBasedTargetPosition.Subtract(linkPosition)) + // Position segments only after all points have been positioned + for i := 0; i < len(dlr.link.linkPoints)-1; i++ { + linkSegment := dlr.link.linkSegments[i] + linkSegment.SetPoints(dlr.link.linkPoints[i].Position(), dlr.link.linkPoints[i+1].Position()) + // linkSegment.Refresh() + } + // Have to change the sign of Y since the window inverts the Y axis - lineVector := r2.Vec2{X: float64(dlr.line.Position2.X - dlr.line.Position1.X), Y: -float64(dlr.line.Position2.Y - dlr.line.Position1.Y)} + lineVector := r2.Vec2{X: float64(dlr.link.linkPoints[1].Position().X - dlr.link.linkPoints[0].Position().X), + Y: -float64(dlr.link.linkPoints[1].Position().Y - dlr.link.linkPoints[0].Position().Y)} sourceAngle := lineVector.Angle() - targetAngle := r2.AddAngles(sourceAngle, math.Pi) sourceOffset := 0.0 for _, decoration := range dlr.link.SourceDecorations { decorationReferencePoint := fyne.Position{ - X: float32(float64(dlr.line.Position1.X) + math.Cos(sourceAngle)*sourceOffset), - Y: float32(float64(dlr.line.Position1.Y) - math.Sin(sourceAngle)*sourceOffset), + X: float32(float64(dlr.link.linkPoints[0].Position().X) + math.Cos(sourceAngle)*sourceOffset), + Y: float32(float64(dlr.link.linkPoints[0].Position().Y) - math.Sin(sourceAngle)*sourceOffset), } decoration.Move(decorationReferencePoint) decoration.SetReferenceAngle(sourceAngle) sourceOffset = sourceOffset + float64(decoration.GetReferenceLength()) } - dlr.link.midPoint = r2.Vec2{ - X: float64((dlr.link.sourcePoint.X + dlr.link.targetPoint.X) / 2), - Y: float64((dlr.link.sourcePoint.Y + dlr.link.targetPoint.Y) / 2), - } + + // TODO Update target angle for multiple segments + targetAngle := r2.AddAngles(sourceAngle, math.Pi) + midOffset := 0.0 for _, decoration := range dlr.link.MidpointDecorations { decorationReferencePoint := fyne.Position{ - X: float32(float64(dlr.link.midPoint.X) + math.Cos(targetAngle)*midOffset), - Y: float32(float64(dlr.link.midPoint.Y) - math.Sin(targetAngle)*midOffset), + X: float32(float64(dlr.link.GetMidPosition().X) + math.Cos(targetAngle)*midOffset), + Y: float32(float64(dlr.link.GetMidPosition().Y) - math.Sin(targetAngle)*midOffset), } decoration.Move(decorationReferencePoint) decoration.SetReferenceAngle(targetAngle) midOffset = midOffset + float64(decoration.GetReferenceLength()) } + dlr.link.midPad.Move(dlr.link.GetMidPosition()) + targetOffset := 0.0 for _, decoration := range dlr.link.TargetDecorations { decorationReferencePoint := fyne.Position{ - X: float32(float64(dlr.line.Position2.X) + math.Cos(targetAngle)*targetOffset), - Y: float32(float64(dlr.line.Position2.Y) - math.Sin(targetAngle)*targetOffset), + X: float32(float64(dlr.link.linkPoints[len(dlr.link.linkPoints)-1].Position().X) + math.Cos(targetAngle)*targetOffset), + Y: float32(float64(dlr.link.linkPoints[len(dlr.link.linkPoints)-1].Position().Y) - math.Sin(targetAngle)*targetOffset), } decoration.Move(decorationReferencePoint) decoration.SetReferenceAngle(targetAngle) targetOffset = targetOffset + float64(decoration.GetReferenceLength()) } for _, anchoredText := range dlr.link.sourceAnchoredText { - anchoredText.SetReferencePosition(fyne.Position{X: float32(dlr.link.sourcePoint.X), Y: float32(dlr.link.sourcePoint.Y)}) + anchoredText.SetReferencePosition(dlr.link.GetSourcePosition()) } for _, anchoredText := range dlr.link.midpointAnchoredText { - anchoredText.SetReferencePosition(fyne.Position{X: float32(dlr.link.midPoint.X), Y: float32(dlr.link.midPoint.Y)}) + anchoredText.SetReferencePosition(dlr.link.GetMidPosition()) } for _, anchoredText := range dlr.link.targetAnchoredText { - anchoredText.SetReferencePosition(fyne.Position{X: float32(dlr.link.targetPoint.X), Y: float32(dlr.link.targetPoint.Y)}) + anchoredText.SetReferencePosition(dlr.link.GetTargetPosition()) } -} -func (dlr *diagramLinkRenderer) Objects() []fyne.CanvasObject { - obj := []fyne.CanvasObject{ - dlr.line, + // Now we take care of property changes. + for _, linkSegment := range dlr.link.linkSegments { + linkSegment.Refresh() } - for _, sourceDecoration := range dlr.link.SourceDecorations { - if sourceDecoration != nil { - obj = append(obj, sourceDecoration) - } - } - for _, sourceAnchoredText := range dlr.link.sourceAnchoredText { - obj = append(obj, sourceAnchoredText) - } - for _, midpointDecoration := range dlr.link.MidpointDecorations { - if midpointDecoration != nil { - obj = append(obj, midpointDecoration) - } - } - for _, midpointAnchoredText := range dlr.link.midpointAnchoredText { - obj = append(obj, midpointAnchoredText) - } - for _, targetDecoration := range dlr.link.TargetDecorations { - if targetDecoration != nil { - obj = append(obj, targetDecoration) - } - } - for _, targetAnchoredText := range dlr.link.targetAnchoredText { - obj = append(obj, targetAnchoredText) - } - - return obj -} - -func (dlr *diagramLinkRenderer) Refresh() { for _, decoration := range dlr.link.SourceDecorations { decoration.SetStrokeColor(dlr.link.LinkColor) decoration.SetStrokeWidth(dlr.link.Width) @@ -242,4 +304,6 @@ func (dlr *diagramLinkRenderer) Refresh() { decoration.SetStrokeWidth(dlr.link.Width) decoration.Refresh() } + dlr.link.diagram.refreshDependentLinks(dlr.link) + ForceRepaint() } diff --git a/widget/diagramwidget/linkpoint.go b/widget/diagramwidget/linkpoint.go new file mode 100644 index 00000000..d15bd70e --- /dev/null +++ b/widget/diagramwidget/linkpoint.go @@ -0,0 +1,47 @@ +package diagramwidget + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +// LinkPoint identifies the point at which a link end is connected to another diagram element's connection pad +type LinkPoint struct { + widget.BaseWidget +} + +func NewLinkPoint(link *DiagramLink) *LinkPoint { + lp := &LinkPoint{} + lp.BaseWidget.ExtendBaseWidget(lp) + return lp +} + +func (lp *LinkPoint) CreateRenderer() fyne.WidgetRenderer { + lpr := &linkPointRenderer{} + return lpr +} + +// linkPointRenderer +type linkPointRenderer struct { +} + +func (lpr *linkPointRenderer) Destroy() { + +} + +func (lpr *linkPointRenderer) Layout(size fyne.Size) { + +} + +func (lpr *linkPointRenderer) MinSize() fyne.Size { + return fyne.NewSize(1, 1) +} + +func (lp *linkPointRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{} + return obj +} + +func (lp *linkPointRenderer) Refresh() { + +} diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go new file mode 100644 index 00000000..a3f06739 --- /dev/null +++ b/widget/diagramwidget/linksegment.go @@ -0,0 +1,72 @@ +package diagramwidget + +import ( + "math" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" +) + +type LinkSegment struct { + widget.BaseWidget + link *DiagramLink + p1 fyne.Position + p2 fyne.Position +} + +func NewLinkSegment(link *DiagramLink, p1 fyne.Position, p2 fyne.Position) *LinkSegment { + ls := &LinkSegment{ + link: link, + p1: p1, + p2: p2, + } + ls.BaseWidget.ExtendBaseWidget(ls) + return ls +} + +func (ls *LinkSegment) CreateRenderer() fyne.WidgetRenderer { + lsr := &linkSegmentRenderer{ + ls: ls, + line: canvas.NewLine(ls.link.diagram.GetForegroundColor()), + } + return lsr +} + +func (ls *LinkSegment) SetPoints(p1 fyne.Position, p2 fyne.Position) { + ls.p1 = p1 + ls.p2 = p2 + ls.Refresh() +} + +// linkSegmentRenderer +type linkSegmentRenderer struct { + ls *LinkSegment + line *canvas.Line +} + +func (lsr *linkSegmentRenderer) Destroy() { + +} + +func (lsr *linkSegmentRenderer) Layout(size fyne.Size) { +} + +func (lsr *linkSegmentRenderer) MinSize() fyne.Size { + return fyne.NewSize(float32(math.Abs(float64(lsr.ls.p1.X-lsr.ls.p2.X))), float32(math.Abs(float64(lsr.ls.p1.Y-lsr.ls.p2.Y)))) +} + +func (lsr *linkSegmentRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{ + lsr.line, + } + return obj +} + +func (lsr *linkSegmentRenderer) Refresh() { + lsr.line.Position1 = lsr.ls.p1 + lsr.line.Position2 = lsr.ls.p2 + lsr.line.StrokeColor = lsr.ls.link.LinkColor + lsr.line.StrokeWidth = lsr.ls.link.Width + ForceRepaint() +} diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 75fd5f36..e2de46e5 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -31,9 +31,9 @@ type DiagramNode struct { // InnerSize stores size that the inner object should have, may not // be respected if not large enough for the object. InnerSize fyne.Size - // InnerObject is the canvas object that should be drawn inside of + // innerObject is the canvas object that should be drawn inside of // the diagram node. - InnerObject fyne.CanvasObject + innerObject fyne.CanvasObject // Padding is the distance between the inner object's drawing area // and the box. Padding float32 @@ -52,7 +52,7 @@ type DiagramNode struct { func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string) *DiagramNode { dn := &DiagramNode{ InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, - InnerObject: obj, + innerObject: obj, Padding: diagram.DiagramTheme.Size(theme.SizeNamePadding), BoxStrokeWidth: 1, HandleColor: diagram.DiagramTheme.Color(theme.ColorNameForeground, diagram.ThemeVariant), @@ -67,7 +67,8 @@ func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string newHandle.Hide() } dn.ExtendBaseWidget(dn) - dn.diagram.Nodes[nodeID] = dn + dn.diagram.AddNode(dn) + dn.Refresh() return dn } @@ -85,25 +86,26 @@ func (dn *DiagramNode) CreateRenderer() fyne.WidgetRenderer { return &dnr } -func (dn *DiagramNode) Cursor() desktop.Cursor { - return desktop.DefaultCursor +func (dn *DiagramNode) Center() fyne.Position { + return fyne.Position{X: float32(dn.R2Center().X), Y: float32(dn.R2Center().Y)} } -func (dn *DiagramNode) Displace(delta fyne.Position) { - dn.Move(dn.Position().Add(delta)) +func (dn *DiagramNode) Cursor() desktop.Cursor { + return desktop.DefaultCursor } func (dn *DiagramNode) DragEnd() { } func (dn *DiagramNode) Dragged(event *fyne.DragEvent) { - delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} - dn.Displace(delta) - ForceRefresh() + dn.diagram.DiagramNodeDragged(dn, event) } func (dn *DiagramNode) effectiveInnerSize() fyne.Size { - return dn.InnerSize.Max(dn.InnerObject.MinSize()) + if dn.innerObject == nil { + return dn.InnerSize + } + return dn.InnerSize.Max(dn.innerObject.MinSize()) } func (dn *DiagramNode) findKeyForHandle(handle *Handle) string { @@ -115,6 +117,10 @@ func (dn *DiagramNode) findKeyForHandle(handle *Handle) string { return "" } +func (dn *DiagramNode) GetEdgePad() ConnectionPad { + return dn.edgePad +} + func (dn *DiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) { // determine which handle it is handleKey := dn.findKeyForHandle(handle) @@ -150,10 +156,10 @@ func (dn *DiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) { } dn.Move(dn.Position().Add(positionChange)) trialInnerSize := dn.InnerSize.Add(sizeChange) - dn.InnerSize = dn.InnerObject.MinSize().Max(trialInnerSize) + dn.InnerSize = dn.innerObject.MinSize().Max(trialInnerSize) dn.Resize(dn.Size().Add(sizeChange)) dn.Refresh() - ForceRefresh() + ForceRepaint() } func (dn *DiagramNode) innerPos() fyne.Position { @@ -165,19 +171,20 @@ func (dn *DiagramNode) innerPos() fyne.Position { func (dn *DiagramNode) MouseIn(event *desktop.MouseEvent) { dn.HandleColor = dn.diagram.DiagramTheme.Color(theme.ColorNameFocus, dn.diagram.ThemeVariant) - ForceRefresh() + ForceRepaint() } func (dn *DiagramNode) MouseOut() { dn.HandleColor = dn.diagram.DiagramTheme.Color(theme.ColorNameForeground, dn.diagram.ThemeVariant) - ForceRefresh() + ForceRepaint() } func (dn *DiagramNode) MouseMoved(event *desktop.MouseEvent) { } -func (dn *DiagramNode) R2Position() r2.Vec2 { - return r2.V2(float64(dn.Position().X), float64(dn.Position().Y)) +func (dn *DiagramNode) Move(position fyne.Position) { + dn.BaseWidget.Move(position) + dn.Refresh() } func (dn *DiagramNode) R2Box() r2.Box { @@ -194,8 +201,14 @@ func (dn *DiagramNode) R2Center() r2.Vec2 { return dn.R2Box().Center() } -func (dn *DiagramNode) Center() fyne.Position { - return fyne.Position{X: float32(dn.R2Center().X), Y: float32(dn.R2Center().Y)} +func (dn *DiagramNode) R2Position() r2.Vec2 { + return r2.V2(float64(dn.Position().X), float64(dn.Position().Y)) +} + +func (dn *DiagramNode) SetInnerObject(obj fyne.CanvasObject) { + dn.innerObject = obj + dn.Refresh() + dn.diagram.refreshDependentLinks(dn) } func (dn *DiagramNode) Tapped(event *fyne.PointEvent) { @@ -228,13 +241,29 @@ func (dnr *diagramNodeRenderer) MinSize() fyne.Size { } func (dnr *diagramNodeRenderer) Layout(size fyne.Size) { - nodeSize := dnr.MinSize().Max(size) +} + +func (dnr *diagramNodeRenderer) Objects() []fyne.CanvasObject { + obj := make([]fyne.CanvasObject, 0) + obj = append(obj, dnr.box) + obj = append(obj, dnr.node.edgePad) + obj = append(obj, dnr.node.innerObject) + for _, handle := range dnr.node.handles { + obj = append(obj, handle) + } + return obj +} + +func (dnr *diagramNodeRenderer) Refresh() { + nodeSize := dnr.MinSize() dnr.node.Resize(nodeSize) dnr.node.edgePad.Resize(nodeSize) dnr.node.edgePad.Move(fyne.NewPos(0, 0)) - dnr.node.InnerObject.Move(dnr.node.innerPos()) - dnr.node.InnerObject.Resize(dnr.node.effectiveInnerSize()) + if dnr.node.innerObject != nil { + dnr.node.innerObject.Move(dnr.node.innerPos()) + dnr.node.innerObject.Resize(dnr.node.effectiveInnerSize()) + } dnr.box.Resize(nodeSize) @@ -261,25 +290,11 @@ func (dnr *diagramNodeRenderer) Layout(size fyne.Size) { handle.Move(fyne.Position{X: width, Y: height}) } } -} -func (dnr *diagramNodeRenderer) Objects() []fyne.CanvasObject { - obj := make([]fyne.CanvasObject, 0) - obj = append(obj, dnr.box) - obj = append(obj, dnr.node.edgePad) - obj = append(obj, dnr.node.InnerObject) - for _, handle := range dnr.node.handles { - obj = append(obj, handle) - } - return obj -} - -func (dnr *diagramNodeRenderer) Refresh() { dnr.box.StrokeWidth = dnr.node.BoxStrokeWidth dnr.box.FillColor = color.Transparent dnr.box.StrokeColor = dnr.node.diagram.GetForegroundColor() - for _, e := range dnr.node.diagram.GetEdges(dnr.node) { - e.Refresh() - } dnr.node.edgePad.Refresh() + dnr.node.diagram.refreshDependentLinks(dnr.node) + ForceRepaint() } From 5b995195d6dfde28fbb58ac8a8e0fd4852f4e304 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 14 Apr 2023 08:09:44 -0400 Subject: [PATCH 09/53] Added documentation to AnchoredText --- widget/diagramwidget/anchoredtext.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index fd6e8ce6..a7a42563 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -24,6 +24,9 @@ type AnchoredText struct { ForegroundColor color.Color } +// NewAnchoredText creates an textual annotation for a link. After it is created, one of the +// three AddAnchoredText methods must be called on the link to actually associate the +// anchored text with the appropriate reference point on the link. func NewAnchoredText(text string) *AnchoredText { at := &AnchoredText{ displayedText: text, @@ -35,6 +38,7 @@ func NewAnchoredText(text string) *AnchoredText { return at } +// CreateRenderer is the required method for a widget extension func (at *AnchoredText) CreateRenderer() fyne.WidgetRenderer { atr := &anchoredTextRenderer{ widget: at, @@ -46,14 +50,18 @@ func (at *AnchoredText) CreateRenderer() fyne.WidgetRenderer { return atr } +// Displace moves the anchored text relative to its reference position. func (at *AnchoredText) Displace(delta fyne.Position) { at.Move(at.Position().Add(delta)) } +// DragEnd is one of the required methods for a draggable widget. It just refreshes the widget. func (at *AnchoredText) DragEnd() { at.Refresh() } +// Dragged is the required method for a draggable widget. It moves the anchored text +// relative to its reference position func (at *AnchoredText) Dragged(event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} at.Move(at.Position().Add(delta)) @@ -61,36 +69,45 @@ func (at *AnchoredText) Dragged(event *fyne.DragEvent) { ForceRepaint() } +// MinSize returns a fixed minimum size for the anchored text. func (at *AnchoredText) MinSize() fyne.Size { minSize := fyne.Size{Height: 25, Width: 50} return minSize } +// 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 func (at *AnchoredText) MouseMoved(event *desktop.MouseEvent) { } +// MousOut 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 +// and then calls the normal BaseWidget.Move method. func (at *AnchoredText) Move(position fyne.Position) { delta := r2.MakeVec2(float64(position.X-at.Position().X), float64(position.Y-at.Position().Y)) at.offset = at.offset.Add(delta) at.BaseWidget.Move(position) } +// SetForegroundColor sets the text color func (at *AnchoredText) SetForegroundColor(fc color.Color) { at.ForegroundColor = fc at.Refresh() } +// SetReferencePosition sets the reference position of the anchored text and calls +// the BaseWidget.Move() method to actually move the displayed text func (at *AnchoredText) SetReferencePosition(position fyne.Position) { delta := fyne.Delta{DX: float32(position.X - at.referencePosition.X), DY: float32(position.Y - at.referencePosition.Y)} // We don't want to change the offset here, so we call the BaseWidget.Move directly From d8e92c8b90920e9ea54b643bec2c17f1ada20ad2 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 14 Apr 2023 09:48:24 -0400 Subject: [PATCH 10/53] Updated Readme.md and DiagramWidget documentation --- widget/diagramwidget/README.md | 91 +++++++++++++++++++++++++--------- widget/diagramwidget/node.go | 4 ++ 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/widget/diagramwidget/README.md b/widget/diagramwidget/README.md index 23152253..2933b829 100644 --- a/widget/diagramwidget/README.md +++ b/widget/diagramwidget/README.md @@ -1,36 +1,81 @@ -# Fyne Hacks +# Fyne DiagramWidget -This repository contains a collection of widgets and programs for the -[Fyne](https://fyne.io/) toolkit. In general, code in here should not be -considered production-ready. Most of it is either things I'm experimenting -with, or prototypes of things I will later upstream. Expect -backwards-incompatible changes without warning. +This package contains a collection of widgets for the [Fyne](https://fyne.io/) +toolkit. The code here is intended to be production ready, but may be lacking +some desirable functional features. If you have suggestions for changes to +existing functionality or addition of new functionality, please look at the existing +issues in the repository to see if your idea is already on the table. If it is not, +feel free to open an issue. -If you have a question, comment, or patch, you should send it in via [my public -inbox](https://lists.sr.ht/~charles/public-inbox). +This collection should be considered a work in progress. When changes are made, +serious consideration will be given to backward compatibility, but compatibility +is not guaranteed. -## Hacks +## Diagram Widget -### Table Widget +The DiagramWidget is intended to be incorporated into a Fyne application. It provides a +drawing area within which a diagram can be created. The diagram itself is a collection of +DiagramElement widgets (an interface). There are two types of DiagramElements: DiagramNode widgets and DiagramLink widgets. DiagramNode widgets are thin wrappers around a user-supplied CanvasObject. +Any valid CanvasObject can be used. DiagramLinks are line-based connections between DiagramElements. +Note that links can connect to other links as well as nodes. -* [code](./table) -* [demo](./cmd/tabledemo) +While some provisions have been made for automatic layout, layouts are for the convenience +of the author and are on-demand only. The design intent is that users will place the diagram elements for human readability. +DiagramElements are essentially self-managed from a layout perspective. DiagramNodes have no size +constraints imposed by the DiagramWidget and can be placed anywhere. DiagramLinks connect +DiagramElements. The DiagramWidget keeps track of the DiagramElements to which each DiagramLink +is connected and calls the Refresh() method on the link when the connected diagram element is moved +or resized. -### Viewport Widget +* [demo](../../cmd/diagramdemo/main.go) -The viewport widget provides a canvas-like object which can be zoomed and -panned. +### DiagramElement Interface -* [code](./viewport) -* [demo](./cmd/viewportdemo) +A DiagramElement is the base interface for any element of the diagram being managed by the +DiagramWidget. It provides a common interface for DiagramNode and DiagramLink widgets. The DiagramElement +interface provides operations for retrieving the DiagramWidget, the ID of the DiagramElement, and +for showing and hiding the handles that are used for graphically manipulating the diagram element. +The specifics of what handles do are different for nodes and links - these are described below in the +sections for their respective widgets. -### Graph Widget +### DiagramNode Widget -The graph widget implements a graph visualization widget which supports -directed and un-directed edges. Widgets can be embedded within nodes, and nodes -can be moved around. +The DiagramNode widget is a wrapper around a user-supplied CanvasObject. In addition to the user-supplied +CanvasObject, the node displays a border and, when selected, handles at the corners and edge mid-points that can be used to manipulate the size of the node. The node can be selected and dragged to a new position with a mouse by clicking in the border area around the canvas object. -* [code](./graph) -* [demo](./cmd/graphdemo) +### DiagramLink Widget +The DiagramLink widget provides a directed line-based connection between two DiagramElements. +The link is defined in terms of LinkPoints that are connected by LinkSegments (both of which +are widgets in their own right). The link maintains an array of points, with the point at index +[0] being the point at which the link connects to the source DiagramElement and the point at the +last index being the point at which the link connects to the target DiagramElement. The link also +maintains an array of line segments, with the segment at index [0] connecting points [0] and [1], +the segment at index [1] connecting the points [1] and [2], etc. The current implementation only +has a single segment, but interfaces will be added shortly to enable the addition and removal of +points and segments. + +Many visual languages (formalized diagrams) utilize graphical decorations on lines. The link +provides the ability to add an arbitrary number of graphic decorations at three points along +the link: the source end, the target end, and the midpoint. Decorations are stacked in the order +they are added at the indicated point. The location of the source and target points is obvious, +but the midpoint bears some discussion. If there is only one line segment, the midpoint is the +midpoint of this segment. If there is more than one line segment, the "midpoint" is defined to +be the next to last point in the array of points. For a two-segment link, this will be the point +at which the two segments join. For a multi-segment link, this will be the point at which the +next-to-last and last segments join. + +Also common in visual languages are textual annotations associated with either the link as a whole +or to the ends of the link. For this purpose, the link allows the association of one or more +AnchoredText widgets with each of the reference points on the link: source, target, and midpoint. +These widgets keep track of their position relative to the link's reference points. They can +be moved interactively with the mouse to a new position. When the reference point on the link +moves, the anchored text will also move, maintaining its relative position. + +Users do not create AnchoredText widgets directly: the link itself creates and manages them. +the user calls AddAnchoredText(key, text) to add an anchored text. The key is expected +to be unique at the position and can be used to update the text later. The AnchoredText can also +be directly edited in the diagram. + +When a link connects to another link, it connects at the midpoint of the source or target link. \ No newline at end of file diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index e2de46e5..679f047e 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -49,6 +49,10 @@ type DiagramNode struct { edgePad *RectanglePad } +// NewDiagramNode creates a DiagramNode widget and adds it to the DiagramWidget. The user-supplied +// 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) *DiagramNode { dn := &DiagramNode{ InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, From cbe35450fe60d6db7ebb291ccb16a5c5c4b8dba3 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 14 Apr 2023 10:11:50 -0400 Subject: [PATCH 11/53] Fixed Readme.md formatting --- widget/diagramwidget/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/diagramwidget/README.md b/widget/diagramwidget/README.md index 2933b829..04c6eddd 100644 --- a/widget/diagramwidget/README.md +++ b/widget/diagramwidget/README.md @@ -74,7 +74,7 @@ be moved interactively with the mouse to a new position. When the reference poin moves, the anchored text will also move, maintaining its relative position. Users do not create AnchoredText widgets directly: the link itself creates and manages them. -the user calls AddAnchoredText(key, text) to add an anchored text. The key is expected +the user calls Add* *position* *AnchoredText(key, text) to add an anchored text. The key is expected to be unique at the position and can be used to update the text later. The AnchoredText can also be directly edited in the diagram. From a072838314a54e5926b03359573bf907f105ca59 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 14 Apr 2023 10:13:49 -0400 Subject: [PATCH 12/53] Update README.md --- widget/diagramwidget/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/diagramwidget/README.md b/widget/diagramwidget/README.md index 04c6eddd..f63001c1 100644 --- a/widget/diagramwidget/README.md +++ b/widget/diagramwidget/README.md @@ -74,7 +74,7 @@ be moved interactively with the mouse to a new position. When the reference poin moves, the anchored text will also move, maintaining its relative position. Users do not create AnchoredText widgets directly: the link itself creates and manages them. -the user calls Add* *position* *AnchoredText(key, text) to add an anchored text. The key is expected +the user calls Add*position*AnchoredText(key, text) to add an anchored text. The key is expected to be unique at the position and can be used to update the text later. The AnchoredText can also be directly edited in the diagram. From 5e96093a1819367523f3a58925dc04978f3e9256 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 14 Apr 2023 10:18:26 -0400 Subject: [PATCH 13/53] Update README.md --- widget/diagramwidget/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/diagramwidget/README.md b/widget/diagramwidget/README.md index f63001c1..13745a9a 100644 --- a/widget/diagramwidget/README.md +++ b/widget/diagramwidget/README.md @@ -74,7 +74,7 @@ be moved interactively with the mouse to a new position. When the reference poin moves, the anchored text will also move, maintaining its relative position. Users do not create AnchoredText widgets directly: the link itself creates and manages them. -the user calls Add*position*AnchoredText(key, text) to add an anchored text. The key is expected +the user calls Add\AnchoredText(key, text) to add an anchored text. The key is expected to be unique at the position and can be used to update the text later. The AnchoredText can also be directly edited in the diagram. From bd3d70f4be3bf11cd296242a1d43f7f98d448f92 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Mon, 17 Apr 2023 15:17:37 -0400 Subject: [PATCH 14/53] Documented ForceRepaint --- widget/diagramwidget/diagram.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 651ce5fe..f3f18fbd 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -12,8 +12,16 @@ import ( var Globaldiagram *DiagramWidget +// ForceRepaint is a workaround for a Fyne bug (Issue #2205) in which moving a canvas object does not +// trigger repainting. When the issue is resolved, this function and all references to it should be +// removed. The DummyBox on the GlobalDiagram should also be removed. +// The conditionals here are required during initialization. func ForceRepaint() { - Globaldiagram.DummyBox.Refresh() + if Globaldiagram != nil { + if Globaldiagram.DummyBox != nil { + Globaldiagram.DummyBox.Refresh() + } + } } // Verify that interfaces are fully implemented From f7a8fe32aa2799f2c31859281b0422f516b31301 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 18 Apr 2023 09:15:36 -0400 Subject: [PATCH 15/53] Removed .gitarchive, table and viewport widgets --- cmd/tabledemo/main.go | 80 ------- cmd/tabledemo/sample.csv | 9 - cmd/viewportdemo/main.go | 112 ---------- .../diagramwidget/.gitarchive/COMMIT_EDITMSG | 1 - widget/diagramwidget/.gitarchive/FETCH_HEAD | 1 - widget/diagramwidget/.gitarchive/HEAD | 1 - widget/diagramwidget/.gitarchive/config | 12 -- widget/diagramwidget/.gitarchive/description | 1 - .../.gitarchive/hooks/applypatch-msg.sample | 15 -- .../.gitarchive/hooks/commit-msg.sample | 24 --- .../hooks/fsmonitor-watchman.sample | 174 ---------------- .../.gitarchive/hooks/post-update.sample | 8 - .../.gitarchive/hooks/pre-applypatch.sample | 14 -- .../.gitarchive/hooks/pre-commit.sample | 49 ----- .../.gitarchive/hooks/pre-merge-commit.sample | 13 -- .../.gitarchive/hooks/pre-push.sample | 53 ----- .../.gitarchive/hooks/pre-rebase.sample | 169 --------------- .../.gitarchive/hooks/pre-receive.sample | 24 --- .../hooks/prepare-commit-msg.sample | 42 ---- .../.gitarchive/hooks/push-to-checkout.sample | 78 ------- .../.gitarchive/hooks/update.sample | 128 ------------ widget/diagramwidget/.gitarchive/index | Bin 2242 -> 0 bytes widget/diagramwidget/.gitarchive/info/exclude | 6 - widget/diagramwidget/.gitarchive/logs/HEAD | 2 - .../.gitarchive/logs/refs/heads/master | 2 - .../.gitarchive/logs/refs/remotes/origin/HEAD | 1 - .../17/405e5eeee11b3313baec5a5f30836880bcb6bb | Bin 391 -> 0 bytes .../6f/12fb35c43922c619da894876d6c1905ca4fd8c | Bin 1053 -> 0 bytes .../90/027c0cdc3acf4f23846a4e71b21a0b6661a3ab | Bin 52 -> 0 bytes .../ca/7d6114760e08cee5a2e622b98c97213732c16c | 2 - ...52e4c029b0d1030cb93a7bfb9152a2869365c2.idx | Bin 5664 -> 0 bytes ...2e4c029b0d1030cb93a7bfb9152a2869365c2.pack | Bin 47997 -> 0 bytes widget/diagramwidget/.gitarchive/packed-refs | 2 - .../.gitarchive/refs/heads/master | 1 - .../.gitarchive/refs/remotes/origin/HEAD | 1 - widget/diagramwidget/LICENSE | 30 --- widget/diagramwidget/table/table.go | 196 ------------------ widget/diagramwidget/viewport/viewport.go | 173 ---------------- 38 files changed, 1424 deletions(-) delete mode 100644 cmd/tabledemo/main.go delete mode 100644 cmd/tabledemo/sample.csv delete mode 100644 cmd/viewportdemo/main.go delete mode 100644 widget/diagramwidget/.gitarchive/COMMIT_EDITMSG delete mode 100644 widget/diagramwidget/.gitarchive/FETCH_HEAD delete mode 100644 widget/diagramwidget/.gitarchive/HEAD delete mode 100644 widget/diagramwidget/.gitarchive/config delete mode 100644 widget/diagramwidget/.gitarchive/description delete mode 100644 widget/diagramwidget/.gitarchive/hooks/applypatch-msg.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/commit-msg.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/fsmonitor-watchman.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/post-update.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-applypatch.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-commit.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-merge-commit.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-push.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-rebase.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/pre-receive.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/prepare-commit-msg.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/push-to-checkout.sample delete mode 100644 widget/diagramwidget/.gitarchive/hooks/update.sample delete mode 100644 widget/diagramwidget/.gitarchive/index delete mode 100644 widget/diagramwidget/.gitarchive/info/exclude delete mode 100644 widget/diagramwidget/.gitarchive/logs/HEAD delete mode 100644 widget/diagramwidget/.gitarchive/logs/refs/heads/master delete mode 100644 widget/diagramwidget/.gitarchive/logs/refs/remotes/origin/HEAD delete mode 100644 widget/diagramwidget/.gitarchive/objects/17/405e5eeee11b3313baec5a5f30836880bcb6bb delete mode 100644 widget/diagramwidget/.gitarchive/objects/6f/12fb35c43922c619da894876d6c1905ca4fd8c delete mode 100644 widget/diagramwidget/.gitarchive/objects/90/027c0cdc3acf4f23846a4e71b21a0b6661a3ab delete mode 100644 widget/diagramwidget/.gitarchive/objects/ca/7d6114760e08cee5a2e622b98c97213732c16c delete mode 100644 widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.idx delete mode 100644 widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.pack delete mode 100644 widget/diagramwidget/.gitarchive/packed-refs delete mode 100644 widget/diagramwidget/.gitarchive/refs/heads/master delete mode 100644 widget/diagramwidget/.gitarchive/refs/remotes/origin/HEAD delete mode 100644 widget/diagramwidget/LICENSE delete mode 100644 widget/diagramwidget/table/table.go delete mode 100644 widget/diagramwidget/viewport/viewport.go diff --git a/cmd/tabledemo/main.go b/cmd/tabledemo/main.go deleted file mode 100644 index 3d31a93b..00000000 --- a/cmd/tabledemo/main.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io/ioutil" - "strings" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/app" - "fyne.io/fyne/v2/dialog" - - "fyne.io/x/fyne/widget/diagramwidget/table" - - "github.com/rocketlaunchr/dataframe-go" - "github.com/rocketlaunchr/dataframe-go/imports" -) - -var mainTable *table.TableWidget - -func main() { - - s1 := dataframe.NewSeriesInt64("day", nil, 1, 2, 3, 4, 5, 6, 7, 8) - s2 := dataframe.NewSeriesFloat64("sales", nil, 50.3, 23.4, 56.2, nil, nil, 84.2, 72, 89) - s3 := dataframe.NewSeriesString("string!", nil, "foo", "bar", "three", "four", "five", "six", "seven", "eight") - df := dataframe.NewDataFrame(s1, s2, s3) - - fmt.Print(df.Table()) - - app := app.New() - w := app.NewWindow("Table Demo") - - w.SetMainMenu( - fyne.NewMainMenu( - fyne.NewMenu("File", - fyne.NewMenuItem("Load CSV", func() { - dialog.ShowFileOpen(func(uri fyne.URIReadCloser, e error) { - - if e != nil { - dialog.ShowError(e, w) - return - } - - content, err := ioutil.ReadAll(uri) - if err != nil { - dialog.ShowError(err, w) - return - } - text := string(content) - reader := strings.NewReader(text) - - ctx := context.Background() - opts := imports.CSVLoadOptions{ - InferDataTypes: true, - } - loaded, err := imports.LoadFromCSV(ctx, reader, opts) - - if err != nil { - dialog.ShowError(e, w) - return - } - - fmt.Printf("loaded new table:\n%s\n", loaded.Table()) - - mainTable.ReplaceDataFrame(loaded) - - }, w) - }), - ), - ), - ) - w.SetMaster() - - mainTable = table.NewTableWidget(df) - - w.SetContent(mainTable) - - w.ShowAndRun() - -} diff --git a/cmd/tabledemo/sample.csv b/cmd/tabledemo/sample.csv deleted file mode 100644 index 756448cb..00000000 --- a/cmd/tabledemo/sample.csv +++ /dev/null @@ -1,9 +0,0 @@ -Country,Date,Age,Amount,Id -"United States",2012-02-01,50,112.1,01234 -"United States",2012-02-01,32,321.31,54320 -"United Kingdom",2012-02-01,17,18.2,12345 -"United States",2012-02-01,32,321.31,54320 -"United Kingdom",2015-05-07,NA,18.2,12345 -"United States",2012-02-01,32,321.31,54320 -"United States",2012-02-01,32,321.31,54320 -Spain,2012-02-01,66,555.42,00241 diff --git a/cmd/viewportdemo/main.go b/cmd/viewportdemo/main.go deleted file mode 100644 index c3f4475b..00000000 --- a/cmd/viewportdemo/main.go +++ /dev/null @@ -1,112 +0,0 @@ -package main - -import ( - "fmt" - "image/color" - "strconv" - - "fyne.io/x/fyne/widget/diagramwidget/viewport" - - "fyne.io/fyne/v2/app" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/widget" - - "github.com/pkg/profile" -) - -func main() { - - defer profile.Start(profile.MemProfile).Stop() - - app := app.New() - w := app.NewWindow("Viewport Demo") - - w.SetMaster() - - stepSize := 1.0 - - stepSizeEntry := widget.NewEntry() - stepSizeEntry.OnChanged = func(text string) { - var err error - stepSize, err = strconv.ParseFloat(text, 64) - if err != nil { - stepSizeEntry.SetText(fmt.Sprintf("%f", stepSize)) - } - } - stepSizeEntry.SetText("1.0") - - vp := viewport.NewViewportWidget(800, 600) - - vp.Objects = append(vp.Objects, &viewport.ViewportLine{ - X1: 20, - Y1: 20, - X2: 200, - Y2: 400, - StrokeColor: color.RGBA{255, 255, 255, 255}, - StrokeWidth: 1, - }) - - vp.Objects = append(vp.Objects, &viewport.ViewportLine{ - X1: 40, - Y1: 20, - X2: 220, - Y2: 400, - StrokeColor: color.RGBA{255, 128, 128, 255}, - StrokeWidth: 3, - }) - - vp.Objects = append(vp.Objects, &viewport.ViewportLine{ - X1: 10, - Y1: 200, - X2: 300, - Y2: 10, - StrokeColor: color.RGBA{64, 255, 64, 255}, - StrokeWidth: 0.5, - }) - - w.SetContent(container.NewHSplit( - vp, - container.NewVBox( - stepSizeEntry, - widget.NewButton("Pan Left", func() { - fmt.Printf("vp.XOffset %v", vp.XOffset) - vp.XOffset += vp.Zoom * stepSize - vp.Refresh() - fmt.Printf(" -> %v\n", vp.XOffset) - }), - widget.NewButton("Pan Right", func() { - fmt.Printf("vp.XOffset %v", vp.XOffset) - vp.XOffset -= vp.Zoom * stepSize - vp.Refresh() - fmt.Printf(" -> %v\n", vp.XOffset) - }), - widget.NewButton("Pan Up", func() { - fmt.Printf("vp.YOffset %v", vp.YOffset) - vp.YOffset += vp.Zoom * stepSize - vp.Refresh() - fmt.Printf(" -> %v\n", vp.YOffset) - }), - widget.NewButton("Pan Down", func() { - fmt.Printf("vp.YOffset %v", vp.YOffset) - vp.YOffset -= vp.Zoom * stepSize - vp.Refresh() - fmt.Printf(" -> %v\n", vp.YOffset) - }), - widget.NewButton("Zoom In", func() { - fmt.Printf("vp.Zoom %v", vp.Zoom) - vp.Zoom *= 1.15 - vp.Refresh() - fmt.Printf(" -> %v\n", vp.Zoom) - }), - widget.NewButton("Zoom Out", func() { - fmt.Printf("vp.Zoom %v", vp.Zoom) - vp.Zoom *= 0.85 - vp.Refresh() - fmt.Printf(" -> %v\n", vp.Zoom) - }), - ), - )) - - w.ShowAndRun() - -} diff --git a/widget/diagramwidget/.gitarchive/COMMIT_EDITMSG b/widget/diagramwidget/.gitarchive/COMMIT_EDITMSG deleted file mode 100644 index 0f7020e6..00000000 --- a/widget/diagramwidget/.gitarchive/COMMIT_EDITMSG +++ /dev/null @@ -1 +0,0 @@ -Change terminology: graph to diagram diff --git a/widget/diagramwidget/.gitarchive/FETCH_HEAD b/widget/diagramwidget/.gitarchive/FETCH_HEAD deleted file mode 100644 index 0e2a37bd..00000000 --- a/widget/diagramwidget/.gitarchive/FETCH_HEAD +++ /dev/null @@ -1 +0,0 @@ -583b47634a43f829bce77cb7b9203108ba165d5d branch 'master' of https://git.sr.ht/~charles/fynehax diff --git a/widget/diagramwidget/.gitarchive/HEAD b/widget/diagramwidget/.gitarchive/HEAD deleted file mode 100644 index cb089cd8..00000000 --- a/widget/diagramwidget/.gitarchive/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/master diff --git a/widget/diagramwidget/.gitarchive/config b/widget/diagramwidget/.gitarchive/config deleted file mode 100644 index 92768d4f..00000000 --- a/widget/diagramwidget/.gitarchive/config +++ /dev/null @@ -1,12 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = false - bare = false - logallrefupdates = true - ignorecase = true -[remote "origin"] - url = https://git.sr.ht/~charles/fynehax - fetch = +refs/heads/*:refs/remotes/origin/* -[branch "master"] - remote = origin - merge = refs/heads/master diff --git a/widget/diagramwidget/.gitarchive/description b/widget/diagramwidget/.gitarchive/description deleted file mode 100644 index 498b267a..00000000 --- a/widget/diagramwidget/.gitarchive/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/widget/diagramwidget/.gitarchive/hooks/applypatch-msg.sample b/widget/diagramwidget/.gitarchive/hooks/applypatch-msg.sample deleted file mode 100644 index a5d7b84a..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/applypatch-msg.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message taken by -# applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the commit message file. -# -# To enable this hook, rename this file to "applypatch-msg". - -. git-sh-setup -commitmsg="$(git rev-parse --git-path hooks/commit-msg)" -test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} -: diff --git a/widget/diagramwidget/.gitarchive/hooks/commit-msg.sample b/widget/diagramwidget/.gitarchive/hooks/commit-msg.sample deleted file mode 100644 index b58d1184..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/commit-msg.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message. -# Called by "git commit" with one argument, the name of the file -# that has the commit message. The hook should exit with non-zero -# status after issuing an appropriate message if it wants to stop the -# commit. The hook is allowed to edit the commit message file. -# -# To enable this hook, rename this file to "commit-msg". - -# Uncomment the below to add a Signed-off-by line to the message. -# Doing this in a hook is a bad idea in general, but the prepare-commit-msg -# hook is more suited to it. -# -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" - -# This example catches duplicate Signed-off-by lines. - -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - exit 1 -} diff --git a/widget/diagramwidget/.gitarchive/hooks/fsmonitor-watchman.sample b/widget/diagramwidget/.gitarchive/hooks/fsmonitor-watchman.sample deleted file mode 100644 index 23e856f5..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/fsmonitor-watchman.sample +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use IPC::Open2; - -# An example hook script to integrate Watchman -# (https://facebook.github.io/watchman/) with git to speed up detecting -# new and modified files. -# -# The hook is passed a version (currently 2) and last update token -# formatted as a string and outputs to stdout a new update token and -# all files that have been modified since the update token. Paths must -# be relative to the root of the working tree and separated by a single NUL. -# -# To enable this hook, rename this file to "query-watchman" and set -# 'git config core.fsmonitor .git/hooks/query-watchman' -# -my ($version, $last_update_token) = @ARGV; - -# Uncomment for debugging -# print STDERR "$0 $version $last_update_token\n"; - -# Check the hook interface version -if ($version ne 2) { - die "Unsupported query-fsmonitor hook version '$version'.\n" . - "Falling back to scanning...\n"; -} - -my $git_work_tree = get_working_dir(); - -my $retry = 1; - -my $json_pkg; -eval { - require JSON::XS; - $json_pkg = "JSON::XS"; - 1; -} or do { - require JSON::PP; - $json_pkg = "JSON::PP"; -}; - -launch_watchman(); - -sub launch_watchman { - my $o = watchman_query(); - if (is_work_tree_watched($o)) { - output_result($o->{clock}, @{$o->{files}}); - } -} - -sub output_result { - my ($clockid, @files) = @_; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # binmode $fh, ":utf8"; - # print $fh "$clockid\n@files\n"; - # close $fh; - - binmode STDOUT, ":utf8"; - print $clockid; - print "\0"; - local $, = "\0"; - print @files; -} - -sub watchman_clock { - my $response = qx/watchman clock "$git_work_tree"/; - die "Failed to get clock id on '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - - return $json_pkg->new->utf8->decode($response); -} - -sub watchman_query { - my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') - or die "open2() failed: $!\n" . - "Falling back to scanning...\n"; - - # In the query expression below we're asking for names of files that - # changed since $last_update_token but not from the .git folder. - # - # To accomplish this, we're using the "since" generator to use the - # recency index to select candidate nodes and "fields" to limit the - # output to file names only. Then we're using the "expression" term to - # further constrain the results. - my $last_update_line = ""; - if (substr($last_update_token, 0, 1) eq "c") { - $last_update_token = "\"$last_update_token\""; - $last_update_line = qq[\n"since": $last_update_token,]; - } - my $query = <<" END"; - ["query", "$git_work_tree", {$last_update_line - "fields": ["name"], - "expression": ["not", ["dirname", ".git"]] - }] - END - - # Uncomment for debugging the watchman query - # open (my $fh, ">", ".git/watchman-query.json"); - # print $fh $query; - # close $fh; - - print CHLD_IN $query; - close CHLD_IN; - my $response = do {local $/; }; - - # Uncomment for debugging the watch response - # open ($fh, ">", ".git/watchman-response.json"); - # print $fh $response; - # close $fh; - - die "Watchman: command returned no output.\n" . - "Falling back to scanning...\n" if $response eq ""; - die "Watchman: command returned invalid output: $response\n" . - "Falling back to scanning...\n" unless $response =~ /^\{/; - - return $json_pkg->new->utf8->decode($response); -} - -sub is_work_tree_watched { - my ($output) = @_; - my $error = $output->{error}; - if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { - $retry--; - my $response = qx/watchman watch "$git_work_tree"/; - die "Failed to make watchman watch '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - $output = $json_pkg->new->utf8->decode($response); - $error = $output->{error}; - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # close $fh; - - # Watchman will always return all files on the first query so - # return the fast "everything is dirty" flag to git and do the - # Watchman query just to get it over with now so we won't pay - # the cost in git to look up each individual file. - my $o = watchman_clock(); - $error = $output->{error}; - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - output_result($o->{clock}, ("/")); - $last_update_token = $o->{clock}; - - eval { launch_watchman() }; - return 0; - } - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - return 1; -} - -sub get_working_dir { - my $working_dir; - if ($^O =~ 'msys' || $^O =~ 'cygwin') { - $working_dir = Win32::GetCwd(); - $working_dir =~ tr/\\/\//; - } else { - require Cwd; - $working_dir = Cwd::cwd(); - } - - return $working_dir; -} diff --git a/widget/diagramwidget/.gitarchive/hooks/post-update.sample b/widget/diagramwidget/.gitarchive/hooks/post-update.sample deleted file mode 100644 index ec17ec19..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/post-update.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare a packed repository for use over -# dumb transports. -# -# To enable this hook, rename this file to "post-update". - -exec git update-server-info diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-applypatch.sample b/widget/diagramwidget/.gitarchive/hooks/pre-applypatch.sample deleted file mode 100644 index 4142082b..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/pre-applypatch.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -. git-sh-setup -precommit="$(git rev-parse --git-path hooks/pre-commit)" -test -x "$precommit" && exec "$precommit" ${1+"$@"} -: diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-commit.sample b/widget/diagramwidget/.gitarchive/hooks/pre-commit.sample deleted file mode 100644 index e144712c..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/pre-commit.sample +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=$(git hash-object -t tree /dev/null) -fi - -# If you want to allow non-ASCII filenames set this variable to true. -allownonascii=$(git config --type=bool hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - -# Cross platform projects tend to avoid non-ASCII filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - cat <<\EOF -Error: Attempt to add a non-ASCII file name. - -This can cause problems if you want to work with people on other platforms. - -To be portable it is advisable to rename the file. - -If you know what you are doing you can disable this check using: - - git config hooks.allownonascii true -EOF - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -exec git diff-index --check --cached $against -- diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-merge-commit.sample b/widget/diagramwidget/.gitarchive/hooks/pre-merge-commit.sample deleted file mode 100644 index 399eab19..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/pre-merge-commit.sample +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git merge" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message to -# stderr if it wants to stop the merge commit. -# -# To enable this hook, rename this file to "pre-merge-commit". - -. git-sh-setup -test -x "$GIT_DIR/hooks/pre-commit" && - exec "$GIT_DIR/hooks/pre-commit" -: diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-push.sample b/widget/diagramwidget/.gitarchive/hooks/pre-push.sample deleted file mode 100644 index 4ce688d3..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/pre-push.sample +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -# An example hook script to verify what is about to be pushed. Called by "git -# push" after it has checked the remote status, but before anything has been -# pushed. If this script exits with a non-zero status nothing will be pushed. -# -# This hook is called with the following parameters: -# -# $1 -- Name of the remote to which the push is being done -# $2 -- URL to which the push is being done -# -# If pushing without using a named remote those arguments will be equal. -# -# Information about the commits which are being pushed is supplied as lines to -# the standard input in the form: -# -# -# -# This sample shows how to prevent push of commits where the log message starts -# with "WIP" (work in progress). - -remote="$1" -url="$2" - -zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" - exit 1 - fi - fi -done - -exit 0 diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-rebase.sample b/widget/diagramwidget/.gitarchive/hooks/pre-rebase.sample deleted file mode 100644 index 6cbef5c3..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/pre-rebase.sample +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006, 2008 Junio C Hamano -# -# The "pre-rebase" hook is run just before "git rebase" starts doing -# its job, and can prevent the command from running by exiting with -# non-zero status. -# -# The hook is called with the following parameters: -# -# $1 -- the upstream the series was forked from. -# $2 -- the branch being rebased (or empty when rebasing the current branch). -# -# This sample shows how to prevent topic branches that are already -# merged to 'next' branch from getting rebased, because allowing it -# would result in rebasing already published history. - -publish=next -basebranch="$1" -if test "$#" = 2 -then - topic="refs/heads/$2" -else - topic=`git symbolic-ref HEAD` || - exit 0 ;# we do not interrupt rebasing detached HEAD -fi - -case "$topic" in -refs/heads/??/*) - ;; -*) - exit 0 ;# we do not interrupt others. - ;; -esac - -# Now we are dealing with a topic branch being rebased -# on top of master. Is it OK to rebase it? - -# Does the topic really exist? -git show-ref -q "$topic" || { - echo >&2 "No such branch $topic" - exit 1 -} - -# Is topic fully merged to master? -not_in_master=`git rev-list --pretty=oneline ^master "$topic"` -if test -z "$not_in_master" -then - echo >&2 "$topic is fully merged to master; better remove it." - exit 1 ;# we could allow it, but there is no point. -fi - -# Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git rev-list ^master ${publish} | sort` -if test "$only_next_1" = "$only_next_2" -then - not_in_topic=`git rev-list "^$topic" master` - if test -z "$not_in_topic" - then - echo >&2 "$topic is already up to date with master" - exit 1 ;# we could allow it, but there is no point. - else - exit 0 - fi -else - not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` - /usr/bin/perl -e ' - my $topic = $ARGV[0]; - my $msg = "* $topic has commits already merged to public branch:\n"; - my (%not_in_next) = map { - /^([0-9a-f]+) /; - ($1 => 1); - } split(/\n/, $ARGV[1]); - for my $elem (map { - /^([0-9a-f]+) (.*)$/; - [$1 => $2]; - } split(/\n/, $ARGV[2])) { - if (!exists $not_in_next{$elem->[0]}) { - if ($msg) { - print STDERR $msg; - undef $msg; - } - print STDERR " $elem->[1]\n"; - } - } - ' "$topic" "$not_in_next" "$not_in_master" - exit 1 -fi - -<<\DOC_END - -This sample hook safeguards topic branches that have been -published from being rewound. - -The workflow assumed here is: - - * Once a topic branch forks from "master", "master" is never - merged into it again (either directly or indirectly). - - * Once a topic branch is fully cooked and merged into "master", - it is deleted. If you need to build on top of it to correct - earlier mistakes, a new topic branch is created by forking at - the tip of the "master". This is not strictly necessary, but - it makes it easier to keep your history simple. - - * Whenever you need to test or publish your changes to topic - branches, merge them into "next" branch. - -The script, being an example, hardcodes the publish branch name -to be "next", but it is trivial to make it configurable via -$GIT_DIR/config mechanism. - -With this workflow, you would want to know: - -(1) ... if a topic branch has ever been merged to "next". Young - topic branches can have stupid mistakes you would rather - clean up before publishing, and things that have not been - merged into other branches can be easily rebased without - affecting other people. But once it is published, you would - not want to rewind it. - -(2) ... if a topic branch has been fully merged to "master". - Then you can delete it. More importantly, you should not - build on top of it -- other people may already want to - change things related to the topic as patches against your - "master", so if you need further changes, it is better to - fork the topic (perhaps with the same name) afresh from the - tip of "master". - -Let's look at this example: - - o---o---o---o---o---o---o---o---o---o "next" - / / / / - / a---a---b A / / - / / / / - / / c---c---c---c B / - / / / \ / - / / / b---b C \ / - / / / / \ / - ---o---o---o---o---o---o---o---o---o---o---o "master" - - -A, B and C are topic branches. - - * A has one fix since it was merged up to "next". - - * B has finished. It has been fully merged up to "master" and "next", - and is ready to be deleted. - - * C has not merged to "next" at all. - -We would want to allow C to be rebased, refuse A, and encourage -B to be deleted. - -To compute (1): - - git rev-list ^master ^topic next - git rev-list ^master next - - if these match, topic has not merged in next at all. - -To compute (2): - - git rev-list master..topic - - if this is empty, it is fully merged to "master". - -DOC_END diff --git a/widget/diagramwidget/.gitarchive/hooks/pre-receive.sample b/widget/diagramwidget/.gitarchive/hooks/pre-receive.sample deleted file mode 100644 index a1fd29ec..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/pre-receive.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to make use of push options. -# The example simply echoes all push options that start with 'echoback=' -# and rejects all pushes when the "reject" push option is used. -# -# To enable this hook, rename this file to "pre-receive". - -if test -n "$GIT_PUSH_OPTION_COUNT" -then - i=0 - while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" - do - eval "value=\$GIT_PUSH_OPTION_$i" - case "$value" in - echoback=*) - echo "echo from the pre-receive-hook: ${value#*=}" >&2 - ;; - reject) - exit 1 - esac - i=$((i + 1)) - done -fi diff --git a/widget/diagramwidget/.gitarchive/hooks/prepare-commit-msg.sample b/widget/diagramwidget/.gitarchive/hooks/prepare-commit-msg.sample deleted file mode 100644 index 10fa14c5..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/prepare-commit-msg.sample +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare the commit log message. -# Called by "git commit" with the name of the file that has the -# commit message, followed by the description of the commit -# message's source. The hook's purpose is to edit the commit -# message file. If the hook fails with a non-zero status, -# the commit is aborted. -# -# To enable this hook, rename this file to "prepare-commit-msg". - -# This hook includes three examples. The first one removes the -# "# Please enter the commit message..." help message. -# -# The second includes the output of "git diff --name-status -r" -# into the message, just before the "git status" output. It is -# commented because it doesn't cope with --amend or with squashed -# commits. -# -# The third example adds a Signed-off-by line to the message, that can -# still be edited. This is rarely a good idea. - -COMMIT_MSG_FILE=$1 -COMMIT_SOURCE=$2 -SHA1=$3 - -/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" - -# case "$COMMIT_SOURCE,$SHA1" in -# ,|template,) -# /usr/bin/perl -i.bak -pe ' -# print "\n" . `git diff --cached --name-status -r` -# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; -# *) ;; -# esac - -# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" -# if test -z "$COMMIT_SOURCE" -# then -# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" -# fi diff --git a/widget/diagramwidget/.gitarchive/hooks/push-to-checkout.sample b/widget/diagramwidget/.gitarchive/hooks/push-to-checkout.sample deleted file mode 100644 index af5a0c00..00000000 --- a/widget/diagramwidget/.gitarchive/hooks/push-to-checkout.sample +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/sh - -# An example hook script to update a checked-out tree on a git push. -# -# This hook is invoked by git-receive-pack(1) when it reacts to git -# push and updates reference(s) in its repository, and when the push -# tries to update the branch that is currently checked out and the -# receive.denyCurrentBranch configuration variable is set to -# updateInstead. -# -# By default, such a push is refused if the working tree and the index -# of the remote repository has any difference from the currently -# checked out commit; when both the working tree and the index match -# the current commit, they are updated to match the newly pushed tip -# of the branch. This hook is to be used to override the default -# behaviour; however the code below reimplements the default behaviour -# as a starting point for convenient modification. -# -# The hook receives the commit with which the tip of the current -# branch is going to be updated: -commit=$1 - -# It can exit with a non-zero status to refuse the push (when it does -# so, it must not modify the index or the working tree). -die () { - echo >&2 "$*" - exit 1 -} - -# Or it can make any necessary changes to the working tree and to the -# index to bring them to the desired state when the tip of the current -# branch is updated to the new commit, and exit with a zero status. -# -# For example, the hook can simply run git read-tree -u -m HEAD "$1" -# in order to emulate git fetch that is run in the reverse direction -# with git push, as the two-tree form of git read-tree -u -m is -# essentially the same as git switch or git checkout that switches -# branches while keeping the local changes in the working tree that do -# not interfere with the difference between the branches. - -# The below is a more-or-less exact translation to shell of the C code -# for the default behaviour for git's push-to-checkout hook defined in -# the push_to_deploy() function in builtin/receive-pack.c. -# -# Note that the hook will be executed from the repository directory, -# not from the working tree, so if you want to perform operations on -# the working tree, you will have to adapt your code accordingly, e.g. -# by adding "cd .." or using relative paths. - -if ! git update-index -q --ignore-submodules --refresh -then - die "Up-to-date check failed" -fi - -if ! git diff-files --quiet --ignore-submodules -- -then - die "Working directory has unstaged changes" -fi - -# This is a rough translation of: -# -# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX -if git cat-file -e HEAD 2>/dev/null -then - head=HEAD -else - head=$(git hash-object -t tree --stdin &2 - echo " (if you want, you could supply GIT_DIR then run" >&2 - echo " $0 )" >&2 - exit 1 -fi - -if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then - echo "usage: $0 " >&2 - exit 1 -fi - -# --- Config -allowunannotated=$(git config --type=bool hooks.allowunannotated) -allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) -denycreatebranch=$(git config --type=bool hooks.denycreatebranch) -allowdeletetag=$(git config --type=bool hooks.allowdeletetag) -allowmodifytag=$(git config --type=bool hooks.allowmodifytag) - -# check for no description -projectdesc=$(sed -e '1q' "$GIT_DIR/description") -case "$projectdesc" in -"Unnamed repository"* | "") - echo "*** Project description file hasn't been set" >&2 - exit 1 - ;; -esac - -# --- Check types -# if $newrev is 0000...0000, it's a commit to delete a ref. -zero=$(git hash-object --stdin &2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 - exit 1 - fi - ;; - refs/tags/*,delete) - # delete tag - if [ "$allowdeletetag" != "true" ]; then - echo "*** Deleting a tag is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/tags/*,tag) - # annotated tag - if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 - then - echo "*** Tag '$refname' already exists." >&2 - echo "*** Modifying a tag is not allowed in this repository." >&2 - exit 1 - fi - ;; - refs/heads/*,commit) - # branch - if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then - echo "*** Creating a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/heads/*,delete) - # delete branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/remotes/*,commit) - # tracking branch - ;; - refs/remotes/*,delete) - # delete tracking branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a tracking branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - *) - # Anything else (is there anything else?) - echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 - exit 1 - ;; -esac - -# --- Finished -exit 0 diff --git a/widget/diagramwidget/.gitarchive/index b/widget/diagramwidget/.gitarchive/index deleted file mode 100644 index 06a3f74c984aaaaf2e31f45ab20a64a11eff2bd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2242 zcmZ?q402{*U|<4bu@vqo8}p)&D=FNO+jx)Hc>u+rpm7O|#lXNg<788T`C#?UXQhM)QVqo|2bawR%b_Hqzn{!bMV$Rd|NNzzkhxxp+s8aC5sjrVlIz9Lx zwRd+Kx8=Mm44gr(jxN5gdbueeb8H2!)VP4$!^culfZaWU4ZAZeU7oW99+?vr;+&s; z^%8IG)no>-#G<17@{H8P6n!{LFFhY*2FP3?#~l!Jl`j6kZZ2mFhg#1?DeHN)Leq6l z_TJcTe6J*KCWB~lZi;?-QDQ+xN@{Mter{rB9@JQ{xxz*27r^c{Vh_P?F57h7GdCAZ zb-vc!>ALP^SG&fmiq$ncA?B7OCgr4JHCKeqN&sxGqr)HU<}&7&rg)s*r?przv&D)h z==@qU@5ky23=$ysBFrsL%q_@C)k`iegM@>q*$fV_xdCN+u$#+yJ=HtUyn=zxNW;E< z)}!p_njCD-J0RwkWu}%F;KXz-}(r+kBzlrbjH5j!E9? z^eDS_a6-(Izda26DVd4D;LX*~$;``!ge=HBG5gdSush?|;YbGz;ng?xt96CMW!jeO z%SdKOuPKgv^q4^;JvBc!wWO$0AI5-17dXDeJ(n&4n_H)N0J}T+_HMi#RQ~AfwoM)O z@xktXx7BiO0?#uD!Hm-{GSW}VuYj8h30LulcP+r?_Qv8cmw_ikI4j*@<`1iHjjO{A zPKca5{ydN?&+%OcT}=0U3yHQ@X)cnvOAtI z2*b<*xfd9=sR##y!dG&7u>{!M)yH07HG^uO`6(bx zK=;{LEXah!)6HBPgh9yV$ttU<6OY;Hi8`e(OMs!5@glqz)OHdi2pO#;goSKtZnO|B04>*vyGI__& zfXx;2*THTsr=I?+sNTJgSF8!XAfhV$eEOC0T3HhYK8U$s5^5sIJXz<1C1CSZn`5w> zCuZ+-JE6@W%_zB3JnzkH_QX>v@prRe{?5x!LGia-Ne9H=g|l(wN0H`ikrVglo=;l* z_VEjQ-lpy_qH{YcJyZiXhmHqy^=#kccK6kcbNf$Ls zj{N`sSDrxxRtD+A81Qfm333HiM~qbrMn($eT;dLKaqk{V8w>Ax6BTdJoYAmn+iqal z$Y7#i%(XCCd!m?C-Hqe|$;aP*q+9g}+WC zxn|b?`4!V$?r{|vfo;wWJkT%yV0Hgd=KY@S!t19!vRrl;=6JB-A~rV-B9ou;M1& diff --git a/widget/diagramwidget/.gitarchive/info/exclude b/widget/diagramwidget/.gitarchive/info/exclude deleted file mode 100644 index a5196d1b..00000000 --- a/widget/diagramwidget/.gitarchive/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/widget/diagramwidget/.gitarchive/logs/HEAD b/widget/diagramwidget/.gitarchive/logs/HEAD deleted file mode 100644 index bd12ddeb..00000000 --- a/widget/diagramwidget/.gitarchive/logs/HEAD +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 583b47634a43f829bce77cb7b9203108ba165d5d pbrown12303 1678465462 -0500 clone: from https://git.sr.ht/~charles/fynehax -583b47634a43f829bce77cb7b9203108ba165d5d ca7d6114760e08cee5a2e622b98c97213732c16c pbrown12303 1678546829 -0500 commit: Change terminology: graph to diagram diff --git a/widget/diagramwidget/.gitarchive/logs/refs/heads/master b/widget/diagramwidget/.gitarchive/logs/refs/heads/master deleted file mode 100644 index bd12ddeb..00000000 --- a/widget/diagramwidget/.gitarchive/logs/refs/heads/master +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 583b47634a43f829bce77cb7b9203108ba165d5d pbrown12303 1678465462 -0500 clone: from https://git.sr.ht/~charles/fynehax -583b47634a43f829bce77cb7b9203108ba165d5d ca7d6114760e08cee5a2e622b98c97213732c16c pbrown12303 1678546829 -0500 commit: Change terminology: graph to diagram diff --git a/widget/diagramwidget/.gitarchive/logs/refs/remotes/origin/HEAD b/widget/diagramwidget/.gitarchive/logs/refs/remotes/origin/HEAD deleted file mode 100644 index f986a32f..00000000 --- a/widget/diagramwidget/.gitarchive/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 583b47634a43f829bce77cb7b9203108ba165d5d pbrown12303 1678465462 -0500 clone: from https://git.sr.ht/~charles/fynehax diff --git a/widget/diagramwidget/.gitarchive/objects/17/405e5eeee11b3313baec5a5f30836880bcb6bb b/widget/diagramwidget/.gitarchive/objects/17/405e5eeee11b3313baec5a5f30836880bcb6bb deleted file mode 100644 index 64609ade5a1bf80027a511dd65b87d0d9fc9c1ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 391 zcmV;20eJp+0V^p=O;s>4G-5C`FfcPQQP4}zEXhpI%P&f0_}XAre4JZW%lB>Mq0o%o zC2iG}9Z*$1p3bg*!LAH7>)x{kE>HQ$bW>se6V`uGDZTeDL6ro#I=c9}>gA>|D2plu zKb-pdXr$AF4^n$~r*T`(yJBJh1PX~oMfv3!sfj5Jvt2yGzGTN&&pq1Gz&bn1?QXOD zTZo!upa~0;wI_;M)!j%gkbL~@N4oW2qg9y@*_6!0^rFOEh6zkHJa??l`zyC(`4w)G z;!aCkyc(h+JvBc!wWO$$!SSkk;OgcU{yL51npywnS4?-g#|3q3dcIz6ehR~@cBK>3 zUq4&V-4JAW#jt<(+qg$B5GsmGa~Y;iJZ7gS>Xg>t<~66*OZ9!n{OxT}Yl;#JG8p#G zu((w=iMe&no4OTqgrc4Qh)y^IkuOP1%1LFoy>>+i*Z!B+SIu~9eO4_)Ytp&8yAb)Z l%+&IN{Gt*D5u2L^k;%__pBcEX{OatfBZ~q> z$99YCxA%?|8O7;hVEGc=@pv!KJ-qX@Qd7uY|N8o;M$Yc!0wFKuLd#_wm&-={RZc11NKJ)5RdOtiGlMxym8v+``Kq2lqT#6P zG{^M7r0}7vugkBPq+mP1-we|x$I3_;pMVw&nQ$41&#Em=sp=#Z6L{qhT_#TsmH;X=rk&KtoH|a zgDrJ4BR<%e-197T(3*|dItpj&jxssFSjhv=HqlIW$VT$Xo+gD zYEDm2zY-tAzQ+n_>)r{Kk1I(050xkGQ5Vm>{oLxAN>Hup}FU{l~u|#0PnYXMWWt zHis}tjt6l3FxVL{R_Uima;Q`BNYqgIq5ki}{pbXp+1=mcCuukz&ir?+mX~$jC>mrf zl@TWE1@fd!(gC0d6V)pp-wFZnhn7uoNf0HAfW5=Y$OWVedx= zBi@MwG%OhPfG$ywmyEc5bxY XV~A&uzj6|!-(Q|+NN9ft=WS7rSbGwr diff --git a/widget/diagramwidget/.gitarchive/objects/90/027c0cdc3acf4f23846a4e71b21a0b6661a3ab b/widget/diagramwidget/.gitarchive/objects/90/027c0cdc3acf4f23846a4e71b21a0b6661a3ab deleted file mode 100644 index 416f1183f8ddee8fab30909444d8acc8adf29483..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52 zcmV-40L%Y)0V^p=O;s>9WiT`_Ff%bx$jQvh)=ST4$QSx;dc;!cnB=WakFskAC&VoI K+XDbA`VhJ<(HBDi diff --git a/widget/diagramwidget/.gitarchive/objects/ca/7d6114760e08cee5a2e622b98c97213732c16c b/widget/diagramwidget/.gitarchive/objects/ca/7d6114760e08cee5a2e622b98c97213732c16c deleted file mode 100644 index 333ca1ab..00000000 --- a/widget/diagramwidget/.gitarchive/objects/ca/7d6114760e08cee5a2e622b98c97213732c16c +++ /dev/null @@ -1,2 +0,0 @@ -xŽAjÅ0 D»ö)t9²lÿRJ¡'‘ý$ðãRzûzÙug530)­ÖcÀÂü4ºømÊ{%ò¤b……ï„™bΨE£ª»¤Û9€3iH‘‚ºçå¦ÅR*šô¶ yÌ*>òÊ«“¯±·—öö}ú…àíOøت—Òê;ø˜2‡8iðŒŒèf;OûçÜ}îrnP³=Úöó -[—k‡Ñ`=dúê~°éOr \ No newline at end of file diff --git a/widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.idx b/widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.idx deleted file mode 100644 index 4252462206a5d970ea2d6a160553ecf16e7f3652..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5664 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 diff --git a/widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.pack b/widget/diagramwidget/.gitarchive/objects/pack/pack-3e52e4c029b0d1030cb93a7bfb9152a2869365c2.pack deleted file mode 100644 index 22f58c7b278edf8b55160558003b3741b22fcc47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47997 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

O)$P_$Wqwf#JWj`y~M3D4OB1 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 float32(maxWidth) { - strwidth = float32(maxWidth) - } - if table.columnWidths[col] < int(strwidth) { - table.columnWidths[col] = int(strwidth) - } - } - - iterator := table.df.ValuesIterator(dataframe.ValuesOptions{InitialRow: 0, Step: 1, DontReadLock: true}) // Don't apply read lock because we are write locking from outside. - table.df.Lock() - for { - row, vals, _ := iterator() - if row == nil { - break - } - - for col := 0; col < len(table.df.Series); col++ { - s := fmt.Sprintf("%v", vals[col]) - strwidth := fyne.MeasureText(s, theme.TextSize(), fyne.TextStyle{Bold: false, Italic: false, Monospace: false, Symbol: false, TabWidth: 10}).Width - if strwidth > float32(maxWidth) { - strwidth = float32(maxWidth) - } - if table.columnWidths[col] < int(strwidth) { - table.columnWidths[col] = int(strwidth) - } - } - } - table.df.Unlock() - -} - -func (table *TableWidget) Tapped(ev *fyne.PointEvent) { -} - -func (table *TableWidget) TappedSecondary(ev *fyne.PointEvent) { -} - -func (table *TableWidget) CreateRenderer() fyne.WidgetRenderer { - r := tableRenderer{ - table: table, - } - - r.Refresh() - - return &r -} - -func NewTableWidget(df *dataframe.DataFrame) *TableWidget { - table := &TableWidget{df: df} - table.CalculateColumnWidths(maxWidth) - table.ExtendBaseWidget(table) - return table -} - -func (table *TableWidget) ReplaceDataFrame(newdf *dataframe.DataFrame) { - table.df = newdf - table.CalculateColumnWidths(maxWidth) - table.Refresh() -} diff --git a/widget/diagramwidget/viewport/viewport.go b/widget/diagramwidget/viewport/viewport.go deleted file mode 100644 index bd91c8e1..00000000 --- a/widget/diagramwidget/viewport/viewport.go +++ /dev/null @@ -1,173 +0,0 @@ -package viewport - -import ( - "fmt" - "image/color" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/driver/desktop" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" -) - -type viewportRenderer struct { - viewport *ViewportWidget - statusText *canvas.Text -} - -func (r *viewportRenderer) MinSize() fyne.Size { - return fyne.Size{Width: float32(r.viewport.Width), Height: float32(r.viewport.Height)} -} - -func (r *viewportRenderer) Layout(size fyne.Size) { - r.Refresh() -} - -func (r *viewportRenderer) ApplyTheme(size fyne.Size) { -} - -func (r *viewportRenderer) Refresh() { - r.statusText.Move(fyne.Position{X: 0, Y: 0}) - r.statusText.Text = fmt.Sprintf("x=%f y=%f zoom=%f", r.viewport.XOffset, r.viewport.YOffset, r.viewport.Zoom) - - for _, viewportObj := range r.viewport.Objects { - viewportObj.Refresh(r.viewport) - } - - // XXX: I think this might be causing Fyne to refresh the whole canvas, - // since without this the ViewPort widgets don't seem to update - // themselves ??? Might need Refresh() to also call some kind of - // Refresh() function of the ViewportObjects. - r.statusText.Refresh() -} - -func (r *viewportRenderer) BackgroundColor() color.Color { - return theme.BackgroundColor() -} - -func (r *viewportRenderer) Destroy() { -} - -func (r *viewportRenderer) Objects() []fyne.CanvasObject { - objects := make([]fyne.CanvasObject, 0) - for _, viewportObj := range r.viewport.Objects { - objects = append(objects, viewportObj.CanvasObjects(r.viewport)...) - } - objects = append(objects, r.statusText) - return objects -} - -type ViewportWidget struct { - widget.BaseWidget - Width int - Height int - Zoom float64 - XOffset float64 - YOffset float64 - Objects []ViewportObject -} - -func (w *ViewportWidget) Tapped(ev *fyne.PointEvent) { -} - -func (w *ViewportWidget) TappedSecondary(ev *fyne.PointEvent) { -} - -func (w *ViewportWidget) CreateRenderer() fyne.WidgetRenderer { - r := viewportRenderer{ - viewport: w, - statusText: canvas.NewText("status", color.RGBA{255, 255, 255, 255}), - } - - r.Refresh() - return &r -} - -func NewViewportWidget(width, height int) *ViewportWidget { - vp := &ViewportWidget{ - Width: width, - Height: height, - Zoom: 1.0, - XOffset: 0, - YOffset: 0, - Objects: make([]ViewportObject, 0), - } - - vp.ExtendBaseWidget(vp) - return vp -} - -func (w *ViewportWidget) Cursor() desktop.Cursor { - return desktop.DefaultCursor -} - -func (w *ViewportWidget) DragEnd() { - w.Refresh() -} - -func (w *ViewportWidget) Dragged(event *fyne.DragEvent) { - w.XOffset += float64(event.Dragged.DX) / w.Zoom - w.YOffset += float64(event.Dragged.DY) / w.Zoom - w.Refresh() -} - -func (w *ViewportWidget) MouseIn(event *desktop.MouseEvent) { -} - -func (w *ViewportWidget) MouseOut() { -} - -func (w *ViewportWidget) MouseMoved(event *desktop.MouseEvent) { -} - -func (w *ViewportWidget) Scrolled(ev *fyne.ScrollEvent) { - if ev.Scrolled.DY > 0 { - w.Zoom *= 1.15 - } else { - w.Zoom *= 0.85 - } - w.Refresh() -} - -type ViewportObject interface { - CanvasObjects(viewport *ViewportWidget) []fyne.CanvasObject - Refresh(viewport *ViewportWidget) -} - -type ViewportLine struct { - obj *canvas.Line - X1 float64 - Y1 float64 - X2 float64 - Y2 float64 - StrokeColor color.Color - StrokeWidth float64 -} - -func setLineEndpoints(l *canvas.Line, X1, Y1, X2, Y2 float64) { - l.Move(fyne.NewPos(float32(X1), float32(Y1))) - l.Resize(fyne.NewSize(float32(X2)-float32(X1), float32(Y2)-float32(Y1))) -} - -func (l *ViewportLine) CanvasObjects(viewport *ViewportWidget) []fyne.CanvasObject { - if l.obj != nil { - return []fyne.CanvasObject{l.obj} - } - return []fyne.CanvasObject{} -} - -func (l *ViewportLine) Refresh(viewport *ViewportWidget) { - if l.obj == nil { - l.obj = canvas.NewLine(l.StrokeColor) - } - - setLineEndpoints(l.obj, - (l.X1+viewport.XOffset)*viewport.Zoom, - (l.Y1+viewport.YOffset)*viewport.Zoom, - (l.X2+viewport.XOffset)*viewport.Zoom, - (l.Y2+viewport.YOffset)*viewport.Zoom, - ) - l.obj.StrokeWidth = float32(l.StrokeWidth * viewport.Zoom) - l.obj.Hidden = false -} From d412f536a5884cf16e4539eca517082b56476317 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Sun, 23 Apr 2023 16:09:28 -0400 Subject: [PATCH 16/53] Polygon decoration implemented --- cmd/diagramdemo/main.go | 36 +++ go.mod | 46 +++- go.sum | 349 ++++++------------------ widget/diagramwidget/arrowhead.go | 63 ++--- widget/diagramwidget/connectionpad.go | 40 ++- widget/diagramwidget/decoration.go | 10 +- widget/diagramwidget/geometry/r2/box.go | 41 +-- widget/diagramwidget/link.go | 118 +++++--- widget/diagramwidget/linksegment.go | 2 +- widget/diagramwidget/polygon.go | 279 +++++++++++++++++++ 10 files changed, 615 insertions(+), 369 deletions(-) create mode 100644 widget/diagramwidget/polygon.go diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index cdd68671..71d9ec4a 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -100,6 +100,9 @@ func main() { link0 := diagramwidget.NewDiagramLink(diagramWidget, node0.GetEdgePad(), node1.GetEdgePad(), "Link0") link0.AddSourceAnchoredText("sourceRole", "sourceRole") link0.AddMidpointAnchoredText("linkName", "Link 0") + solidDiamond := createDiamondDecoration() + solidDiamond.SetSolid(true) + link0.AddSourceDecoration(solidDiamond) // Link1 link1 := diagramwidget.NewDiagramLink(diagramWidget, node2.GetEdgePad(), node1.GetEdgePad(), "Link1") @@ -115,12 +118,14 @@ func main() { // Link2 link2 := diagramwidget.NewDiagramLink(diagramWidget, node0.GetEdgePad(), node3.GetEdgePad(), "Link2") link2.AddMidpointAnchoredText("linkName", "Link 2") + link2.AddSourceDecoration(createHalfArrowDecoration()) // Link3 link3 := diagramwidget.NewDiagramLink(diagramWidget, node2.GetEdgePad(), node3.GetEdgePad(), "Link3") link3.AddSourceAnchoredText("sourceRole", "sourceRole") link3.AddMidpointAnchoredText("linkName", "Link 3") link3.AddTargetAnchoredText("targetRole", "targetRole") + link3.AddMidpointDecoration(createTriangleDecoration()) // Link4 link4 := diagramwidget.NewDiagramLink(diagramWidget, node4.GetEdgePad(), node3.GetEdgePad(), "Link4") @@ -135,3 +140,34 @@ func main() { w.ShowAndRun() } + +func createTriangleDecoration() diagramwidget.Decoration { + points := []fyne.Position{ + {X: 0, Y: 15}, + {X: 15, Y: 0}, + {X: 0, Y: -15}, + } + polygon := diagramwidget.NewPolygon(points) + return polygon +} + +func createDiamondDecoration() diagramwidget.Decoration { + points := []fyne.Position{ + {X: 0, Y: 0}, + {X: 8, Y: 4}, + {X: 16, Y: 0}, + {X: 8, Y: -4}, + } + polygon := diagramwidget.NewPolygon(points) + return polygon +} + +func createHalfArrowDecoration() diagramwidget.Decoration { + points := []fyne.Position{ + {X: 0, Y: 0}, + {X: 16, Y: 8}, + {X: 16, Y: 0}, + } + polygon := diagramwidget.NewPolygon(points) + return polygon +} diff --git a/go.mod b/go.mod index 38aec92b..156b788f 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,44 @@ module fyne.io/x/fyne -go 1.14 +go 1.20 require ( - fyne.io/fyne/v2 v2.2.4 - github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 - github.com/eclipse/paho.mqtt.golang v1.3.5 - github.com/gorilla/websocket v1.4.2 + fyne.io/fyne/v2 v2.3.3 + github.com/Andrew-M-C/go.jsonvalue v1.3.3 + github.com/eclipse/paho.mqtt.golang v1.4.2 + github.com/gorilla/websocket v1.5.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/pkg/profile v1.7.0 - github.com/rocketlaunchr/dataframe-go v0.0.0-20211025052708-a1030444159b - github.com/stretchr/testify v1.8.0 + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef + github.com/stretchr/testify v1.8.2 github.com/wagslane/go-password-validator v0.3.0 - golang.org/x/image v0.0.0-20220601225756-64ec528b34cd + golang.org/x/image v0.7.0 +) + +require ( + fyne.io/systray v1.10.1-0.20230312215936-7f71b037e260 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fredbi/uri v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 // indirect + github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 // indirect + github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 // indirect + github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect + github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/goki/freetype v1.0.1 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect + github.com/tevino/abool v1.2.0 // indirect + github.com/yuin/goldmark v1.5.4 // indirect + golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 // indirect ) diff --git a/go.sum b/go.sum index daf4d336..a2e1adcd 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -6,7 +5,6 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= @@ -19,7 +17,6 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -40,44 +37,25 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fyne.io/fyne/v2 v2.2.4 h1:izyiDUjJYAB7B/MST7M9GDs+mQ0CwDgRZTiVJZQoEe4= -fyne.io/fyne/v2 v2.2.4/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA= -fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4= -fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= -github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= -github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +fyne.io/fyne/v2 v2.3.3 h1:nBr4yKaCjd3a8MMgn6/MvZ6CzFBN2WQ9HW4ZKzSPXQ0= +fyne.io/fyne/v2 v2.3.3/go.mod h1:KVTDLs6ce4a5vgg2Lj+dh2AHUL7gZYPzXf11y7gu6Qw= +fyne.io/systray v1.10.1-0.20230312215936-7f71b037e260 h1:hNHShALSK9F0n6iJoBNmXs1GW0AzOgkKvp8N4ECK59M= +fyne.io/systray v1.10.1-0.20230312215936-7f71b037e260/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= +github.com/Andrew-M-C/go.jsonvalue v1.3.3 h1:zPTwpjYwNmqHMyIWyES++xm4k5k1bkdtZoU620pxjEc= +github.com/Andrew-M-C/go.jsonvalue v1.3.3/go.mod h1:IwnJ6++SYu/lYAvrJCGGCd9WaT0zTcsD6Li1X9IHcSU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DzananGanic/numericalgo v0.0.0-20170804125527-2b389385baf0/go.mod h1:uIo7VpFvBkDQoCyKqUL/mTNjpOlv1KdWaJyCsBSpCe4= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/thrift v0.0.0-20181112125854-24918abba929 h1:ubPe2yRkS6A/X37s0TVGfuN42NV2h0BlzWj0X76RoUw= -github.com/apache/thrift v0.0.0-20181112125854-24918abba929/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.30.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= +github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= +github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blend/go-sdk v1.1.1/go.mod h1:IP1XHXFveOXHRnojRJO7XvqWGqyzevtXND9AdSztAe8= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/brianvoe/gofakeit/v4 v4.3.0/go.mod h1:GC/GhKWdGJ2eskBf4zGdjo3eHj8rX4E9hFLFg0bqK4s= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -86,26 +64,15 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cnkei/gospline v0.0.0-20191204072713-842a72f86331/go.mod h1:DXXGDL64/wxXgBSgmGMEL0vYC0tdvpgNhkJrvavhqDM= -github.com/colinmarc/hdfs/v2 v2.1.1/go.mod h1:M3x+k8UKKmxtFu++uAZ0OtDU8jR3jnaZIAc6yK4Ue0c= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk= -github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= -github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= +github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4= +github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -114,46 +81,43 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= -github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= -github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA= -github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= +github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg= +github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= -github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU= +github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 h1:SFtj9yo9C7F4CxyJeSJi9AjT6x9c88gnY1tjlXWh9QU= +github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E= -github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk= +github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 h1:0Ayg0/do/sqX2R7NonoLZvWxGrd9utTVf3A0QvCbC88= +github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= +github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 h1:ONkcbJmsWUOHyjUm0wlnkFc/uaacFFtStVbsG6qJfew= +github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/goccy/go-json v0.7.6 h1:H0wq4jppBQ+9222sk5+hPLL25abZQiRuQ6YPnjO9c+A= -github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= +github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae h1:LCcaQgYrnS+sx9Tc3oGUvbRBRt+5oFnKWakaxeAvNVI= +github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae/go.mod h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA= +github.com/go-text/typesetting-utils v0.0.0-20230412163830-89e4bcfa3ecc h1:9Kf84pnrmmjdRzZIkomfjowmGUhHs20jkrWYw/I6CYc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= -github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= +github.com/goki/freetype v1.0.1 h1:10DgpEu+QEh/hpvAxgx//RT8ayWwHJI+nZj3QNcn8uk= +github.com/goki/freetype v1.0.1/go.mod h1:ni9Dgz8vA6o+13u1Ke0q3kJcCJ9GuXb1dtlfKho98vs= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -166,7 +130,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -184,8 +147,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -199,10 +160,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -218,8 +176,6 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -228,16 +184,12 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY= github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/guptarohit/asciigraph v0.5.1 h1:rzRUdibSt3ff75gVGtcUXQ0dEkNgG0A20fXkA8cOMsA= -github.com/guptarohit/asciigraph v0.5.1/go.mod h1:9fYEfE5IGJGxlP1B+w8wHFy7sNZMhPtn59f0RLtpRFM= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -248,7 +200,6 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -259,18 +210,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/icza/gox v0.0.0-20200320174535-a6ff52ab3d90/go.mod h1:VbcN86fRkkUMPX2ufM85Um8zFndLZswoIW1eYtpAcVk= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= -github.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -279,65 +222,17 @@ github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= -github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A= -github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= -github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18= -github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY= -github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/errors v0.0.0-20200330140219-3fe23663418f h1:MCOvExGLpaSIzLYB4iQXEHP4jYVU6vmzLNQPdMVrxnM= -github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g= -github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e h1:FdDd7bdI6cjq5vaoYlK1mfQYfF9sF2VZw8VEZMsl5t8= -github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY= -github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= -github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU= -github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= -github.com/juju/testing v0.0.0-20180402130637-44801989f0f7/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= -github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc= -github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= -github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= -github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647 h1:wQpkHVbIIpz1PCcLYku9KFWsJ7aEMQXHBBmLy3tRBTk= -github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= -github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1 h1:3y/lDs71xT7YIYtlfODytPNGEF4XVvUUZhFe3s5kkQQ= -github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI= -github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= -github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt/BxiIxjLt9IKGvS+0injMHbag= -github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= -github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.7 h1:hYW1gP94JUmAhBtJ+LNz5My+gBobDxPR1iVuKug26aA= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= -github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/masterzen/azure-sdk-for-go v3.2.0-beta.0.20161014135628-ee4f0065d00c+incompatible/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE= -github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= -github.com/masterzen/winrm v0.0.0-20161014151040-7a535cd943fc/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E= -github.com/masterzen/xmlpath v0.0.0-20140218185901-13f4951698ad/go.mod h1:A0zPC53iKKKcXYxr4ROjpQRQ5FgJXtelNdSmHHuq/tY= -github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -357,81 +252,48 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= -github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= -github.com/ompluscator/dynamic-struct v1.3.0/go.mod h1:ADQ1+6Ox1D+ntuNwTHyl1NvpAqY2lBXPSPbcO4CJdeA= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= -github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= -github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rocketlaunchr/dataframe-go v0.0.0-20211025052708-a1030444159b h1:VHVU5r4JpQu1QmnRMrvn8bJ9WxqZpWm5cY+ylVz22/s= -github.com/rocketlaunchr/dataframe-go v0.0.0-20211025052708-a1030444159b/go.mod h1:uokiUsvKMBQyLXbfCAIwc9aw+1PBsy+0U7IKrpO1j60= -github.com/rocketlaunchr/dbq/v2 v2.5.0/go.mod h1:MckY8J697t+AGc0ENl968yDVnD5cP/FFOBSPPyJXY5A= -github.com/rocketlaunchr/mysql-go v1.1.3 h1:7wYwOWWSl2tP6D9AI3MKqVJdiI5YL3uDnHV40b5e6CE= -github.com/rocketlaunchr/mysql-go v1.1.3/go.mod h1:SD/1bpRrmcdnBYRJq8eCerqqS1nTR9Y9WdW+LPzDLAQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sandertv/go-formula/v2 v2.0.0-alpha.7/go.mod h1:Ag4V2fiOHWXct3SraXNN3dFzFtyu9vqBfrjfYWMGLhE= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= -github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= -github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM= -github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -439,32 +301,24 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/tealeg/xlsx/v3 v3.0.0/go.mod h1:fSua0Owrk9yAMAFGZI7piq5UL2BcubuQuLNOEhr3X80= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= -github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE= -github.com/xitongsys/parquet-go v1.5.1/go.mod h1:xUxwM8ELydxh4edHGegYq1pA8NnMKDx0K/GyB0o2bww= -github.com/xitongsys/parquet-go v1.5.2 h1:t8kVBM+7jPIbM+9ptrpZajWV1lOyHHVIQkTRUTlbK84= -github.com/xitongsys/parquet-go v1.5.2/go.mod h1:90swTgY6VkNM4MkMDsNxq8h30m6Yj1Arv9UMEl5V5DM= -github.com/xitongsys/parquet-go-source v0.0.0-20190524061010-2b72cbee77d5/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA= -github.com/xitongsys/parquet-go-source v0.0.0-20200326031722-42b453e70c3b/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA= -github.com/xitongsys/parquet-go-source v0.0.0-20200509081216-8db33acb0acf/go.mod h1:EVm7J5W7X/BJsvlGnCaj81kYxgbNzssi/+LF16FoV2s= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zserge/lorca v0.1.9/go.mod h1:bVmnIbIRlOcoV285KIRSe4bUABKi7R7384Ycuum6e4A= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -478,26 +332,16 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -golang.org/x/build v0.0.0-20200402160453-61705b562fc9/go.mod h1:BEpJH1kxLue/53k7XKPHBk7Qb3nvSlpB/rQWTi7bMdA= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= @@ -508,13 +352,14 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo= -golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw= +golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= +golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -529,8 +374,9 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee h1:/tShaw8UTf0XzI8DOZwQHzC7d6Vi3EtrBnftiZ4vAvU= golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4= +golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -540,10 +386,10 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -571,7 +417,6 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -580,10 +425,14 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -595,7 +444,6 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -606,17 +454,16 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -658,10 +505,16 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -670,16 +523,16 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -713,7 +566,6 @@ golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200402223321-bcf690261a44/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -732,15 +584,12 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -843,46 +692,23 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= -gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= -gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700= honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= +honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 h1:2ZZFiPwRLxiNX2E/YO6Jgw1pCjDRDgmx20PGyw/cw+M= +honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -890,9 +716,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= -launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/widget/diagramwidget/arrowhead.go b/widget/diagramwidget/arrowhead.go index a4f20986..3d17b474 100644 --- a/widget/diagramwidget/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -18,26 +18,15 @@ const ( defaultLength int = 15 ) -// Arrowhead defines a canvas object which renders an arrow pointing in -// a particular direction. The direction is indicated by the BaseAngle. -// The arrowhead is defined with respect to a nominal reference axis with the -// BaseAngle 0. The Position() is the reference point. -// -// Left -// \ -// \ Length -// Theta \ -// Axis ------- + Position() -// / -// / -// / -// Right +// Arrowhead defines a canvas object which renders an arrow. The arrowhead is defined with reference +// to X axis, with the tip of the arrow being at the origin. When rendered, the arrowhead is rotated +// to match the angle of the link's line segment with which it is oriented, indicated by the baseAngle. type Arrowhead struct { widget.BaseWidget link *DiagramLink - // BaseAngle is used to define direction in which the arrowhead points + // baseAngle is used to define direction in which the arrowhead points // Base fyne.Position - BaseAngle float64 + baseAngle float64 // Position() is the point at which the tip of the arrow will be placed. // StrokeWidth is the width of the arrowhead lines StrokeWidth float32 @@ -54,9 +43,10 @@ type Arrowhead struct { visible bool } +// NewArrowhead creates an arrowhead with defaults func NewArrowhead() *Arrowhead { a := &Arrowhead{ - BaseAngle: 0.0, + baseAngle: 0.0, StrokeWidth: defaultStrokeWidth, StrokeColor: theme.ForegroundColor(), Theta: defaultTheta, @@ -67,6 +57,7 @@ func NewArrowhead() *Arrowhead { return a } +// CreateRenderer creates a renderer for the Arrowhead func (a *Arrowhead) CreateRenderer() fyne.WidgetRenderer { ar := arrowheadRenderer{ arrowhead: a, @@ -81,8 +72,9 @@ func (a *Arrowhead) GetReferenceLength() float32 { return float32(math.Abs(math.Cos(float64(a.Theta)) * float64(a.Length))) } +// LeftPoint returns the position of the end of the left half of the arrowhead func (a *Arrowhead) LeftPoint() fyne.Position { - leftAngle := r2.AddAngles(a.BaseAngle, -a.Theta) + leftAngle := r2.AddAngles(a.baseAngle, -a.Theta) // We have to change the sign of Y because the window coordinate Y axis goes down rather than up leftPosition := fyne.Position{ X: float32(float64(a.Length) * math.Cos(leftAngle)), @@ -91,10 +83,12 @@ func (a *Arrowhead) LeftPoint() fyne.Position { return leftPosition } +// MinSize returns the minimum size which is the actual size of the arrowhead func (a *Arrowhead) MinSize() fyne.Size { return a.Size() } +// Resize scales the arrowhead func (a *Arrowhead) Resize(s fyne.Size) { // We get the current size and scale the length based on the difference between sizes currentSize := a.Size() @@ -105,8 +99,9 @@ func (a *Arrowhead) Resize(s fyne.Size) { a.Length = int(float64(a.Length) * newLength / currentLength) } +// RightPoint returns the position of the end of the right half of the arrowhead func (a *Arrowhead) RightPoint() fyne.Position { - rightAngle := r2.AddAngles(a.BaseAngle, a.Theta) + rightAngle := r2.AddAngles(a.baseAngle, a.Theta) // We have to change the sign of Y because the window coordinate Y axis goes down rather than up rightPosition := fyne.Position{ X: float32(float64(a.Length) * math.Cos(rightAngle)), @@ -115,23 +110,36 @@ func (a *Arrowhead) RightPoint() fyne.Position { return rightPosition } +// setBaseAngle sets the angle (in radians) of the reference axis +func (a *Arrowhead) setBaseAngle(angle float64) { + a.baseAngle = angle +} + +// setLink sets the DiagramLink on which this arrowhead appears func (a *Arrowhead) setLink(link *DiagramLink) { a.link = link } +// SetFillColor is a noop for the arrowhead +func (a *Arrowhead) SetFillColor(fillColor color.Color) { + +} + +// SetSolid is a noop because the arrowhead is an open structure +func (a *Arrowhead) SetSolid(bool) { +} + +// SetStrokeColor sets the color used to draw the arrowhead func (a *Arrowhead) SetStrokeColor(strokeColor color.Color) { a.StrokeColor = strokeColor } +// SetStrokeWidth sets the width of the lines used to render the arrowhead func (a *Arrowhead) SetStrokeWidth(strokeWidth float32) { a.StrokeWidth = strokeWidth } -// SetReferenceAngle sets the angle (in radians) of the reference axis -func (a *Arrowhead) SetReferenceAngle(angle float64) { - a.BaseAngle = angle -} - +// Size returns the size of the arrowhead func (a *Arrowhead) Size() fyne.Size { lp := a.LeftPoint() rp := a.RightPoint() @@ -154,13 +162,6 @@ type arrowheadRenderer struct { right *canvas.Line } -func (ar *arrowheadRenderer) ApplyTheme(size fyne.Size) { -} - -func (ar *arrowheadRenderer) BackgroundColor() color.Color { - return theme.BackgroundColor() -} - func (ar *arrowheadRenderer) Destroy() { } diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index 52210cc0..ef7c8ed5 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -16,13 +16,13 @@ const ( padLineWidth float32 = 3 ) -// ConnectionPad is an interface to a connection area on a DiagramElement. +// ConnectionPad is an interface to a connection shape on a DiagramElement. type ConnectionPad interface { fyne.Widget desktop.Hoverable GetPadOwner() DiagramElement GetCenter() fyne.Position - GetConnectionPoint(referencePoint fyne.Position) fyne.Position + getConnectionPoint(referencePoint fyne.Position) fyne.Position } type connectionPad struct { @@ -40,11 +40,14 @@ func (cp *connectionPad) GetPadOwner() DiagramElement { // Validate that PointPad implements ConnectionPad var _ ConnectionPad = (*PointPad)(nil) +// PointPad is a ConnectionPad consisting of a single point (the Position of the PointPad) type PointPad struct { widget.BaseWidget connectionPad } +// NewPointPad creates a PointPad and associates it with the DiagramElement. Note that, by default, +// the position of the PointPad will be (0,0), i.e. the origin of the DiagramElement. func NewPointPad(padOwner DiagramElement) *PointPad { pp := &PointPad{} pp.connectionPad.padOwner = padOwner @@ -52,6 +55,7 @@ func NewPointPad(padOwner DiagramElement) *PointPad { return pp } +// CreateRenderer creates the WidgetRenderer for a PointPad func (pp *PointPad) CreateRenderer() fyne.WidgetRenderer { ppr := &pointPadRenderer{ pp: pp, @@ -68,18 +72,23 @@ func (pp *PointPad) GetCenter() fyne.Position { return pp.padOwner.Position().Add(pp.Position()) } -func (pp *PointPad) GetConnectionPoint(referencePoint fyne.Position) fyne.Position { +// getConnectionPoint returns the point on the pad to which a connection will be made from the referencePoint. +// For a point pad, this is always the center. +func (pp *PointPad) getConnectionPoint(referencePoint fyne.Position) fyne.Position { return pp.GetCenter() } +// MouseIn responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseIn(event *desktop.MouseEvent) { - + // TODO implement this } +// MouseMoved responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseMoved(event *desktop.MouseEvent) { - + // TODO implement this } +// MouseOut responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseOut() { } @@ -128,13 +137,14 @@ func (ppr *pointPadRenderer) Refresh() { // Validate that RectanglePad implements ConnectionPad var _ ConnectionPad = (*RectanglePad)(nil) +// RectanglePad provides a ConnectionPad corresponding to the perimeter of the DiagramElement owning the pad. type RectanglePad struct { widget.BaseWidget connectionPad - // box is the pad shape in diagram coordinates - // box r2.Box } +// NewRectanglePad creates a RectanglePad and associates it with the DiagramElement. The size of the +// pad becomes the size of the padOwner. func NewRectanglePad(padOwner DiagramElement) *RectanglePad { rp := &RectanglePad{} rp.connectionPad.padOwner = padOwner @@ -142,6 +152,7 @@ func NewRectanglePad(padOwner DiagramElement) *RectanglePad { return rp } +// CreateRenderer creates the WidgetRenderer for the RectanglePad func (rp *RectanglePad) CreateRenderer() fyne.WidgetRenderer { rpr := &rectanglePadRenderer{ rp: rp, @@ -159,7 +170,11 @@ func (rp *RectanglePad) GetCenter() fyne.Position { return fyne.NewPos(float32(r2Center.X), float32(r2Center.Y)) } -func (rp *RectanglePad) GetConnectionPoint(referencePoint fyne.Position) fyne.Position { +// getConnectionPoint returns the point at which the connection should be made from a reference point. +// The reference point is in diagram coordinates and the returned point is also in diagram coordinates. +// For a RectanglePad this point is the intersection of a line segment from the reference point to the center +// of the rectangle pad and the rectangle bounding the pad. +func (rp *RectanglePad) getConnectionPoint(referencePoint fyne.Position) fyne.Position { box := rp.makeBox() r2ReferencePoint := r2.MakeVec2(float64(referencePoint.X), float64(referencePoint.Y)) linkLine := r2.MakeLineFromEndpoints(box.Center(), r2ReferencePoint) @@ -179,16 +194,19 @@ func (rp *RectanglePad) makeBox() r2.Box { return r2.MakeBox(r2Position, s) } +// MouseIn responds to the mouse entering the bounds of the RectanglePad func (rp *RectanglePad) MouseIn(event *desktop.MouseEvent) { - + // TODO implement this } +// MouseMoved responds to mouse movements within the rectangle pad func (rp *RectanglePad) MouseMoved(event *desktop.MouseEvent) { - + // TODO implement this } +// MouseOut responds to mouse movements leaving the rectangle pad func (rp *RectanglePad) MouseOut() { - + // TODO implement this } // rectanglePadRenderer diff --git a/widget/diagramwidget/decoration.go b/widget/diagramwidget/decoration.go index a9bd6c1a..250927cf 100644 --- a/widget/diagramwidget/decoration.go +++ b/widget/diagramwidget/decoration.go @@ -21,10 +21,16 @@ import ( type Decoration interface { fyne.Widget setLink(link *DiagramLink) + // setBaseAngle sets the angle of the reference axis + setBaseAngle(angle float64) // Angle in radians + SetFillColor(color color.Color) + // SetSolid determines whether the stroke color is used to fill the decoration + // It has no impact if the decoration is open + SetSolid(bool) + // SetStrokeColor sets the color to be used for lines in the decoration SetStrokeColor(color color.Color) + // SetStrokeWidth sets the width of the lines to be used in the decoration SetStrokeWidth(width float32) - // SetReferenceAngle sets the angle of the reference axis - SetReferenceAngle(angle float64) // Angle in radians // GetReferenceLength returns the length of the decoration along the reference axis GetReferenceLength() float32 } diff --git a/widget/diagramwidget/geometry/r2/box.go b/widget/diagramwidget/geometry/r2/box.go index b1db5277..3ffd8a37 100644 --- a/widget/diagramwidget/geometry/r2/box.go +++ b/widget/diagramwidget/geometry/r2/box.go @@ -143,27 +143,30 @@ func BoundingBox(points []Vec2) Box { if len(points) < 2 { return MakeBox(V2(0, 0), V2(0, 0)) } - - topleft := points[0] - bottomright := points[1] - points = points[1 : len(points)-1] - - for _, p := range points { - if p.X < topleft.X { - topleft.X = p.X - } - if p.Y < topleft.Y { - topleft.Y = p.Y - } - if p.X > bottomright.X { - bottomright.X = p.X - } - if p.Y > bottomright.Y { - bottomright.Y = p.Y + var xMin, xMax, yMin, yMax float64 + for i, p := range points { + if i == 0 { + xMin = p.X + xMax = p.X + yMin = p.Y + yMax = p.Y + } else { + if p.X < xMin { + xMin = p.X + } + if p.Y < yMin { + yMin = p.Y + } + if p.X > xMax { + xMax = p.X + } + if p.Y > yMax { + yMax = p.Y + } } } - - return MakeBox(topleft, bottomright) + // MakeBox expects the first point to be the upper left, second the bottom right + return MakeBox(V2(xMin, yMax), V2(xMax-xMin, yMin-yMax)) } func (b Box) Width() float64 { diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 11e23ddd..703fa2ca 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -15,15 +15,31 @@ import ( var _ desktop.Hoverable = (*DiagramLink)(nil) var _ DiagramElement = (*DiagramLink)(nil) +// DiagramLink is a directed graphic connection between two DiagramElements that are referred to as the Source +// and Target. The link consists of one or more line segments. By default a single line segment connects the +// Source and Target. The Link connects to ConnectionPads on the DiagramElements. +// There are three key points on a Link: the Source connection point, the Target connection point, and a MidPoint. +// For a single segment, the MidPoint is the middle of the segment. When there are two or more segments, the +// MidPoint is the source end of the next-to-last segment. +// Graphic Decoration widgets may be added at each of these points. Multiple decorations may be added at each point +// Multiple decorations are "stacked" along the line in the order added. These graphic decorations rotate with their +// associated line segments to maintain their orientation with respect to the line segment. +// Textual AnchoredText widgets may be added at each of the key points. These may be moved with respect to their associated +// key points. When the key point is moved, associated anchored texts move by the same amount, maintaining the existing offset +// between the anchored text and the key point. Multiple AnchoredText widgets may be associated with each key point. +// AnchoredText widgets are indexed by string key values provided at the time the AnchoredText is added. These key values +// can be used to retrieve the AnchoredText widget so that the displayed text value (among other things) can be set programatically. +// By default, there is a single ConnectionPad (a PointPad) associated with a Link and located at the MidPoint. Thus a +// Link can connect to another Link using this ConnectionPad. type DiagramLink struct { widget.BaseWidget diagramElement linkPoints []*LinkPoint linkSegments []*LinkSegment LinkColor color.Color - Width float32 - midPad *PointPad + strokeWidth float32 sourcePad ConnectionPad + midPad *PointPad targetPad ConnectionPad SourceDecorations []Decoration sourceAnchoredText map[string]*AnchoredText @@ -31,26 +47,33 @@ type DiagramLink struct { targetAnchoredText map[string]*AnchoredText MidpointDecorations []Decoration midpointAnchoredText map[string]*AnchoredText + showHandles bool } +// NewDiagramLink creates a DiagramLink widget connecting the two indicated ConnectionPads. It adds itself to the +// DiagramWidget, indexed by the supplied LinkID. This id must be unique across all of the DiagramElements in the Diagram. +// It can be used to retrieve the DiagramLink from the Diagram. The ID is intended to be used to facilitate mapping the +// DiagramLink to the information it represents in the application. The DiagramLink uses the DiagramWidget's ForegroundColor +// as the default color for the line segments. func NewDiagramLink(diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, linkID string) *DiagramLink { dl := &DiagramLink{ linkPoints: []*LinkPoint{}, linkSegments: []*LinkSegment{}, LinkColor: diagram.GetForegroundColor(), - Width: 2, + strokeWidth: 2, sourcePad: sourcePad, targetPad: targetPad, sourceAnchoredText: make(map[string]*AnchoredText), midpointAnchoredText: make(map[string]*AnchoredText), targetAnchoredText: make(map[string]*AnchoredText), + showHandles: false, } dl.diagramElement.initialize(diagram, linkID) dl.linkPoints = append(dl.linkPoints, NewLinkPoint(dl)) dl.linkPoints = append(dl.linkPoints, NewLinkPoint(dl)) dl.linkSegments = append(dl.linkSegments, NewLinkSegment(dl, dl.linkPoints[0].Position(), dl.linkPoints[1].Position())) dl.midPad = NewPointPad(dl) - dl.midPad.Move(dl.GetMidPosition()) + dl.midPad.Move(dl.getMidPosition()) dl.ExtendBaseWidget(dl) dl.diagram.AddLink(dl) @@ -60,6 +83,7 @@ func NewDiagramLink(diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, return dl } +// CreateRenderer creates the WidgetRenderer for a DiagramLink func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { dlr := diagramLinkRenderer{ link: dl, @@ -70,57 +94,70 @@ func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { return &dlr } +// AddSourceAnchoredText creates a new AnchoredText widget and adds it to the DiagramLink at the Source +// position. It uses the supplied key to index the widget so that it can be retrieved later. +// Multiple AnchoredText widgets can be added. func (dl *DiagramLink) AddSourceAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) dl.sourceAnchoredText[key] = at - at.SetReferencePosition(dl.GetSourcePosition()) - at.Move(dl.GetSourcePosition()) + at.SetReferencePosition(dl.getSourcePosition()) + at.Move(dl.getSourcePosition()) dl.Refresh() } +// AddSourceDecoration adds the supplied Decoration widget at the Source position. Multiple +// calls to this function will stack the decorations along the line segment at the Source position. func (dl *DiagramLink) AddSourceDecoration(decoration Decoration) { decoration.setLink(dl) dl.SourceDecorations = append(dl.SourceDecorations, decoration) dl.Refresh() } +// AddMidpointAnchoredText creates a new AnchoredText widget and adds it to the DiagramLink at the Midpoint +// position. It uses the supplied key to index the widget so that it can be retrieved later. +// Multiple AnchoredText widgets can be added. func (dl *DiagramLink) AddMidpointAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) dl.midpointAnchoredText[key] = at - at.SetReferencePosition(dl.GetMidPosition()) - at.Move(dl.GetMidPosition()) + at.SetReferencePosition(dl.getMidPosition()) + at.Move(dl.getMidPosition()) dl.Refresh() } +// AddMidpointDecoration adds the supplied Decoration widget at the Midpoint position. Multiple +// calls to this function will stack the decorations along the line segment at the Midpoint position. func (dl *DiagramLink) AddMidpointDecoration(decoration Decoration) { decoration.setLink(dl) dl.MidpointDecorations = append(dl.MidpointDecorations, decoration) dl.Refresh() } +// AddTargetAnchoredText creates a new AnchoredText widget and adds it to the DiagramLink at the Target +// position. It uses the supplied key to index the widget so that it can be retrieved later. +// Multiple AnchoredText widgets can be added. func (dl *DiagramLink) AddTargetAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) dl.targetAnchoredText[key] = at - at.SetReferencePosition(dl.GetTargetPosition()) - at.Move(dl.GetTargetPosition()) + at.SetReferencePosition(dl.getTargetPosition()) + at.Move(dl.getTargetPosition()) dl.Refresh() } +// AddTargetDecoration adds the supplied Decoration widget at the Target position. Multiple +// calls to this function will stack the decorations along the line segment at the Target position. func (dl *DiagramLink) AddTargetDecoration(decoration Decoration) { decoration.setLink(dl) dl.TargetDecorations = append(dl.TargetDecorations, decoration) dl.Refresh() } -func (dl *DiagramLink) GetSourcePosition() fyne.Position { - return dl.linkPoints[0].Position() -} - +// GetMidPad returns the PointPad at the midpoint so that it can be used as either the Source or Target +// pad for another Link. func (dl *DiagramLink) GetMidPad() ConnectionPad { return dl.midPad } -func (dl *DiagramLink) GetMidPosition() fyne.Position { +func (dl *DiagramLink) getMidPosition() fyne.Position { // TODO update when additional points are introduced sourcePoint := dl.linkPoints[0].Position() targetPoint := dl.linkPoints[len(dl.linkPoints)-1].Position() @@ -128,7 +165,11 @@ func (dl *DiagramLink) GetMidPosition() fyne.Position { return midPoint } -func (dl *DiagramLink) GetTargetPosition() fyne.Position { +func (dl *DiagramLink) getSourcePosition() fyne.Position { + return dl.linkPoints[0].Position() +} + +func (dl *DiagramLink) getTargetPosition() fyne.Position { return dl.linkPoints[len(dl.linkPoints)-1].Position() } @@ -136,22 +177,30 @@ func (dl *DiagramLink) handleDragged(handle *Handle, event *fyne.DragEvent) { // TODO implement this } +// HideHandles prevents the handles from being displayed func (dl *DiagramLink) HideHandles() { - + dl.showHandles = false + dl.Refresh() } +// MouseIn responds to the mouse entering the bounding rectangle of the Link func (dl *DiagramLink) MouseIn(event *desktop.MouseEvent) { + // TODO implement this } +// MouseMoved responds to the mouse moving while within the bounding rectangle of the Link func (dl *DiagramLink) MouseMoved(event *desktop.MouseEvent) { - + // TODO implement this } +// MouseOut responds to the mouse leaving the bounding rectangle of the Link func (dl *DiagramLink) MouseOut() { } +// ShowHandles causes the handles of the Link to be displayed func (dl *DiagramLink) ShowHandles() { - + dl.showHandles = true + dl.Refresh() } // diagramLinkRenderer @@ -219,8 +268,8 @@ func (dlr *diagramLinkRenderer) Objects() []fyne.CanvasObject { func (dlr *diagramLinkRenderer) Refresh() { padBasedSourceReferencePoint := dlr.link.sourcePad.GetCenter() padBasedTargetReferencePoint := dlr.link.targetPad.GetCenter() - padBasedSourcePosition := dlr.link.sourcePad.GetConnectionPoint(padBasedTargetReferencePoint) - padBasedTargetPosition := dlr.link.targetPad.GetConnectionPoint(padBasedSourceReferencePoint) + padBasedSourcePosition := dlr.link.sourcePad.getConnectionPoint(padBasedTargetReferencePoint) + padBasedTargetPosition := dlr.link.targetPad.getConnectionPoint(padBasedSourceReferencePoint) // The Position of the link is the upper left hand corner of a bounding box surrounding the source and target positions linkPosition := fyne.NewPos(float32(math.Min(float64(padBasedSourcePosition.X), float64(padBasedTargetPosition.X))), float32(math.Min(float64(padBasedSourcePosition.Y), float64(padBasedTargetPosition.Y)))) @@ -246,7 +295,7 @@ func (dlr *diagramLinkRenderer) Refresh() { Y: float32(float64(dlr.link.linkPoints[0].Position().Y) - math.Sin(sourceAngle)*sourceOffset), } decoration.Move(decorationReferencePoint) - decoration.SetReferenceAngle(sourceAngle) + decoration.setBaseAngle(sourceAngle) sourceOffset = sourceOffset + float64(decoration.GetReferenceLength()) } @@ -256,14 +305,14 @@ func (dlr *diagramLinkRenderer) Refresh() { midOffset := 0.0 for _, decoration := range dlr.link.MidpointDecorations { decorationReferencePoint := fyne.Position{ - X: float32(float64(dlr.link.GetMidPosition().X) + math.Cos(targetAngle)*midOffset), - Y: float32(float64(dlr.link.GetMidPosition().Y) - math.Sin(targetAngle)*midOffset), + X: float32(float64(dlr.link.getMidPosition().X) + math.Cos(targetAngle)*midOffset), + Y: float32(float64(dlr.link.getMidPosition().Y) - math.Sin(targetAngle)*midOffset), } decoration.Move(decorationReferencePoint) - decoration.SetReferenceAngle(targetAngle) + decoration.setBaseAngle(sourceAngle) midOffset = midOffset + float64(decoration.GetReferenceLength()) } - dlr.link.midPad.Move(dlr.link.GetMidPosition()) + dlr.link.midPad.Move(dlr.link.getMidPosition()) targetOffset := 0.0 for _, decoration := range dlr.link.TargetDecorations { @@ -272,17 +321,17 @@ func (dlr *diagramLinkRenderer) Refresh() { Y: float32(float64(dlr.link.linkPoints[len(dlr.link.linkPoints)-1].Position().Y) - math.Sin(targetAngle)*targetOffset), } decoration.Move(decorationReferencePoint) - decoration.SetReferenceAngle(targetAngle) + decoration.setBaseAngle(targetAngle) targetOffset = targetOffset + float64(decoration.GetReferenceLength()) } for _, anchoredText := range dlr.link.sourceAnchoredText { - anchoredText.SetReferencePosition(dlr.link.GetSourcePosition()) + anchoredText.SetReferencePosition(dlr.link.getSourcePosition()) } for _, anchoredText := range dlr.link.midpointAnchoredText { - anchoredText.SetReferencePosition(dlr.link.GetMidPosition()) + anchoredText.SetReferencePosition(dlr.link.getMidPosition()) } for _, anchoredText := range dlr.link.targetAnchoredText { - anchoredText.SetReferencePosition(dlr.link.GetTargetPosition()) + anchoredText.SetReferencePosition(dlr.link.getTargetPosition()) } // Now we take care of property changes. @@ -291,17 +340,20 @@ func (dlr *diagramLinkRenderer) Refresh() { } for _, decoration := range dlr.link.SourceDecorations { decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.Width) + decoration.SetStrokeWidth(dlr.link.strokeWidth) + decoration.SetFillColor(dlr.link.diagram.GetBackgroundColor()) decoration.Refresh() } for _, decoration := range dlr.link.MidpointDecorations { decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.Width) + decoration.SetStrokeWidth(dlr.link.strokeWidth) + decoration.SetFillColor(dlr.link.diagram.GetBackgroundColor()) decoration.Refresh() } for _, decoration := range dlr.link.TargetDecorations { decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.Width) + decoration.SetStrokeWidth(dlr.link.strokeWidth) + decoration.SetFillColor(dlr.link.diagram.GetBackgroundColor()) decoration.Refresh() } dlr.link.diagram.refreshDependentLinks(dlr.link) diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index a3f06739..e238328f 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -67,6 +67,6 @@ func (lsr *linkSegmentRenderer) Refresh() { lsr.line.Position1 = lsr.ls.p1 lsr.line.Position2 = lsr.ls.p2 lsr.line.StrokeColor = lsr.ls.link.LinkColor - lsr.line.StrokeWidth = lsr.ls.link.Width + lsr.line.StrokeWidth = lsr.ls.link.strokeWidth ForceRepaint() } diff --git a/widget/diagramwidget/polygon.go b/widget/diagramwidget/polygon.go new file mode 100644 index 00000000..bc04c4ac --- /dev/null +++ b/widget/diagramwidget/polygon.go @@ -0,0 +1,279 @@ +package diagramwidget + +import ( + "image" + "image/color" + "math" + + "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" + "github.com/srwiley/rasterx" + "golang.org/x/image/math/fixed" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" +) + +var _ Decoration = (*Polygon)(nil) + +// Polygon defines a canvas object which renders a polygon defined with respect to +// a reference point (the Position of the canvas object). The polygon is intended +// to be a decoration on a line. The nominal definition of the polygon assumes that +// the nominal line lays along the X axis extending to the right of the reference point. +// The polygon is defined as a series of points, with (0,0) corresponting to the +// the polygon's Position. When rendered, the polygon is rotated by the BaseAngle. +// The BaseAngle is supplied by the Link for which the Polygon is a decoration. +// By default, the polygon is a closed structure that can optionally be filled by +// providing p fill color (the default is no fill). SetClosed(false) can be used +// to make the structure open, which makes it a polyline. Fill color is ignored in this case. +// By default, stroke color and fill color are color.Black +type Polygon struct { + widget.BaseWidget + link *DiagramLink + // baseAngle is used to define the rotation of the polygon from the nominal position + // Base fyne.Position + baseAngle float64 + // StrokeWidth is the width of the polygon perimeter line + StrokeWidth float32 + // StrokeColor is the color of the polygon perimeter line + StrokeColor color.Color + // FillColor is the color if the polygon interior. A nil value indicates + // no fill + FillColor color.Color + visible bool + definingPoints []fyne.Position + closed bool + solid bool +} + +// NewPolygon creates a Polygon as defined by the supplied points +func NewPolygon(definingPoints []fyne.Position) *Polygon { + p := &Polygon{ + StrokeWidth: defaultStrokeWidth, + StrokeColor: color.Black, + FillColor: color.Black, + visible: true, + definingPoints: definingPoints, + closed: true, + solid: false, + } + p.ExtendBaseWidget(p) + return p +} + +// CreateRenderer creates an instance of the renderer for the Polygon +func (p *Polygon) CreateRenderer() fyne.WidgetRenderer { + pr := polygonRenderer{ + polygon: p, + image: *canvas.NewImageFromImage(nil), + } + return &pr +} + +// GetReferenceLength returns the length of the decoration along the reference axis +func (p *Polygon) GetReferenceLength() float32 { + xMin := float64(0) + xMax := float64(0) + for _, point := range p.definingPoints { + xMin = math.Min(xMin, float64(point.X)) + xMax = math.Max(xMax, float64(point.X)) + } + return float32(math.Abs(xMax-xMin)) + p.StrokeWidth +} + +// getRenderingData returns the defining points rotated to the correct orientation and +// translated so that all points have positive coordinates (as required by the image rendering). +// It also returns an offset vector indicating the translation required for the rendered image +// so that the reference point on the defining points (the 0,0 coordinate) will align with the +// position of the polygon. Thus the rendered polygon will appear on the link with the correct +// orientation and position. +func (p *Polygon) getRenderingData() ([]fyne.Position, fyne.Position) { + rotatedPoints := p.getRotatedPoints() + var xMin, yMin float64 + for i, point := range rotatedPoints { + if i == 0 { + xMin = float64(point.X) + yMin = float64(point.Y) + } else { + xMin = math.Min(xMin, float64(point.X)) + yMin = math.Min(yMin, float64(point.Y)) + } + } + // For positioning, treat the reference coordinate of 0,0 as one of the rotated points + // even if it is not part of the polygon definition + xMin = math.Min(xMin, 0) + yMin = math.Min(yMin, 0) + // the inverse of xMin and yMin now represent the point translation required to + // normalize the coordinates for rendering into a pixmap + normalizationVector := r2.MakeVec2(float64(-xMin), float64(-yMin)) + renderingPoints := []fyne.Position{} + for _, point := range rotatedPoints { + pointVector := r2.MakeVec2(float64(point.X), float64(point.Y)) + normalizedPointVector := pointVector.Add(normalizationVector) + normalizedPoint := fyne.Position{X: float32(normalizedPointVector.X), Y: float32(normalizedPointVector.Y)} + renderingPoints = append(renderingPoints, normalizedPoint) + } + // The offset vector is the inverse of the normalization vector + offsetVector := fyne.Position{X: float32(xMin), Y: float32(yMin)} + return renderingPoints, offsetVector +} + +// getRotatedPoints returns the points after the nominal points +// have been rotated by the reference angle +func (p *Polygon) getRotatedPoints() []fyne.Position { + rotatedPoints := []fyne.Position{} + var rotX float32 + var rotY float32 + for _, point := range p.definingPoints { + v2Point := r2.V2(float64(point.X), float64(point.Y)) + len := v2Point.Length() + if len == 0 { + rotX = 0 + rotY = 0 + } else { + ang := v2Point.Angle() + rotAng := r2.AddAngles(ang, p.baseAngle) + rotX = float32(math.Cos(rotAng) * len) + rotY = -float32(math.Sin(rotAng) * len) + } + rotatedPoint := fyne.Position{ + X: rotX, + Y: rotY, + } + rotatedPoints = append(rotatedPoints, rotatedPoint) + } + return rotatedPoints +} + +// MinSize returns the minimum size based on nominal polygon points and base angle +func (p *Polygon) MinSize() fyne.Size { + // The origin is always one of the points regardless of whether the polygon uses that point + points := []r2.Vec2{{X: 0.0, Y: 0.0}} + for _, point := range p.getRotatedPoints() { + points = append(points, r2.Vec2{X: float64(point.X), Y: float64(point.Y)}) + } + bounding := r2.BoundingBox(points) + return fyne.Size{ + Width: float32(bounding.Width()) + p.StrokeWidth, + Height: float32(bounding.Height()) + p.StrokeWidth, + } +} + +// setBaseAngle sets the angle (in radians) of the reference axis +func (p *Polygon) setBaseAngle(angle float64) { + p.baseAngle = angle +} + +// SetClosed determines whether the polygon is open or closed. A value of false +// makes the polygon a polyline +func (p *Polygon) SetClosed(closed bool) { + p.closed = closed + p.Refresh() +} + +// SetFillColor sets the color that will be used for the polygon interior +// A nil value indicates there is no fill +func (p *Polygon) SetFillColor(fillColor color.Color) { + p.FillColor = fillColor +} + +// setLink sets the Link with which the polygon is associated +func (p *Polygon) setLink(link *DiagramLink) { + p.link = link +} + +// SetStrokeColor sets the color that will be used for the polygon perimeter +func (p *Polygon) SetStrokeColor(strokeColor color.Color) { + p.StrokeColor = strokeColor +} + +// SetStrokeWidth sets the width of the linke that will be used for the +// polygon perimeter +func (p *Polygon) SetStrokeWidth(strokeWidth float32) { + p.StrokeWidth = strokeWidth +} + +// SetSolid determines whether the foreground color should be used to fill the polygon. +// It has no effect if the polygon is open instead of closed +func (p *Polygon) SetSolid(value bool) { + p.solid = value +} + +// polygonRenderer is a renderer for the Polygon +type polygonRenderer struct { + polygon *Polygon + image canvas.Image +} + +func (pr *polygonRenderer) Destroy() { +} + +func (pr *polygonRenderer) MinSize() fyne.Size { + return pr.polygon.Size() +} + +// Layout is a noop for the Polygon +func (pr *polygonRenderer) Layout(size fyne.Size) { +} + +func (pr *polygonRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{ + &pr.image, + } + return obj +} + +func (pr *polygonRenderer) Refresh() { + renderingPoints, offsetVector := pr.polygon.getRenderingData() + // For efficiency, only get the size once + polygonSize := pr.polygon.MinSize() + width := int(polygonSize.Width) + height := int(polygonSize.Height) + stroke := pr.polygon.StrokeWidth + pr.polygon.Resize(polygonSize) + raw := image.NewRGBA(image.Rect(0, 0, width, height)) + scanner := rasterx.NewScannerGV(int(polygonSize.Width), int(polygonSize.Height), raw, raw.Bounds()) + + if pr.polygon.closed && pr.polygon.FillColor != nil { + filler := rasterx.NewFiller(width, height, scanner) + if pr.polygon.solid { + filler.SetColor(pr.polygon.StrokeColor) + } else { + filler.SetColor(pr.polygon.FillColor) + } + for i, point := range renderingPoints { + if i == 0 { + filler.Start(rasterx.ToFixedP(float64(point.X+stroke/2), float64(point.Y+stroke/2))) + } else { + filler.Line(rasterx.ToFixedP(float64(point.X+stroke/2), float64(point.Y+stroke/2))) + } + } + filler.Stop(true) + filler.Draw() + } + + if pr.polygon.StrokeColor != nil && pr.polygon.StrokeWidth > 0 { + dasher := rasterx.NewDasher(width, height, scanner) + dasher.SetColor(pr.polygon.StrokeColor) + dasher.SetStroke(fixed.Int26_6(float64(stroke)*64), 0, nil, nil, nil, 0, nil, 0) + for i, point := range renderingPoints { + if i == 0 { + dasher.Start(rasterx.ToFixedP(float64(point.X+stroke/2), float64(point.Y+stroke/2))) + } else { + dasher.Line(rasterx.ToFixedP(float64(point.X+stroke/2), float64(point.Y+stroke/2))) + } + } + dasher.Stop(true) + dasher.Draw() + } + + pr.image.Image = raw + pr.image.Resize(polygonSize) + pr.image.Move(offsetVector) + pr.image.Refresh() + if pr.polygon.visible { + pr.image.Show() + } else { + pr.image.Hide() + } +} From 9b5449b9e30f4382dd8bae611fee03af22c0edf2 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 27 Apr 2023 10:41:32 -0400 Subject: [PATCH 17/53] Made refresh workaround private; go.mod image version --- README.md | 122 ++++++++++++++++++++++++++------ go.mod | 35 +-------- go.sum | 62 ++++++---------- img/DiagramWidget.png | Bin 0 -> 38325 bytes widget/diagramwidget/.gitignore | 21 ------ widget/diagramwidget/README.md | 81 --------------------- widget/diagramwidget/diagram.go | 16 ++--- 7 files changed, 133 insertions(+), 204 deletions(-) create mode 100644 img/DiagramWidget.png delete mode 100644 widget/diagramwidget/.gitignore delete mode 100644 widget/diagramwidget/README.md diff --git a/README.md b/README.md index 69ee797a..60bdb314 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ This repository holds community extensions for the [Fyne](https://fyne.io) toolk This is in early development and more information will appear soon. -## Layouts +# Layouts Community contributed layouts. `import "fyne.io/x/fyne/layout"` -### Responsive Layout +## Responsive Layout The responsive layout provides a "bootstrap like" configuration to automatically make containers and canvas reponsive to the window width. It reacts to the window size to resize and move the elements. The sizes are configured with a ratio of the **container** width (`0.5` is 50% of the container size). @@ -51,15 +51,22 @@ layout := NewResponsiveLayout( ``` -## Widgets +# Widgets Community contributed widgets. `import "fyne.io/x/fyne/widget"` -### Calendar +## Animated Gif - +A widget that will run animated gifs. + +```go +gif, err := NewAnimatedGif(storage.NewFileURI("./testdata/gif/earth.gif")) +gif.Start() +``` + +## Calendar A date picker which returns a [time](https://pkg.go.dev/time) object with the selected date. @@ -80,16 +87,91 @@ calendar := widget.NewCalendar(time.Now(), onSelected, cellSize, padding) ``` [Demo](./cmd/hexwidget_demo/main.go) available for example usage -### Animated Gif +## DiagramWidget -A widget that will run animated gifs. +This package contains a collection of widgets for the [Fyne](https://fyne.io/) +toolkit. The code here is intended to be production ready, but may be lacking +some desirable functional features. If you have suggestions for changes to +existing functionality or addition of new functionality, please look at the existing +issues in the repository to see if your idea is already on the table. If it is not, +feel free to open an issue. -```go -gif, err := NewAnimatedGif(storage.NewFileURI("./testdata/gif/earth.gif")) -gif.Start() -``` +This collection should be considered a work in progress. When changes are made, +serious consideration will be given to backward compatibility, but compatibility +is not guaranteed. + +The DiagramWidget itself is intended to be incorporated into a Fyne application. It provides a +drawing area within which a diagram can be created. The diagram itself is a collection of +DiagramElement widgets (an interface). There are two types of DiagramElements: DiagramNode widgets and DiagramLink widgets. DiagramNode widgets are thin wrappers around a user-supplied CanvasObject. +Any valid CanvasObject can be used. DiagramLinks are line-based connections between DiagramElements. +Note that links can connect to other links as well as nodes. + +While some provisions have been made for automatic layout, layouts are for the convenience +of the author and are on-demand only. The design intent is that users will place the diagram elements for human readability. + +DiagramElements are essentially self-managed from a layout perspective. DiagramNodes have no size +constraints imposed by the DiagramWidget and can be placed anywhere. DiagramLinks connect +DiagramElements. The DiagramWidget keeps track of the DiagramElements to which each DiagramLink +is connected and calls the Refresh() method on the link when the connected diagram element is moved +or resized. + +* [demo](../../cmd/diagramdemo/main.go) + +

+ Diagram Widget +

-### FileTree +**DiagramElement Interface** + +A DiagramElement is the base interface for any element of the diagram being managed by the +DiagramWidget. It provides a common interface for DiagramNode and DiagramLink widgets. The DiagramElement +interface provides operations for retrieving the DiagramWidget, the ID of the DiagramElement, and +for showing and hiding the handles that are used for graphically manipulating the diagram element. +The specifics of what handles do are different for nodes and links - these are described below in the +sections for their respective widgets. + +**DiagramNode Widget** + +The DiagramNode widget is a wrapper around a user-supplied CanvasObject. In addition to the user-supplied +CanvasObject, the node displays a border and, when selected, handles at the corners and edge mid-points that can be used to manipulate the size of the node. The node can be selected and dragged to a new position with a mouse by clicking in the border area around the canvas object. + +**DiagramLink Widget** + +The DiagramLink widget provides a directed line-based connection between two DiagramElements. +The link is defined in terms of LinkPoints that are connected by LinkSegments (both of which +are widgets in their own right). The link maintains an array of points, with the point at index +[0] being the point at which the link connects to the source DiagramElement and the point at the +last index being the point at which the link connects to the target DiagramElement. The link also +maintains an array of line segments, with the segment at index [0] connecting points [0] and [1], +the segment at index [1] connecting the points [1] and [2], etc. The current implementation only +has a single segment, but interfaces will be added shortly to enable the addition and removal of +points and segments. + +Many visual languages (formalized diagrams) utilize graphical decorations on lines. The link +provides the ability to add an arbitrary number of graphic decorations at three points along +the link: the source end, the target end, and the midpoint. Decorations are stacked in the order +they are added at the indicated point. The location of the source and target points is obvious, +but the midpoint bears some discussion. If there is only one line segment, the midpoint is the +midpoint of this segment. If there is more than one line segment, the "midpoint" is defined to +be the next to last point in the array of points. For a two-segment link, this will be the point +at which the two segments join. For a multi-segment link, this will be the point at which the +next-to-last and last segments join. + +Also common in visual languages are textual annotations associated with either the link as a whole +or to the ends of the link. For this purpose, the link allows the association of one or more +AnchoredText widgets with each of the reference points on the link: source, target, and midpoint. +These widgets keep track of their position relative to the link's reference points. They can +be moved interactively with the mouse to a new position. When the reference point on the link +moves, the anchored text will also move, maintaining its relative position. + +Users do not create AnchoredText widgets directly: the link itself creates and manages them. +the user calls Add\AnchoredText(key, text) to add an anchored text. The key is expected +to be unique at the position and can be used to update the text later. The AnchoredText can also +be directly edited in the diagram. + +When a link connects to another link, it connects at the midpoint of the source or target link. + +## FileTree An extension of widget.Tree for displaying a file system hierarchy. @@ -105,7 +187,7 @@ tree.Sorter = func(u1, u2 fyne.URI) bool { FileTree Widget

-### CompletionEntry +## CompletionEntry An extension of widget.Entry for displaying a popup menu for completion. The "up" and "down" keys on the keyboard are used to navigate through the menu, the "Enter" key is used to confirm the selection. The options can also be selected with the mouse. The "Escape" key closes the selection list. @@ -149,7 +231,7 @@ entry.OnChanged = func(s string) { CompletionEntry Widget

-### 7-Segment ("Hex") Display +## 7-Segment ("Hex") Display A skeuomorphic widget simulating a 7-segment "hex" display. Supports setting digits by value, as well as directly controlling which segments are on or @@ -168,7 +250,7 @@ h := widget.NewHexWidget() h.Set(0xf) ``` -### Map +## Map An OpenStreetMap widget that can the user can pan and zoom. To use this in your app and be compliant with their requirements you may need to request @@ -180,13 +262,13 @@ m := NewMap() ![](img/map.png) -## Data Binding +# Data Binding Community contributed data sources for binding. `import fyne.io/x/fyne/data/binding` -### WebString +## WebString A `WebSocketString` binding creates a `String` data binding to the specified web socket URL. Each time a message is read the value will be converted to a `string` and set on the binding. @@ -201,7 +283,7 @@ The code above uses a test web sockets server from "PieSocket", you can run the and go to [their test page](https://www.piesocket.com/websocket-tester) to send messages. The widget will automatically update to the latest data sent through the socket. -### MqttString +## MqttString A `MqttString` binding creates a `String` data binding to the specified _topic_ associated with the specified **MQTT** client connection. Each time a message is received the value will be converted @@ -224,13 +306,13 @@ if err := token.Error(); err != nil { s, err := binding.NewMqttString(client, "fyne.io/x/string") ``` -## Data Validation +# Data Validation Community contributed validators. `import fyne.io/x/fyne/data/validation` -### Password +## Password A validator for validating passwords. Uses https://github.com/wagslane/go-password-validator for validation using an entropy system. diff --git a/go.mod b/go.mod index 156b788f..794385c8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module fyne.io/x/fyne -go 1.20 +go 1.14 require ( fyne.io/fyne/v2 v2.3.3 @@ -8,37 +8,8 @@ require ( github.com/eclipse/paho.mqtt.golang v1.4.2 github.com/gorilla/websocket v1.5.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef + github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 github.com/stretchr/testify v1.8.2 github.com/wagslane/go-password-validator v0.3.0 - golang.org/x/image v0.7.0 -) - -require ( - fyne.io/systray v1.10.1-0.20230312215936-7f71b037e260 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fredbi/uri v1.0.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 // indirect - github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 // indirect - github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 // indirect - github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect - github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/goki/freetype v1.0.1 // indirect - github.com/gopherjs/gopherjs v1.17.2 // indirect - github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/shopspring/decimal v1.3.1 // indirect - github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect - github.com/tevino/abool v1.2.0 // indirect - github.com/yuin/goldmark v1.5.4 // indirect - golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/text v0.9.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 // indirect + golang.org/x/image v0.0.0-20220601225756-64ec528b34cd ) diff --git a/go.sum b/go.sum index a2e1adcd..7f8d7400 100644 --- a/go.sum +++ b/go.sum @@ -52,7 +52,9 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= +github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk= github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= +github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk= github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= @@ -81,25 +83,21 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM= github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= -github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg= -github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= -github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 h1:SFtj9yo9C7F4CxyJeSJi9AjT6x9c88gnY1tjlXWh9QU= -github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= +github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU= github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E= -github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 h1:0Ayg0/do/sqX2R7NonoLZvWxGrd9utTVf3A0QvCbC88= -github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E= +github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= -github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 h1:ONkcbJmsWUOHyjUm0wlnkFc/uaacFFtStVbsG6qJfew= -github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -107,17 +105,14 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f h1:cWE//ddvZ7bZAYGtNi3+SPGvUFTeTRUL/TQ9LUnQOP0= github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= -github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae h1:LCcaQgYrnS+sx9Tc3oGUvbRBRt+5oFnKWakaxeAvNVI= -github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae/go.mod h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA= -github.com/go-text/typesetting-utils v0.0.0-20230412163830-89e4bcfa3ecc h1:9Kf84pnrmmjdRzZIkomfjowmGUhHs20jkrWYw/I6CYc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8= github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= -github.com/goki/freetype v1.0.1 h1:10DgpEu+QEh/hpvAxgx//RT8ayWwHJI+nZj3QNcn8uk= -github.com/goki/freetype v1.0.1/go.mod h1:ni9Dgz8vA6o+13u1Ke0q3kJcCJ9GuXb1dtlfKho98vs= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -285,12 +280,10 @@ github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t6 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA= github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= -github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= -github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY= github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= -github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= -github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -316,9 +309,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= -github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -357,9 +349,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw= golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= -golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= -golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -374,9 +365,8 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee h1:/tShaw8UTf0XzI8DOZwQHzC7d6Vi3EtrBnftiZ4vAvU= golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= -golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4= -golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -387,7 +377,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -428,10 +417,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -455,9 +442,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -507,14 +493,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -524,10 +506,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -585,7 +565,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -706,9 +685,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700= honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= -honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 h1:2ZZFiPwRLxiNX2E/YO6Jgw1pCjDRDgmx20PGyw/cw+M= -honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/img/DiagramWidget.png b/img/DiagramWidget.png new file mode 100644 index 0000000000000000000000000000000000000000..9754d30065b690ef05dd6a767cb101df11537a3d GIT binary patch literal 38325 zcmcG$byQSs7%w`sfPe~!qJ%-Jpum8Dlpx(HElPJIHH3nq5+Wf(_b{|{3#dplbeGZ$ zJ@mkNx8FJEu65S^>#lQI>sx-n%$~j9c%EOqgsH2_QBg2bz+f;c1$h}w7>o!GgAsnX zKni|BOy+S2{vmMFlzR#*>bbcB{me>AMG6Khjifv^BLUx&JIm|4!C=&_(0>GNPAGF2 zELKrLMoRm&33iGsX>joP*S6ORQywArvyveBs`QN8u0MTrPVemp-;T@A7^T0rpP%t^ z$)JQ_pIWnkHG}m-Yz8Om{e}_zrrV-L3LudMq_$q@6+c6X0ib`%B^m79PWE1o}^3=`Z zwugLA*bL(1YQ7}Z^d^b@upckyvNM@q(x~4{34ohr6zZ1^cf{RW8d#a~+~td5s<)bMkv-DGe&*NeV3C7GXB=*g00s;Edx^!UW!Iu3rXATUSZGke?Uw58 z7KO1uJXLWH%g8E>d`3-_YA1W$}`-c`1{|JM%=D-~Q)^SV6Z%WxM)UcNlEJ zkonxJ36w7xy?=Ey?>%Tz`6}V>aI9AxX=HL=o_D-|y>=%$!y1xVsLdrdK z^DR~Kbqj45XYSf|REqQW-LE<-Tb$_VVl8hj)^W~Lw|m#shBDXTHs4rq>bNLo7e6`^ zP+Y!Kv8In>)G4%`e()z+d-0^~b?Jkosvo+?aLr0hk9^JiMyz^S%i_TeaPMpzQPR{d zLF`}q<9JOso_2>)-LT(V#tfHPsRd|(-^1j2T0^PIh-jYlg(U1Obhe@4Qf~Yz&~KvF zhl@=&YkN@-GduZKeWg%freK%oDd19R8WAq(>|dc5v5s-|-Mt9cwuQct2_g*NQHrvd zd{NO%2>sx$sE&0ocQ_?@%dJ7^2k!|(61HM0o#$FEdX(!|IVOj*NQn2|N|s<2?pW+e zUW?yxI>A;|wbn5$szwk&?=X~>7OAqvv~cSct>=88zh_|J<5NosNAA$t%GMth!5xOX zc-eCJ{W@lL`yX(^3XLlJYQ3=YJs)WPz5C7ucr3ThINu;jli#f6?Gx$l<Ccw+gL(Mg&a*x#ar4~@V(U1S4k|8OT0S#;sjaw@~5KL1X?G5 z{5`K_?UCh>-oh1I%W)31i1HPLg00u84M$}@z2WoIWGWWHDdfQuOt4NnTjUaS2|+L5Ho#v;MOu2$FDqUO1tK)_yD&}~%PV)?+- zF`&S}vTTH>qG_176t@OHmR$UMP8m~Jg6Unzsq|R&Zaj(Vk!VUBLO$+FIlX)JeSxm1 z==P~Ol|YbDiUj!Fs0=z?Is-Zrx|f8{E?c{5&Z?I=;z_;qi&dW~<(D+aIX<_f9KK&3 zpv&vLHZV`Bk*{qRu9H{S!MmWxzcya>Whh0wHRC#E#@Q*ftxrGF2>!foon?Q1h9|SW zCJ?@RdN31U2$uY*PMAunaA)O2QBlKjcU9#J+)G0}%UB2Qx?g;Jxjdqk<&1D{3({0v zT-=dq2nD@;&#}I4=?>$)ri|#JV#7AHUh&2zmwi}wlE4Uem8}icy%U|-dt>7XsS`;Z zO!i9#w$r$<2UEhH_%5}Sa9H5zNHkluq4R#Or@Do$Z@@;tGDS?(>%y> zyxhidtJHO&L;q-JVag)t9=z10yGu8>uw*B8#PCRQtzvH_nj-|tvq;Jh=3w5hTJ&YZED#dv$Kia!sixNgW;NeqohvMd+ zRg%nybw)hw90%mYZBosr6YCrncQV3e9G~Hy^{V&>pT6?!Jbh{@_5#7ed$K0lWW{0l z#P#*EH`sHmX^k952~mud$61efy3ADItC4A=(-6xB#-O*ib~uN+@(E# zcG089wEixCx%;<$MEQyVR^<7^{Ye?Ne8*JyYG83Vy`5(`eT8|raobV1O4TF(dFeNu zeBEwtU9`E4MZXbBK}o4hcz1E>k^1+@{Z@e)mw^5&c9>oF35m5B!SskjDm(oIn>!AF zmlCQH^~M-=OkTxKy?|BNFO4hN5B}EJ5MRWW$z6>*Q4{U zDrp+=?~DT0vmsq2X}5;qs2y zkGbV(W~p*K8|Gqad&yhS>t#&R-y;83j5gj7?_#&aMQXJ9p#{LtCAk&0)u9f;I>bf+ zHx$d_2mcSdOY{^P{6P6T|Ft85{|nzLDXFRTK5s%a6dG3buq%IkA?CZkr$->U*Oe+U z12D`|{H5jo?2kjmrZc<*uuH~Jzg_H3G|X0vEl`Z*1}NwmHyozH?-cwaMgYsY0$v2ZG^x`ed?%T8`M;LS`2WkdtmgP*T%{-0peZJK z*lE1HotM4~JPLNBZnIL@ZSfFZ)A{>%iTCc}*kp~{)Dy5%uY>z|${cX&B6_l3<}h5K zkLb1>F>(v{$Kys2c`xWf8}a*whBdC)reDfJC>abuOTdQSa4fmuRFCQAHK`vprG&xe z!9%gCLiK)Ywz;TQ^Gb@(D%XTfZt{9NN2>ckgy@EK?~~n6O;=&CA7BP0lUivcCgbkw zzuas%NHP5~I+&-04dX~DdB^PYd)jC10R)npe%XT&-v@U-o+kBXy=Ez;Y;Vs!K;;uM zCY)Jp+_)Hq+wGO8!-1~!t^Etfgi9Nf6#%#{15M|6r$hl8%|FEQ_aL6?Q{0H@h9`QESirQ>UjO7zq{JyB09djpg22bT`XdQEjpO$~0E2!!AiH}1`7EJqqM*Ymqg!muaG_zHr8yb}#+bvf;u(Enprclv zCL2Sbi9b$92oE05vHukZqnXOu-ZdEPg%TLA-%{A|3hbbkT1C3_yIM?J@Zu|}nFIjh zYC8jiKaes!FS86!q|w5$l>jzyPp>HHK9j$2Uy%3u_xO1earR9K&=2cDTU z=g37e7p>?&Wb)s`ke~FT46fS{CAQ#-csHWtVevp4(DS zkphxi94YWZDpxgmat3_H186zZpc_${P3U_BBuqL+Jk_&=LYm`Ix1`rIKJvI$6&lri zGj{n&@Axo~zxT<2&%p*3EmqsWS#2E!)A8vMUb^X(*yw*!o1V1riND7=_PTMdLbL1v zD12A`m7P8f8T@m#uMeAj_SaX62?LikbfiDn8Rpzd8k)%lt8Gm7KIMJm+Cf~d#sd3g z9oyh0J&D8;`g^+O;eM45KAeEUlE%5>RS0%+(K#CNYDg$nkqTx{OhL*!=6no3V)TmU$yOlcJE!{X&~PTv;l`5ueqUZGivsZX&vw*U7)ZuP+KWWSTQse0|~bXU!8 zxF8|_>w5ie&qnQpQ`1yP;68Su&I02=A5ATB>g)^Z>y$=1D0dTYXvD26>_$9?qGu2% z-BYSTX{yO0o@Yl(hVQ7E?AxQ+_J7}rgc?F2D8u`y=cgR^oUzq^A~v>rMAq(C-G5+M zS+1ofHq=0R9c~+QW9tRLui0_rCo&3MiDPlay0rB!uqW?SYwA5fF1DJgZH zd%6ZNX*U27Bwt^=!viW>WiyadbM)P=xWUpCag?2?w=fU-jMqUvEL-w2F=cp*=W_Zx zgKi|;0W_JSUgf2pWNe~SW9gm9C;cHL@Q+%3isqnYRoaj7G04coets1p{EGwyaep1< z{h5FOucqz2(}OW97N{0eT4c&adP=DtB*eunrmP`OuvU7;9t5! zu%k@*oruRR*n+mrxA#6-w@DZ|JYI;=GE#rNR#;^;G+$J=(Q!3r@cw!r5p@Xx5$R~G zp2-R~FNL_2haCRJAOqVq5#h9nJpBZuYu<;u6m;)yy1u8n`M6JPc+4slOBney4w-Hv zT5u_ARTGQ^HqOwol#;i;4__D@&AhwQ$?MfU+!Bify%M7nuKnPO&ig?*xnhOpyUIF= zTv|D!pr~x1AEH?+YzBUXh)y-39}~cJw4UGKGk>2i%f*@Oy_j%j0E2yCfgVximdqV@ zu0CQQNYGs?=P%+8cP#!8;@LM8qeQ?aCN?$GZzS>gFxihJtykiFl6OIcfa8JyAL8^i zgYx`}_)yzYPNL(t8;;e@Dyy@_wi>fgTkGzn%MdWfWhs0y`neJ*apsISZ0Xf8Qzp2F zaT}@y#o9uw2Vjk*7KOu7LW)P|79)4evyBRrtAJ(KwUP3T{NhH}gUp9_Y!`Eu_D(jN z>TJWASzv+Y(kOx`I-Z)pS4PP0f|80T;)?U^(eY4^8g=UxGnR+~n`#tZrn&G8uRO>r zJ4A)KoaplB*O)Yo*k81`3R1vDhgQGVJx>%(o5LN+JuIxjCaPRTqoq}JctTe=;x>YP zv-0!uVTOEr+z?2lQ`C41*`arEls(t^1n19OT&=g;f|A|J)lY!ELx!rINuT* zR5@a~mC;deZPZ~n4-I+wyNI;FFRCK#A&ew^Vk17d)tUoY0-m(92aFFTd)X#WmAl&p ziLt+KIPFz+N#N=*tVFB4Sqee9&puw=a5+0WaXOj*$Xfuo8P97{ygv-a$t55`6eZAj zc5KH$+!7?qG&VG1VMr8y9QaoB#4a<}@&#Iu^P$bdz!$Ty5bE%2o&eR=ElhdMOi^7- z>v)HJk*D@)gI9>LP&t-WM??nyYS2#^Zy}D~)ABl(s*nqk*Sc-Xm-8aK5snsFVf_*u zy`eUz*vg%skYFMWil%69_^%xGj0tWONsbpW_M z*XYrEuc3Y!kdVy%6au4l5>)A@KWNpebKGowa+lDGVX`s9EK=fCn57!~;1Fc%}UWJM1@lp2ooRdLs+ZG+F5JD=Z!qqTB#fMi65H@f5a84SM zop!Ut4Ll8wvGcg`%Gl$RNx$801XoT;A{ik}{Zbe*6#zK8n#*@&tJ#gs(DtY=5*boU z7yCJ_&63{I!ql%UYZ0YA&)a;DM1~M`gY~!Q8A>jI>2C@M5JE|R6*t7y0el59H9B&f z7KZbs*(xjmRJda{nebyAXB0C4rHJq`(arilEh-4oyD(T9>JoS{clh`0WTWJCiL~8r zYpdrAW-_Uys_&hAhU>hXMr5d9fzgsE0(DN=NM>xyCs{nj^7`)h(a?KSd9h>r)>ftb z-ta~uMk4?~=Ved?B`WUJ^w{6FhjW3lHwLJSBc|_E*w6|14>SG}NyGGCJG%~3PXb`L za-3ez$jtv_AE)%l79nWe$J+F^|7q&UgR05-_%2ACZ7Je@4b*V>Q>sA10dm%g)?)yV zPT-bHNp3w?<<>Q7*8#lQ5baBsCTsd-M4~oB4{GEUu*4cwQl5-q(;CmuP7vWWOwX~u zJvXPPiluDYwtB%$0vH*^k$l-yG@FQwde5#4^wOo4Po6J=USB-F2;)=**sG=%zdwNm zwPj!F(dq?p_ht1a;=iBfQVC!`gVc#w(`Qk~pw;6U%fPDW!KQ%@*I!JqBR6v+G=LOF zM-T4U9>9y<2gecucEerRm=xCzE(Ddj^S=+^1|OjBeix6=u6BjD>GWHEPgj!gK=GI#^=Cl)>nuX(aoOaunf)3& zv+_sv-n#{Q#U>E;u3e~CP895JJX*Zb6eEf)7qlJx9U;7U&Fcujq7lKu&W_rs_#148xPe)9iR3LK>rXBt&aV2S#Y9B7{@QS18S>=E zJ=4bex{E?5nEKE;h+bAL+m#hUm9=gqCvp8FZ)3@Ie#>fv_=ssh14O*fu5voi>F(hR zG_yZAG~xFF9m@$KA{z&d!r<=QF@TdEGuULW>9JamHA~#;kQ==+Oj6*IZ6KZJYF@78 z4G}#P?*;n0xzHP@nrD!fLrTMJ@3z?G(z)4qQWVYz?@+Ny^{-4u9ItF#@O5?nyw3L#^;(1nC|)_P)Bku`AZ zzVF512_hb72+#2TbYCCEFbR`ofga{K>Y{Uv9+|6_y3ym2x%t{GWDVLtA-NDow@BvH z*17F3K>%xy1UD1~IKpr2GC%?C#gDwS4b5@@3r48)%!``b>jD2dwOr<#_0V2%B?Zz3O zvj(efX;`h#R@!psAZUVeR1z&I@7xe_bWd0Gryu;pd6`KgQUYDn1fg2fr#4>maPIr19ku)k7D+rR+VbBXUp10?zRcss8)hcK^+SjM3}zZ-L0wCtKf%5H@RXTb2Dv z$m0NUS>$OI5Yd^%R?T=;4GGK5C!P=w{*V*RHWPVTWJ{%(dK$X6F;!=koK^-%xGs=) z)~4#J?0jnAb0W$r69Of_K#ABK0Uekro%B6ta)|LzLR#cUBj;w4B9St95NrLw^Yo`A z5>@)nSx+>g+S@P7-86&X_#2pYU$6}RrijOCOkquua?)d$j|f4|>Zo?Jvf^SS<&7s}(hteEQT z-0oCeWLQ;na%MNW9oV9z-rS{DeU$_WX?lQfz5*O;&yE150lL-gr%ajjmI8$Ol6d`c zeukM-d&BT+k6Ms$yEupOxLfWz!u{&~BE9bkm7W8dZalP-^+naScnKhdayb@%0aKzI ztZ)a1YTdHZtxJomb~zY~drEeF3-0{vKtkv^Ay{RhBesm0_gc$MSkz>H|G)shK*uM~ z=l_o!D}R-7OyQ1r$P{4FW5_9);en*W{O3Z7D<>nU=&JQk@ zl^9SkYvqXd|NB{Py+?Gj9O6ki$_X#QGICEo^MFZmcGUv20hpMT`&z6K#Q4*^GnF0J1d_i8*-fj2^%~>D`Q**;5 zqcQpzy+VV9_?H6Z_RcwOFhe;rc4dyW^_iw7Q$m;wNh~94Sl*X1&)Sxt902(@Y>f)$VtJaYgo z2vqt+N`GQ0nB?~DK^U9)TuTT~*OToZw4iPS$#{xd&u_^R7IF`CxZ?xVn~UI)fb+{H zxzxH0h_&eljOs8@VNb6FW>lVR)YZMYuxd!#Cq)_pnol_>#V_|J-Fj=7azJsLD1Iag zkVU06HBs83bqOpPCa8R_YU;QBtce&V?+I@9nlc0{?lwTMLdE_sTSx-fb0=_^B1NkN zk!1O!EPnO3e0+TC|G@^ZK$KqRxWn4-G?FtyFW7k@ZgG&3A5%9tP`vvW(0cR^M%GGWzWtQEOsu-@^r1=q` z=>W(9_E{=p+9gy%M>Lz#sb&{o^bSCsI#7aXHUm2H2bmGF@|^^hc6fJHOBxlHcC@=R zQSI{Q^h$Iyz&v!zUtN zZx59;Jmr2RN*!~N6v{}zqFe!n1Yftq|8rgX|G#~!MNtSzeMQD=Ij@^Nu3x{7P-(6| zUd`X1eM2+8w=#f7o{m+!Wcp45aq0*@Ml!OrS5J_YV?J)O;sO-37ocC4f{{L{@TP1E z-O}#A%4@Q2at>68)e)Ga`(<0jTln|5m4P-D3H;s{(;Ku#xh&YiZAe9o;nK2S>CZ-} zOoJ66v^G+l>N^SifRlT7LIH(-%=MWsNRtYZp2H2`J?#9Ki|hp~x$5n$n} z1b(YyHdy;@{YpJh1JaEVM+?J%4Ah++uVEp**NjzS?(QjLi;s5%k{t|o#ir2~vHI~PTg94q6CbX&Re6_dVm{?l0OK4{OI6qoeFF_tAFMe5 zX^-~7N~z&$f~xHsQmSvjSviK6W}^P7j;gR|Jq5?&@?_vvykTWRy}1dfVR>$fPX{mkCu&u()2-GCx(_fJy|FX=2~A(*TjaqhxWQqx z&bYx@%leap=J0#y16uub#<{F}P`9Dfa3(_#_GjB>}RaX9?CsAmDVl`h$c zn=8ysj#ITf&)WgY_=8sVN!=5n`v9hs!LC0m%vaT$3xLzqiThQ(SKnu7kC|Y zwl*{XI{rabano6?p%Bnv?Ix>Bfh*KLC0OiG&a4($?0?0f7>B3?U{S0ouyV$!@hB%z zHk@%moCveO2cj`vy?WiN+W!3xxLP+~iM?sti9&3pxR=WK@S0EMAIY8mrZT`m>acIl zm{y}(WwJ-fIg)01a70;wm((OKR!#fl`vaYJgG~4WiLGKf6XLerEQiocM+I+CI<&#hov$h4(E!v4CkapZ-51oYnaQTqQOsrEq|LB zmnUpkqAKxQ>X|%&Nv}=-zstN#mFhvhN3E)fFBX1qCvPP8U>9LO#qS=}o{KIx*tJ z%y0`_WPUP=4g84wX)AXMDX>9vkVv;i&ra6|byEh_J)+c%8upu@%jSr5^~5?yvgmRv1q_WlHK_>+{Wdn2Aj%?0{HzOZMN& z@Zp)|(!DcV(c)g&FN>{vwYY0O#e3e%c+FW&){80e;xza|o?Rb_y|dNr&H7zchK#-X zio%f-VbdOLeo^BiY>FQktkK zRw`DVbQClfq&B62tua}tJRzo|uH6lV&zh=!&`r7rP;mH!#~ZnZ85SoaNC+FMGO#7Y zWN-JFIgM5t(56j|l+ni*%f(0I7N61cI>bMluxVgd*&d2!9R%)0H=f#h!gtc8Z7jhm zWrbO-TxDctm9J@24piew|CNgT)hx+)W&d#4-*9!U4|&7^JMA2GnQq6W!h;kV1b z6Hp0n5l?W^mtqDy%$4OtEza==UL*MzQS6LCFJx{pX;315ziA@pp;VOc16qQonXH+- z+{218&C&AKjPDujthPP3K+hI1i{5Eg??(riUL|{A+6L<*dBFslkR%WU_*MY;} zrN^YAJ5Jkz}YEL~Bh)7`j)h!;}QCx(}Wm%oPvt7poF z8x1_ftPSnUO>khSYfRp>gi!yoPRwS)O)iUjlvw|cy2Z1X7w~bpqNACNWu52E!%XpK z83runBLxndT@WTuHFmY?GhvTMMRDM%0G0 zR~T-LmS&z{rWuc$M=oQ4CNQchfp@Rs$;}M(QSoL<=u+r=avNhea+1RoA$chO@-+vg zF@rDYD-^aq#?b@2)Tmx{*5@n;JOMnB zwxM#?gL87l<8H10SL9mWO5)S_0{D)GcT* z#DFJ7eanbKNs$beKjt{{m!NYel1fDdiwnWnK8ii8?r%VR9n`z)C-Y#rgyrT+f230% zsj=Jzk1Va=^e7L1=W^9x%WLQ&RQvPS6kY;T22$|5_58P`26)AaU%b0@>C}0pO(4qJ zcx&8)4nC?+VUla&_1fx!4H2_DJ#pmO+#KDxY&*A~riF5+s($RV}xOl1zTlu4DtazA`VNjIQcvPS67 z&y==I$cpb2JGf^!xICGX)p>*qBB!>8_`(JsW@hOCe*yi{N4&YdkrI~i2Ge^RRU%7E zA$zf(CLBqLqP221R~*UvlPjWUYyL2HWhh8ng?&%RD#;}@UlCPUQ8W@+!p+_s8H5a6 zUQQN?UA0Cw8m=1UPuT4-hnrlzNktnjo7IzbTsPbH=>9dlG85i|#g>NYG|FS8|0n(s z?Hwf2L8|f(zZEcDW||s`h!WXZ6W{X-W@03v^SCr**XO3*rX$vrw%at#&`^{{RC>H+ zoyA-+G_3j-X^sl7BVOBUE!IG~(w=^Q_sEu7Qw42bJlH0qQdT}t)TU9Uhj(Ol?~^K` z#Y28nrP4U_PnNw4CU214q}}?@MCW{H23ej>@H#jRn#xby(oqm_FAok)iHy_Xy6oVz zcatt~kU@ULZXy$#g8aU+y=UYhVURnqT4AI9rsNRQTkfyDE9%f%o_vwi;zALFLn?0k z&5Ml2`{ToT`bo^vk+}!P-8%p+@gFea&hEWms0bg%qh>x&)9So&XYp}r?e5&DUE?{p zzRW);Xq1{ht<|u3Y>ylDVZlA7@V70Bn>e{#-(*nSYvLbE7fSz_9(lzfw{i z$2w_adh4mmeLC2nwiMGV+?(v^fX97wZxKImZ%|Cc*dM6y2KJSq{CH9BTo!364sq+XwW3CHEiXcBc+Ht2?QhJFeKc^p zHsrJ?E_lR@M51J!PMgk8Bn}RwVp$tSk{BGc1Z+++m$I?(NZFOYNy3<2%$nv5Gs$D@ z`~21P?;uFvTkQ~!Sg#1z;vAs%bDm`rVstgQr5>aaBrA1i*^i~q_xjsAAz`y_>OU2j zws9%>M@VWq8)A4v1nVbggsn0To)T!271Dj1pHcH?7P*jfChpTTD6>^i*5f+DMa*rq zav+tiv#A0~noa;+++G95PD4woK^KPk9G8-klAd{cJk9n_Ry2RK;X^tIG8Zq>7)ncU zQ_vpY8j4hXLPCV)B<|+Cg83}LL_g^8p>`!M;@8R}SD%|9v`itN0(gAF(FUt>Vxv-* z@e_R*MW$!>yTtj!M5-3|#|SlI6esREu(;2Ri*pi(RMARV9Zzlu3Dr6|#P22TuAW@U z!6sCCkBk3gx!>>Bzkd(oAxxg~&LlO$k39qPM|-Ys`7?#*_Fm1ZiyMWew?pQe1Ji)i zW$!iq!B=;W#V||7+|kpmhrJS*lXP(ziae3WIhTj_#wouQT0z7QFFMf7j{7puB<(l zs&V;l$}2zWplgA^OgaLA6d?8;{#y(s69cnGvJ`0TW*QqjfF%2mJb*s^-@7xUIYx)t zUEwR{^~nEsCjH=nn3&irD5eL(Q;84Ff^q9(1EwM7aQo4RUhP(~K8N<7Z4Xs5%jZOC z=)~}%cGLAW5X?tw=e-1}l`PGF;Ybl6kWA90aQqjC`~=ufwXUVzNKyCy`cyVlAINfm zyN$YXjgvKaP}ufxYYt*B$8dT2=B17%>8ua^jSWbCs|f$2g?Rg7rOSW=V0>4y;|m~u znHC5|BZ3Goz%L5~ocRG+Hpx`cKY1o#hOryD5YOioeY}rz%ga-W=h1CK_%{*D7}`vj zgs3F&{e*b9km=2I*`@`j23%YshfM5!=-RgjS&`JjKYCwVC7F5dk0F54)(i4J4rNct zUWHz~J|D{TKr!HS`yFm`W~RFdwD^rHFinjNb?H>byH#>5IR0Bee6@lSL^J1j(@M$A zK#jp5S)du>jm~U{)1gR@LM9pgU|FxYo!6wxJ5$2IXlay`>kaYA-XCGb(=bVLt&e0R z$nPMJ__grmH*3u86Ocv1YtD75p^iYv#R0_A5bf&$=O?L9lte{jzGljELLb;gMy6?} zUj-eIDoKwqyabZ%w+30g8;){P$@BDjd(9d7e)Pg5+mTPazdvF?$+4XN3YgF^sOAk2FDA}0nLMA6D~ua z#r=;*mob@BKHi*eH&6&Zz%n9|fqj;cSU)Pb`7dcu-1IM<2yWEtfPoqwZ|s?n&zNU3 z3TECoR=!uMc>(w%9=&rQ0O4Mx0{E-#5W0q21;pfx0@6xfwbfp_OCbzavYWr-kd+nC zG93MHE5+R}g%UtwmCU@87is<(iS1mINpmT_U8{p(-Nhyj3BDwy-s#sF1FyIP!)^?` z8XMpBFEI+X5s+em0`7vh1)pW{z@WSYFxVO#Po_>8vWZ8 z;kANULTw$jtgfu?c$6|q9w^cQ54>XtI8aJzx#YCrd5KHQsG%Bu) zU=kf)A1ecfPulXsan-d7rXml zbay<;X~-BuLhA`tRO#*x)~^SVF_!>hHw}f|g5@P6X^9y%vU7Y7HV|BkAnW5CFO~NB zUls=Vd8sGOdOz7X&|66!!}>)EhBJ-+Q!|`>@)>QS^Y8CKChLJxqD^wjnu^d=4DJxE zzOX+EQ-6K|E-ya!-F4EXw9~TtWxSbyG?982aGO@S>4QL!Wf12-#<L?&$rGuSBmK!7>2>jJ6t z+MmvYwQ3bx?{g`l2(=GObv!g@SYVvCZyE2V&bIOS$*#+!%MR|{{|=_`LlpYaDltp- z-)N=vU`n1XC2y^FrzIRIuUkkc;D2gCaNbyleK{%h3%d$(6p}bKmHXx-)GKUt>K2i9 z-&82z+mWfiLDJd&pHKt{q@zCGiSBjFV7)!C79rDu3k-@luuM*&#dOIw%BV-Av)8fDQ>ZKQJnKZTDkM886HNNuyIl?bFjx?ptW*_o05eY@9+A z;uO;8o%b8Or}Z-aMgl(iva+F+3V+%nt3VL3ri}rIX;%Oi?G&dygN0+jkbr8sO0mwJ zf-uqxeVmRIX4^eIlg$4R;_-`7YcU2DnED`?6P~koc8pk3KY0V(%%SwsjH4&w>B?>QaGR6=`IegE1P8k5w2)#1SAvYoRYtiUQ!==);&D9 zUf{JkZFq4j^97jZwiGWGo!4#3CT-AI1N>lzQzB?4R8TY+` zoV)ReE~AC+`CejiQ#Y^JCM0K0>Aju}mf3r<=cmoq|BCodI)S8_*&e@)7HYsY&TD3f zx6Bk4*phv@4g`m8nSs=h^=tFwW{C>|8Q`oLQFza-%)oR45)d*gzzn$aBm}m^Gh4MW zc}?E#d(ZuZgd9L8t@G4+!f=#+^3%l71ol1{Cy%{D)TygTZYw#*;dL12lRL7XoR@6L zjXdzWsd15i(g;aS50}kb=uy(sty9^+JdisT@;d6R8Kzx+Z9ZoE9KS0 zgs830asfOfFwTo2zUM&aT#WBZR@d}bj}GbK$lPt-1N~N26e|E?2nUdc`&)3G52QcP ziK1RbH>%Bn2D@?hr>(9MXr*;qPbFwG(m-6J^vJ34QKH}jK^oYE`hEwH}{%T(BLn^45jc^!Il>%9IBEj1vH|89=8Nz#n}kR;dlpwP*9go-yw&~n|MvH zSn(NqO~rA{Ui2Cl!@Elm!H#mvmfw*PFHhE#iP9(!f>2HjJLBDFWEld{nNP!IUD-k3 z4!?Q5u0V^!^LhYbPuzoU5zkNE=eYQ>WlX6z>^GT5Z4;Cvh^}#~wtoGW6PRV3DubuW zORfF#Q4~UqPg6jDrepr)y)`>(;g>DD(J%rwELISFc1;}!a!q+t6M|H58WhS!aQOfT zs7OnK1Qnv1_VSucE7*x_gRBZ6?_8)wR@o`wd@tXowr4d8fk=C@SpbN$Be(=v14Exh z@PX({zYI~%@~IhPvgXncZqvpm>7)vrPd@F!M-CH|6T+T!)_Ne=wXoxsa!mAzGbK{F z3E|yDk~eXw8cUj(&y6TB9kkF@%t1mH%Izi!yIB@U0DuODK7H$95AfU)9w(U2+k@@G zp8y;LfMGr%4#BPg%8K}Y7k}?z_A)qMz^OuK4b@>%UyXA{0DO+#zH{km2M0yi&AYyK z)52auF$-f_^l^&+{+Q=adcDK!!4eQv_0k7cxEDDr&%P%~SkP(u$w;cK&c6!~H_Nm> zUb@=|pe#A?4aN4KF4yZqoddw5^BtwUTu?m_)G*C>)hB*H>#alA1&3dylZ=rPTMwaQ8^G`($icJ1o>FCeQlyIN z{}sVx$6)GLYRV@Hn&3JJ)b1!<0M7_r7#LQbE4?ZNthgrt<_f;vdeLMq_~JWEbnY$f z=@sZii6lyj4(_mO1JWWjBYNg?fIYJK1YFXe?fo*#P8IRmu)YJf#9NM$5Lv5 zqyO>Vwvr_j{+a^03IzGlGw>3FmNi4K?niF{{O~zIabew z+SA|o%L2`x29));7XVY*V|)c%S||TFK^Yv$x(?IW1u^$FC>0pr1?jDJQ?(VQPo=;J z$5?_%x&bEXw!+X?09}u=M+0FZJE4Uj4x*0SrVO?Q`}6ZRfMe~uJCQ<8J%O;0x9UV` z0(S;k$1S^e_+imghueP&LE8oR;p?l2QsCtc@S52HV+zxoFqEg&)=MC{@&!^nN)dP; zLm;lJ{O?mK(WAHC{onz?tMvq{m%Fg&Vg7`3RS9s=0xT44#fZT~r33y-nb186Wrna^`f7?|~Ejs!xpm9h!Up7z7~tfmI7g+-?gc z?#<5uQEK{*=A`IOuXA}eHA7%v%95n#Il~_cO-B_swq^QBN&Y}^P~h^2vu40 zpuc`#m}Y}Ka*9jdXaN?wq)pbMC5Qq8hZgjp%5JVs04|m36P)!{ZR(Q2(zFl#25f8Yw=x>k0J^R7XAvw4QLI3 z4?+)H_rD+jEd4z@I7Fhbw3=UB2`uOt-UO=ad_S0;Nn^lrl)MNk4_(oq{phR}EQSu3 znn2GOu8y}F45NMa5%}~_@zOFtrndhm>5&Y>{7wM1%X892j+B^F*5^DMP$oc~74Lz4 z=Cr6RL6r7*cvQS>QQ zwBaRXS4xgBZulL!Up#ArVa9}Sc6!3#XQ$p&I+Y2{#v*5Ed$Da-ew4HX}&CSB`??G@#G{67)R~rT{JL|K77&AzWX7QZ$zFtkk=Gg*eG; zYQc(cD1XTcW?u+w`Ee-E5#L2Ewx{k3>}-(DAb^G*Ji-noGP)fAss!aQ)$cVgf>PVv zyW!xIhBe`f`x8zFN|unDWDLSw2=eye|M_{0j`bMO;aAb478#1&Aq-&Cg2s6rX7~rn z|AQqt#hV}oY-7*w1Z0P7G3DTF%dv17$VVLu!#I<{G>1yl4C&)v?#}qV5_B+_S1)rm zTmNQQHOdxPm~qD6bh`7=^n;E)SbGccQ5XN!X2pEm+K3fpm6<_tqqE^F(APXcbDT&L zb~{BTIgFMx14Q=4pP{~nk_^73Y4Q`xG<#ptb>N%CD?Gf(|Kl)b_h!U13((B z_(;0@%*XgbT}Uf2AYmKxpbzY9w%t^t<(Zq&6zQazpvT{M^139yDCX@#XS^Y>>efMn z>_MuEtlSNFpza4oAnt#oEWLG26W{J$^Ox7G(!R@nx@Ja1dT~o>HN`U>hn*PVy>2(hregFl{t?Dk~u3 zH|5+) z?7klCX=X`=P~ww+Z;IFy zTZRRaw4uGc;>2W?A38e<@pyZ1%uNuSh8eNfK97?)UcNH{<^npi1&V}Mt(yvy?|Ny1 z#ODX=--&3-!OctnQOOEA5eFJRM#pxd!fq*81w>8OAth=l3;ZpdSUb%e*36~6HJybc zuwW-~G>Mu+I_29 z(@XgM4xL9#*)Qe&PhRdc~n3HS>nhr(_;W=td_Gh|7`aHY!z~6aUbP7jdRJmA1BwfJvnE-(@BkRlmqrESWhq8V9 z9(xoiqe7xv)=F81kc{kPCkatm#x`UP*;A<$NiwppGq%c3QK~yF_OX+c>`Ov2mfqvk z{rf%d=Y5~|{XC!N@27t%%Qe?rb6w|o9N+EW|K5Y_eRh9MQcFVTCWEs6Z780Jq`ewf zjN)rzp=O&SH}MAUA2;!bp(_jer(-S42yx0_MwL86KC&HoG_*q|l}h`GPXbbg*9QGB zF5IggE}Xc_?2JvcJCK-@#OJY_AT3Yr({`oqvdQDGenyDA0szrU&RQF@a!)?P8&&=Q z!(+pY{cr~iHorm8Cn*kl-_PrS%5OQe!f!EM`UZ>S$DhgB=~*{FAg7h4Zbx`5DvJkC zv&a=!ePLS#5BgxVjQMkOv9=iv?lgb_y8dx0^Whv)0J>fc?tCZTa_s9ic^j{H!o}jr zq*@6(x;Ns8uTUdf+itLOi5!h!yJu0g0-*Dg50#&!gTAZEuZAv7)#fTh%BZzIRgD~| z;Z5S3txigW$NxGX`5OWUr@XzvSX9&ez@;l*fLB$MFJS57mb5VjF%HG^qZjWgkKTEN zd?hiSX0a)({6z@09Mo2mAM}a0y(PBA;C|poskc>i*c_2grvOsW?b%~ zRFZ0^wK;L8v4hC3RnGe1bfib&1zHU>VKz};7vIvBR984upPL7Sbp9${7a5*j31GE{Ouk7scT-6 zTcY+G$IGg7B|vHKw?>_o&0`Rt^o(K&>PmMhpI3QQx1K}c2tzkX|tiq zS-xZyV`Y=!#o)E3ET9>z8&ce@9|9RdO}(3qFf>>$Usk)X(45^)bOg>k(oc26MF?rT z_f_%Za3$2N|?mHVy`9vf{v$a)5zD6RD4-vpsmwNcXKzYhbQG@eN#z8 zA-8NCDZK?24Jl=IS_We)Mtq;cGy~f#6T#z`CAHGIT}xDo5j^mF$UHeDYXe|xn3lgU zD{`(WwF)=2Wa^}pGw>333+p97FpQk20#iyl6J3}))Q3J%tKBy|=ua<<#1H1YW=XbF zXj4#V&~KE@Od_s=2br;pxan?b_J89V~Rep}Fn zculv-nUgL(Tl})?o*!Y%bw&bDk+aj>4cnThpFG-dij&YVMr3Pth_lKUAFobE^^3fj zPZcSw0j^fayy_V6Lx#sD&x9BXU)d(DhCnALd>YC^0{4&TbXEoNpTgw!U}EviDb@)t!!rIjPx7J=OUZmh|d5KTaR7 ztvEIfMc2#c%D(0put+J}9(C73#yiMV2^lEOaY&*Gc^!z~9wR zgB8q6brcA&cU@~T=YXi%GfpRV08S!pC@$#IUF8Ae$G>!5swYA*;9H4wfzwU$Lp}?1 zF+@4AyULeLy=tPPikxlok==RLfXWD+2VDrO*x5cyHqC_&o$pw}&NibKMX70M7d+td zmAk&G2kxoRTfXU`)GPLYVt)ISGH% zOKeq8jkb=HLLx~e{4T!+R#DF{j_I7J6`O~C--HVJET!yQT66XJ_!NEeZTs%g-)hdI zLj0RQIhVk;r@*4bj~k0?xesn2?)TNnU!E^@SrNxpTh+ENxgs(}*pTG|a38A69zVc9 zH*}%VafuuN9K@jN^$(J@A8~413HD9MLVJWxY#aKN^53z?Qp_~k?ik~PFL?10kB=V| zLIO3U;XDmheq|*0)g-%PmPOqLUgsaxPQV-Rzgv!k8e@66PQ23mQMZgty<|PFMSO&! z#mzQ&DZ!R6>v0JKGtECYlJVt@WcdM4wXXViD3X9*7GqQet-*?V(BktK(c>+^{@|z zIF`e_TV2#jqs=tlo{2|aAb2s(mai1J+g|v1rz1W>H|`4Z(X4tAP41S@Lt!WHY^>9Z zU2$XI5j>prf`>_9Vw<+3%hSxdqjWRJCbT_N30k_r9#}a)FfkBQdj`Pmw{aFLr9DsIhTcV@=5p{f+Qtte z*qxF{htpy(l7%;3*BapBhI(`LG{4-4xOnQCo|4hZ4%vVmOb^0no%s`IkSoLMaVdA5 zzCp#$0aPk`4YO)Q8ktu8eOJIE?~&Kd)yicA(v~MXQ&rcM%*ss@S_bkbzJW?}&15=^ zF)S0vZ)ne^*Lakv~-Tb5jZCS(mt2}C`w2-CA0?S=B@ zu$c?zB|t)&x>#+64v=5XcbT+qNrRL%P5G@K40A+c`>T_wb9nxo&bRniI<5r5Qa$Tk z+ErMzdKCjw^bn6|sj4aZ^l<)n&fko@>6bUNL&gay%~t7L)e|=uITZkQsGT0Le4c-C z0snfuk@!nJp--w^?1FKow<7s$R(ya2hH0!&b(l>efLHg7nwyXZFaB%B^?iL8#=nI% z3unYBe?eYqj#l@+Sglq~Ph(kj~+bC%19tm=}n z!t>W8;EL~e5qx~)UBWdNUuAN!Q6li^;(=x^UKXw1@324pVG#YS{+e*iA?1UxCt%{DdyTi< z*nOhfB}B7W&mroPBoBrR2qYB`09hvgp%jPX(9z1<7xZANbqo!cy>m>@r|L1|C!uV> z!TVFsKU{Bcfv5G31++8kJ7X)`_qg2rVl9m^f!ZG(XiF=HrKgu}8^=RZBZkTN>`8s& zH28g&;chHB!&pcq-uauZ3d4cxP~bbbp3B`~RVKvj7GuVN1D}1J18vz05QRc z@wUMRB~(T9k!J*iMP231&P5gs0F!h~i7pu;ZPoTZl;|)H`qycCB6|+) zKTe!3Jo~Q(5I>sq*J)iwt?)oAI_R?O|c|TalZ0q=Az?B&c1!F_y z!!2_79{`IjwXSUwwg>G2@$KPO0KqCtnXyq|ex+59n4THChwz5lZ4I{xb?0AQ`w93# zKOM{7&)vK3q_fF6HSe{0Y^T}O|vxr%b*vm-8eSivj00Iy_|vQuTu6Wl59B2 zM={xnb#@VCFpZ?PA%^5;H#E#nfP|LVO8XOIkjL`~orVyPU&Sq&we(9d0?vi%nV9*X z*kAoR^yN1d7=Us4>vtpMym)&u_zQh!`Z^(l(T$x~_PgsvtP21-U(a=W)UQA2Ok>%+ zP`;k@2KO;BAG0|=(DCylNwF>cO#_`MUf-_m=$V&7(KMm@r+!&WBY6$P?#^eC#F6Rs z>S~QxY`%#%KqLJi0rCKN{|TIYi>)hBj9~5v7kiXGmblxnGD$h80x{1OEhkp1C^5~6 z@uqL!#o;mXg~v#$!yx4jjk%1=0rQw&9|ig}K0PQ*gLhxb*N&_Kw1EhMh4! zy9O$tV+KlmG^rn`g>D8ih7? zZN@#2BX!wtM)xhaj!C9_yAYf>#0=i-aPd+~yiM{Tvp%d_1z|v1MndK3Uv*yvM9&yd zlWfl)`VJdvZ9nYa*R2+FSRp$pGfhVRMmY2FJu;5BZ5aeDceunVZo!E$$BP>JrNlB! zelO%#O0VKJ&wn>pZBM;>D!fkYYNTFfMnR+_fr%&Kl=;K9EG?^AznK9Y&xQVMH@IuJ zz2i{Fz?vbn@K#PQ{~evtnQI*UE+`N{Z?w52Bf~lTp3%b$N7oi%)B9LA+3Zqqt0p=9 zajSCg!QJYJ_Vf+mTiSYa8HI>sWNI{Z0x&pCW0`n#408|AXL2-`XJ#y!y^d7Eh|~hs zEhTi=C}fyYD1GYifmU6#`B1p9?b<9b$*kjD?cVc9 z79pQ~EY}FGn0t{D?_n#cMBY)llV;I5 zCXrMuZya0CSr=~s4q3%Krf_Cq@nc_uRk2Ie_9ihBcA!*Y2xq>r2Pz@1rk2Msh-xK` zG9vui|0cz2eL0c14RZh( zUlYA`+LI6Et``&WbmaO5D2rzkKca*`|EDJec@D7Q-g&{{#B|_r4|4TkA@=N(GSiw4 z^J8>HAavM0kb=?bTqMY__28v}@GT$lrEk_d5)%`<>-Ih7%Y6#l#@7V;_A}noH)v}4 z(juI7XBJ~bLIkbq(Fw?h_zr3})4abWtt#)>WKvfF4t~l+0_{x?k|8CAjXZ_?fDY$9 z^oruy5$M_EJs^066_W!necR$(=vzr&`id5nyG?q;rWUgt*5hlB(^Q>v<7w=Bj=ngO zWCIe2u1XKvdd34KNYLR+Q~C8dvNS4jbA>lLi%w%W%7}ho9+~>Rj9Cr48Qw_pp`BN@Gce6zp(K2ZNZZO zw-cNv=tRwVfxpfY>diFH+{1}6&VG5w>`9#9#~Y)3a&81rcLl|9XcVimqNMCZO!^3P z4(^~^gi{=e3;BENNp^Eydm3Dzr_e-zgiT%-%c;SJQ$Y3^c5p$BeoXrM0FX%=K1k6@ zZa%T@Nst6PvC&GnAaJBgP_&#Hdi~nHbicy+g4p%etaHinnKGb zdKB55PgWk!9iX<4gpFme*59#X?bng`Ni`IAY zNg{N; z51f_)Km~oWIiDaE!uiPa#e4ACf*cZ+R||T0S@SK+0w1%Fa&gE%y#ON0-AeT_5;(*& z+k~z!b>$ffAfd}zxudhQbFp+sk(K=JFrQj=$Q!b*9nZXFeT+WL{v=e+Th@;`B`GIM z=!^S9&IIzAu9>*uF-!88OagE+vnB}-kzH;jOjAKmw%zjRUuO{^2oFB-97)dIW$Ug( zz?ZMi!d^Rd-5cb8E9Je;Tb!4At3e$GiILj--~nV_KeNi!BCMW}OEvzTgjBpMzywJN zV*WDhe4yTNUgSg%;|H2N=aD@})u@J=mFrNSaQ)0l0%ZFvhDGABpJwp)?{OIjbY96YALI)OsnYQZ6_B?LU7>MiGWYQz<{p;8qX>TgUjs- zUVfhfoaBcp+^s_OFk(D#A$FxCW>_oK$BG*#XhyQ^_0o1b`Mzo_HE4jt@-uL&rARp& z!g`}ly{QH9Mw?5$v-Vh#d7c4(5TJ{0ppTV9>pg6jwr58*rPWe6%6B|M^6G7O4=^1l z6$DAh8!dr1qZ$m1k1lDtg?xP2<8bD?KihG$OC}9q0&_;e3rOG)W|p-eL5_a-ou9>s zgve_k)@Kdq3ssHG+;dGL$7##;7ZVjFR#wKI1oS$KLMgoDqViShwfB4Y2nAc=-e}%u zVs5|yqX!5f*TlB6+=mz{nM;l!3 zK!@4{6{WGZu^wYEb2hoAC}IntOwbym1W|DGt_lm{Ko=nB1ka(>!lGkN_yYg1$ z5^@4tuER4SK8&QA5JU4zxD?E^AMPkliq$h_7u#XN>>3?0FOZ-B@R>0uY5suhwE2ip zCwQ3#1ivuLvgZH3tr{tx(qURMD0ByQ1g#Q9G_uy%mZ8kSE| zBQGpJO0EIvkV^y2cK3>Kx(`UMUOa%VlHZ*2aHiJ7Eo7#z$6=2-NgK333s3;$t=;Q| zHHiq&L!m9b?vZ~TSo+WHE@FbhI#IqCdR)U0+Hiy}2MP&H1rYm_)u7elI+-9MO$~K~ zv_ayD!-r$wGd~8Jl^zqws1N)b1qJr?~0sK$)e*7Vl%+< zIsQ`~Sb{6M@WPv$W-gi9;f^5AhgY}_XU^AES(3a(R>N@UtvhQU>Z*4U5SdEP&iO|R zhlw^KM+@ed4ws&M+Vfy;1Q3%4hdI&i2u=C+dPY17yMVNbyyQF+;LNi?P((&KbG`I2 zeqCE+UpxR^4t^G~^;el0x6I{VTIAVG0ror`NXwQ6VIa1qlB0qc@xWGZ75l z0FDw1oPXE_NRlw@K?xD}dfDX%Vv8;?pnDY42&@-AZnY(J<07D?KlL0@NKrYMwvW&? zLZ^w@e9dgLm?U|RRNI^ETa0qICGSfY1g>pF65I?kl608fCr)(f+J~$v4p}Wo$B4Fs zVO~^_H`P0st02=iF&+lW zOJ?UznXp`i6aVeS0PB;2(~>VP?$V z?Cp&9pf|lIgZYJ=C6Ja|Wl?aAv>?Vv{oh{)z1ht)ng8gr@aN%J_zPD_CIsaBfVu<4 zGqa4TiRD9BL=Au6pJ--CJ&izmgZz$;K@R`RihBCh+4tW;*+sP+_Ypl*axe(QqqSW1VD>Xi~ zmSo~yFckWGu!WmFrO^vSg(2b!k1l_S?#ee7s%K0;3VE-quxh|Pk#k?sb33wHf7~q} zC6MxP9yt=|rhu({I@wu(h~hC1<6l0zW|8s?k>r)f{ie_GK~s`^sXn9eK-}nCN?QoQk7WNEsA;|xgC~1+dEB}%VRO5I4epQh2 zH#9P}Q{3su_FY+bW#xw-=#Be;p1fcF4C#4LM#zH&y{3V(NCBdxJm~0_A2lFxR4|Kr>F2*uah2(-E{`qRyXP07)cwe|(PMmWYQWM>RGWsVj0 zPvQ`XKRf>l^>T$72}{;0Dp^ zR|tx(dsS99M_YdCGhtHCECyu(UKaQ5%ryT9e@PPFV>`JXjEaW%M1(J)<0e7}cBsoqp%=;*^vAWoe(%y0zJxQikEFma*!3|1hHm9 z#A4nVyj)f#@%EDn*^0qOxW6y1{G1)^2cY>;;WC@H=D)8PrU&2$H|Qq35+44_Jw^#Z z@%es$k|3Vm4T@j?aLrBzCZ3sXu1;sy&%-|I2!!>cDT^Iu`i?EBGZH)dj881KstqpH z+?D@y`IU`8Zptij0D(barhCK?Bxr6(x?s-Q<^Y1xLJ0Ac3}c!y+rh@N+dN7(B9{JI zY7n&FYdA-iy`Evn&NHhpRqpI`GFnbgY}D_ZS# z3W3V1PnCs##BY|np+U6ykeKq{C~Xju@_p8Gun!rKd?`r9*9M>=n{S6aVS%ZgGkHj{ zed%r7YKoyc1$+t^tFBK(QNKtFxBR>oq@+*jOC@4!41+}yAq{s|@LR0r(jW>g8d$2G zsj#XDBhOWS+8#WIyR1%JPsHv!QadfZdO-N z9Ca=Sl4T&w2=4bH5VjTbB>63J3r2|8@BC!h?T)%6KThA~o%($fo!DH;b%`xndoNur zxbxaHM?=3rR+#q%zQwTViF)_JZ08h-y_r)DCHs#Icc)L@@eBEy%qRYAGk@?!0o(mF zgH<*a%2VKgdzM1-Cr$S(_5^a!MQsaCrMNJq-PH`+ns&TYaB&}MHi}Jrf!V2A9)tGM zzZ)7X!!8I}v|yS9uRPy*gU$@W+Y~Qp_dZ#9fz|~YkavU6Xewm3{8e*KXTT-57S&J} z%zI38j8tthY9RwmQRNpztmPOJwPMxFGp3Q2kbxw{NskO33eaXUDrps?hA+cJ2^~^+6C2(oTscbuYS9Txs=(ZE%~KCp~;$vxOc}#G*gI%Ok~MrkvxX`HR*JfM*8ArsO0#9 z3;9F0{k+Oc3sZ*HZiedbw0&dJYE&wV8!{~|K^g=ko(^@t5IrlOn(Bd{vt0|an*E0H-^le(4*#MuHSpuEa#VVM1rf^#k z-G-t-6kC&f{X82EcB^XJ_sI}bC|>FOFswwZBegfy&z$JuuOYf zEPn{bN-5SKTqI~oKlD-*Cm3J}WkN++vo4O=-I+Q~$w)ui4@=TcxnUD;VPEXsl7^A% z4c1jj5BxIA7Yg(s{64+0z$1!e?5rhBh{##pW|2v~DzT^c(B;s`+@QeO;S=J40-YZX z66B0Kq?w`z_JSslIA*neIs%`~zxaqbrt#LNi{^&1CjH<{w?{e1LKV>x{KFEd`IffF zINzEIa&*kRGSj(TZ`ejl@3lKh_R79_3g5)Pq%TjcBa@!|pOVa4+ja!|Hgv_gr-W+gd-+(+9HSUUGcy=<@c9;=F4wBu zJ@dJM^&W+t*G9L}SZ4o>x(kc7R%55r7wA!%<(T-_ww|QZ4cAVH4?8xH)05(#0T0#4ylZ@h0VMXOQP`)b5(8SFiN1lS$dSk)$E=LY=0f z>C#{6Z1*8QV2rm=Ri7MjXThZ*4?}LzN}PO~5u5#%I+i__6|%p&&1ne<&e=PPR!?`H zY}sGJW=O<}bk1o+auTo#z6}-{1|{bE|2h?}Ut&yMJiPYY^u~uN>!ZBePNpn=o$wVu zHFe}P_TExTcWwe1$uZGg0)}Bn`N-%9T9plo+PE55NACQ_k4V)5*mUUv31f25USU1 ze>{4B%DW}{(=Bv&cBhU9xA_JR2DAlb_Kz-pV!m4nSRHV?Y0(=q*M1YB9G+dlUfnns z8sG2Tco%35r_CTpaiNPKYvBPi^`*Ia(6OXG0T|Wk6gZY`5_w!?KxiBr$I~<51sYaq zG^r!?H+TwUfRM=kVX5ip>C?SD)qO-r=1SEH-jUOHS;4veKxN_bUov7&_Se4L249oI zaoTtm5A-d*-Ed?N;=PaG6PKzoXHrwgP|f=Q#&e9^Zcb5hdR_?up&uHzjl%_y5R&;= zGnVr<7K-6xSr8G%leE8i@N~%%2qWL0fzJ+AFgYpN^3A zDsvIcH4qOME6T6a9Y#MNtU&L=k&qv4fo}>hdv3&4WS^P!L-9uv6y4FEwz6{-{keG2 zB&|0c|GIiv@fz1AWZK5PhwDw3PPOC-E-urO2U68S@&j{14$&TU?lmx%XWx>(e1==t z{1*g_H!a9!S5QnToY4{vE(W`wrSZ<3O2`@@h`E^CQcoE+)tM<%s~ydfOyhOf-xMwd z8ZKVu-7C1m{EC?|b=`qkNnS9F)I6~lA6Se8*L*wlBduR|xKfuUZ)H!$(!`uwW9(wt z(;pj?0T;=cxRKp=t6=3Vq;U-pPG}QfuBiMfj6JlN^0Bee^QDHWrrKX`uOC<%G+vNn z62KYW&J($M);88I_r#eRL#AhTXB^wS+g@ztZ97Gmlg?k*DXKeG3jxvxrzd>LNA5*9 zwyy0IJN0rrRuP{bZi7iL>j=3{s+|b-O0pj zEGS0E(czOaw*tf8;;+DcfhVkFF5I6rY{8Y_g#v9*P*pgy#vPc*F#?gW7(63tH&Hu` z`3sVIF;*t=3s9`Uj*QVh{cpo{qH@$3;c9Ml9k;B?xIp2b+*1dg@Y7utA?pbi|}PES$v6SOq; zZ})oN-i*J!0;V4bFU7)fDyW5)GE=P?ksw%aR`i=DfGJ579p8TQ_-KFvTJMqbvY-zv zRsX#38p;}s&4yUGQOSdl^8*b!YP2!hUVq#KVWh~M^H=t=Zgd|c!x9>f>j2bWwC$W(EUwsAtqq+JzYgV8@hRY zIN|MjRb2Y2=`RrvS;ajOOj|eKfDj!1z=OEB^YDn5Ly})Fh@{*gkPuZ2VfLsaeGQ`* zJy20R2wgf%H*+a4FWB`T--VUX6`q1SUyBX9KJU9o zkCz4*41WQdVITb#4yEa%=%WI^4)~R*a-N^@QyqDCQ9mD|{}E{KiLSdyNJSSmlF9KH zMx}6r?e4}3^gAfx9_1FJxt>;=H%4@4kyUpK9Y%efAUoL$dBIQ5e&uvAnnaENzK~z% zSI4vNx&hU?X*mEllw;bJAJO$pnEQL-&D(c_K^GD?l45vd{suesWY-S(RhUQmH(<0% zL70=EN7FZBKrE^&Q9v z>0gPaB<*Mg)X^C@P47f7u%+K|n3NHcux0jXCZVL~fbtgXdL(NE2j)H_TnaZbV0UQZ z%Vri8%m4vOxO^h}l~KG{MK~naf!U2`}4u%+7JLLXspzL&M{nHFUna2)aZjtst9oCkI~G5n9CKw zA;C?Ua?q6?e+lN8p_O6{&fuH)Be;cof8dm4pZC8eC;25EGFB73}W? zu-Fw~wOrVXgpnC(^E(zWHbv=k7=s&R6EC=8((PeUNVnU3YIy#6Ay`2W(jBq+wAstP ze)f91@^*~&<_dX_@(0lyK`({Bhwl9iSTr%(CU6#w2s_5;fYk!C0!R3>xCw@C9$Xy0 zHDdU0-1d9izb6gF76RBhD!BrC#V-T;sHxu@bH4|^!NcMV5z9Ucn6N!F{<0pQ2B0;x zhmzn4JPIZdm?D@|Xj9wO!V?ZnKr}j;otyd@xV&&um{_#D8F_B55@-!!+g#&zqI>D3 z6wQ+Hn_N2;xc{}iMzXT+wU2A6FtD`%>*)dv4LLa!1fhe%7&{sGGDh$n+TJ2Im#P2! zpuJ_z@wIR=6}5~gg$|v{zpfIx?eRM|uhQn?dlFqGmCdVk16`%fC!H0(CHQ;uD(y#C z$@X7Y$sLAmA^+)AFZ6kA?_3~e6jH#P0DO>8Nu(`4XTNp=5}*r_Q?V(FU`{r9)8hW1 z-+yOL|G%;D6)Esc_y>U3@+<&>gU7wPAnvvRGWTrhjd%(F*ZO>;m_^`9D>z2J=Ay|( z5Kv9{y*?K@1HjSGN{mrEmJpHRGtvSh;KoJh^*=2R{w2%6DS_Leo+B~}J|M_H&$Wbg zvj|L`h#KsHB>O{$4>vV~SX~KVul2*m5A&(%g1?CAV)N_tx}u=*(E%c>94Q?Eq$0qq zv8T6hZwmqt%^k90XK85UR8|1kQflFjcAu?5`T7v*F#{^!3W$?`GiU(71%IXF75fz= z5S;>O>zgFRtrLMcOs!Q>9tEmw9d>P)B>`)OI9$+DWZ;1Z9k~^K>Be9^^xm(&Uo+tm zn#o42{9RqDhGOOCS*X28|oXtO@^pQG-VWG{!7NyOYJKHC^@3;JKK-^Jwxe)9$Ev1YM(~ z8Hl#`-|qyZ@1|Y|+-cU)raAh?Tq@?y$gw;HetD~9SaNFg;5D#tCz;%lkjSjV!AhQ& zY!OR_ac5`&hWPT4uFcZ+dA>(8^u{BjaP9U5Dw^{B3WJ-atsNNRMFhfcfdY1b_iksZ!JX*2RXo;Eus|w(y-QWfbjSyuq)S| zkO7xfJwnlmM=>~krIA>*fuRC6)L_Wpz;n7F;t~{A;jX81r+kCps0T#B{B6iUpWprz zEsh8*)08uH3MHQ|BH8$MnitP~K!Y2a-(k$NVJk~*o^a?JD^+XK6nl#SykA*qz83$YQbr!a68~ue=t*n@?($| zq1Yn+Dav8DLE?^nknR>iCeA$4fj6fH%_<-U!c|cY;;t-S_5jXY2(2e7;<}GdL8R*A zBXz$TkiTwu86qsaLyxfSM(}h3c)-E>XNS<=1(f9sHW_3M9`xz~4rXB8g%?7ggACD~ zj^>_qs{mCty8@Aa#iwDM<}>VqM*? zY^eNmU(ob$>aC5RAa$!mgP~xlD}ujgr$5n7Vb_3EU|9~i@oCB~Wn%-J7=8&Pcmk_$ zCXq0ZQPwvoC+Zf2VO;dE?Kg5yD|c@dhVfbWc4WjK$N{7#lHXA2^S{!8iFs$0=7BWx z*Qc79w>^z`i<~XcrT7kOP0_Q7fu*Xl|7ABv{R3PmETxX-4rOY^{J+rfio+}sxi(0M z)*_VZ_e%xkC1!!jQUirA4XjVjsfQN4$dNF@FX#Bu`VnWM%));KvPiR6KnvQG8oCj% zOj@i$qI@(ez)H45Pd%JK%{QL4%RL!O|7f2U9T|Aq*(Xc1$oD<4@=xW$I8lhdu7N8S zJR7L25~ahGZAGkp!=w|XwjNwn{is3jF%j&Ta+kimh9*`ya3ALJVc1T?vh!}Lg7@7f zy{*~*xVclA%pi=`+9#fpw%w%hxhz);`^ zQQ3W{`bR5wXi3T(+-Y5C!2%`NE9-IA{P*6l`B(y_=?(?{va38=or5;Vr6ik~8|8?p z46AWdKnzg30;NXHYFqZ_CAf)dyVE!2|J9%K9?Jlj>AyMv{rgy^XFE`wwGd)75Xf{F z61+Qc57WZjC;c4cl1(J3rw zw8MZR?jRS_o%Du#TujH!FQbS!`czV9bZY#$A+*ju;I_k#W2YBC&B4d{6KaW?DXcct z+8`~mA$$BeGn%%fgXX55zaY^LW~x3`3+`+vnu*tK_Gg`pV%s?DZO+TO9LhS1TYD;E zoOU;i6v1RR%vs*l*)6CE2+FfwL|q-OwLcq_8SMgEP2bZYrf8_?%73CGbK2 zMTozTY+=FN1Pe8WlV`Feliw9L$Y#MrWjtk;!#UH#66}bi5uM^HdrqF|X5nUGCB=|? zJ2iIO+;?w(!AM!Vv7s5iaZdJ8nFJ5z%eZvvwa_kKO75oT3S@4M?97~76ko8sQkzm> zUOuAgAZ8uRdai_`t!lUBT=F^BV~>S*%8J6^pN>MOmQ*XB_U^E4lOrW#8z`#W*F{cb zey*JHZqc&hmSce`5uV(2MF#@)|GE7ejRd#QE*`S28?d=at0rh!rgY;L=QqbA1UQ?? znp>enM1Q52$s7@jnsY*45LKji-Wk@v;+7_8>c$nD3Or&o8BOcszGJ7WhM@i;QcFNz zJfJ@*Ca~-3e&sGG7C-N^QWZU=-#CK8_@;jShV-?nZ(;P;&Ly90Kn?G#TEc;tzj7S| zT-tMed2LR~@>-vZ7+V6ZVNGl1#>x2_pNTJ$om9iQ7|N+kk&v@p`3^3*d2xMIC!Lo0 zSTNlth($>(DYY*_BxQ)v%PsB{EO|1wn;p{+#(&HW?2Ow%?W;2t{X{>MruveOYIf?S zD))nfCC}QK_AmJx=cqR2mkE^^EK^8~W%0!vE4(Kyn~G!=NhfcR9=+M#_DI_(V2i~W z^`;suJ@$m`*(BP=B3RsQ`8ouD=M%>l3=ordhvB0-1!7Aa`^b#??@@r4`X)R~f z9ir@-z=^Am`zZ%5eh*D5jR&6&(}|d8xjVj53k{n-G*Cr#5)K-VZ~er=^q22+_iBMr z=J`}^6CY_4_OkTlX&CHvsl{sDPgWwCrZ$0NHsfi}9(Cc3(ywF)bN`cX`s1M#?^JjABX`&=a{nw-hC@A&>BF0^tWoPjl8zevxf z!*;}FSKf{;>RYkYbBX(GTf|dO-1@xX&327exOg1ygCFH{>0?OZ_LO;TV`Ga zJB?pFU}%C4gtupu8lxc~%O&0_eaHH-$&rUEC~@@TOvqAJGJHvSZeX9x<#Y3NstG&H zb=h_j)GND(-avxg9{!Ol(4-7pf%cn=nYV397wnp88dos4@|F(}&at46JN|+X^$?q> z!OlTB3KvE_j#NrcyWMP=C2|q;XT|l*n0w$p#9-`=Lxq3VCI1 zP#}*Lf6znFoV6cQz(zQ1BxhlQFPbWL8iaU!3SQaIp~~xe>Y!IUNhw$>@{;qPwFVv9 z)WG_2sHsC{9)$c}_df{TB{bn5VG5K%i4^y@pDF4SfBQ3NM9=LuQk)nH`DfrSF;s8W z)6B(45N(u+i11bh?%WS~iBr~*bOagr{cSEegP9en2u_14Z542Lcj$zO>kw@2hGJ=8 zcL^XW)C2spHt+zuW}XrV8`3&_a+m|4lC3~MdQGe}Bnu&V@KefPF!rz#1t&E9hC|KD zQ3lW$6XGC@c^Oc&*>xRFv91qUpGK0F52*fwjLJqdU;<4a-kcyerO{&A^bJk5L=~MI zj8B`bX5Oz4spxj5c?P}-f`bNHbkx`G05r+@j2rp$Fdj3HfYXTuD^;W8m)FsLC?^7% zD|@&`WkBuq&WspB)g2WM<7R6655rh!@Em??c!e}7U5Bl!T8layE}6tX_Dpbqw54K& zM<*YORiV@*$?1>D@-0ZUf;!(pcT}nqg;2a^xJFakqM})X@#7>}=X*i6>P=sL20`32 z^8odS=ZH8Jyj z45^Q)M5n~Odn~Vn1FpqQ76vADBO1c`6u5@YxYXKoJ$#C_?R6l%Ui*84XgV?jeq z%itdFwJo*G)Qyqgne@)wpHK@1m9B3&MEDgXs&WX|7=(t%GG3Dr z)u98vlSfN7a5YVzUq;SAYv?t_MTWW0wProWqH74RIJ`R%_TgUKT|Om0x2|!w^OJf) zNFCsMGB?(=_0GmiSL(X!0of=K^q{ezApvn8Wo5mOIv*QBor?7F=Mw0d=B~00t9GugI#X)ro&ZS&J)u z0lNn^^~-*65pE`5b2!~0lVYpcDP|szB`jL|_c@D(7OWY+=vWE*v5*QUlztgB z(GYt17cY<>m9{Qc=)8p>jNk)x5T=S73T01N<9Ii!4bx5#&1Aq7pOppAp&$*gr2rU? z=jf<7KZPbF}u zD8^unIh&7?taC)pp#Ox~?@qG?)j9mRao_V=8!)BtPfms5y@<|%e~Axo%R*!eta&D2 z*NZ>=D*G@5o}%cp3Wy~{xSSPhy1o5IP#L1)R$z|SJJ=a!M_-{uo+h6xfEZac0=^+? zE1FXZ0HX||BkC*cV25x*nqr74>_8b*C@Cn8h@z-Kc-lW|{aFt*v~gBz&KZi-!G&(u_8^pqh0KMu?jelJ%iv7tgFGjc zgoU(zp)(IqQvYh!vIhc(5PK(KQ9NDJWOZ(CyWA>Zor5r{!eU56kOw8Zp=oH6z}D4v z_B#ktP?;B^1@)RUinF%0I_ln6cB#Ifgi1;0gQp7{TU8{Yc-KEqX`NFNJKGglv~|vC z{4Ec@?F=B)!B9mKLqMzg6oKn2N7sX>^(||SYs>BeSH6e$XPnlago>s=<8EGPHya&5 zbmc&TRK0DzQ|H$;1y)-@IA)w*%+ebn_mzX3v&)E@UpmUX@W#$3

jrK68Q6vg?-Vt}G&FfV7$<0%^mELdl28{@C;ycpF#Qbcn7;z| zjvJu9l6GQ*@nEwA-S8O*Bl6O^hH9SOE{l)qfn?w9f>`i=heR9Xz9n8Ppxl&j3Oq;K zTh>NpK2u_~Oz!>#h-uAgYuxE51*ZM$&xr+(GgqnCV0;)ErQKIA8-#1U)SMfH4#g2{ z?h7r)ovxDBP{7TAQ_ECIbcWk)II%~pm?n~tdJjFDB`$>J270@?*UWAj8 mWY>N|?eWm#011!Z+x4SAS1BBPR|StRMn}Uyz4)YE#Qy@D$kTKH literal 0 HcmV?d00001 diff --git a/widget/diagramwidget/.gitignore b/widget/diagramwidget/.gitignore deleted file mode 100644 index f5803673..00000000 --- a/widget/diagramwidget/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# Mac OS X files -.DS_Store - -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ - -# Dependency directories (remove the comment below to include it) -# vendor/ diff --git a/widget/diagramwidget/README.md b/widget/diagramwidget/README.md deleted file mode 100644 index 13745a9a..00000000 --- a/widget/diagramwidget/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Fyne DiagramWidget - -This package contains a collection of widgets for the [Fyne](https://fyne.io/) -toolkit. The code here is intended to be production ready, but may be lacking -some desirable functional features. If you have suggestions for changes to -existing functionality or addition of new functionality, please look at the existing -issues in the repository to see if your idea is already on the table. If it is not, -feel free to open an issue. - -This collection should be considered a work in progress. When changes are made, -serious consideration will be given to backward compatibility, but compatibility -is not guaranteed. - -## Diagram Widget - -The DiagramWidget is intended to be incorporated into a Fyne application. It provides a -drawing area within which a diagram can be created. The diagram itself is a collection of -DiagramElement widgets (an interface). There are two types of DiagramElements: DiagramNode widgets and DiagramLink widgets. DiagramNode widgets are thin wrappers around a user-supplied CanvasObject. -Any valid CanvasObject can be used. DiagramLinks are line-based connections between DiagramElements. -Note that links can connect to other links as well as nodes. - -While some provisions have been made for automatic layout, layouts are for the convenience -of the author and are on-demand only. The design intent is that users will place the diagram elements for human readability. - -DiagramElements are essentially self-managed from a layout perspective. DiagramNodes have no size -constraints imposed by the DiagramWidget and can be placed anywhere. DiagramLinks connect -DiagramElements. The DiagramWidget keeps track of the DiagramElements to which each DiagramLink -is connected and calls the Refresh() method on the link when the connected diagram element is moved -or resized. - -* [demo](../../cmd/diagramdemo/main.go) - -### DiagramElement Interface - -A DiagramElement is the base interface for any element of the diagram being managed by the -DiagramWidget. It provides a common interface for DiagramNode and DiagramLink widgets. The DiagramElement -interface provides operations for retrieving the DiagramWidget, the ID of the DiagramElement, and -for showing and hiding the handles that are used for graphically manipulating the diagram element. -The specifics of what handles do are different for nodes and links - these are described below in the -sections for their respective widgets. - -### DiagramNode Widget - -The DiagramNode widget is a wrapper around a user-supplied CanvasObject. In addition to the user-supplied -CanvasObject, the node displays a border and, when selected, handles at the corners and edge mid-points that can be used to manipulate the size of the node. The node can be selected and dragged to a new position with a mouse by clicking in the border area around the canvas object. - -### DiagramLink Widget - -The DiagramLink widget provides a directed line-based connection between two DiagramElements. -The link is defined in terms of LinkPoints that are connected by LinkSegments (both of which -are widgets in their own right). The link maintains an array of points, with the point at index -[0] being the point at which the link connects to the source DiagramElement and the point at the -last index being the point at which the link connects to the target DiagramElement. The link also -maintains an array of line segments, with the segment at index [0] connecting points [0] and [1], -the segment at index [1] connecting the points [1] and [2], etc. The current implementation only -has a single segment, but interfaces will be added shortly to enable the addition and removal of -points and segments. - -Many visual languages (formalized diagrams) utilize graphical decorations on lines. The link -provides the ability to add an arbitrary number of graphic decorations at three points along -the link: the source end, the target end, and the midpoint. Decorations are stacked in the order -they are added at the indicated point. The location of the source and target points is obvious, -but the midpoint bears some discussion. If there is only one line segment, the midpoint is the -midpoint of this segment. If there is more than one line segment, the "midpoint" is defined to -be the next to last point in the array of points. For a two-segment link, this will be the point -at which the two segments join. For a multi-segment link, this will be the point at which the -next-to-last and last segments join. - -Also common in visual languages are textual annotations associated with either the link as a whole -or to the ends of the link. For this purpose, the link allows the association of one or more -AnchoredText widgets with each of the reference points on the link: source, target, and midpoint. -These widgets keep track of their position relative to the link's reference points. They can -be moved interactively with the mouse to a new position. When the reference point on the link -moves, the anchored text will also move, maintaining its relative position. - -Users do not create AnchoredText widgets directly: the link itself creates and manages them. -the user calls Add\AnchoredText(key, text) to add an anchored text. The key is expected -to be unique at the position and can be used to update the text later. The AnchoredText can also -be directly edited in the diagram. - -When a link connects to another link, it connects at the midpoint of the source or target link. \ No newline at end of file diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index f3f18fbd..33f4fa73 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -18,8 +18,8 @@ var Globaldiagram *DiagramWidget // The conditionals here are required during initialization. func ForceRepaint() { if Globaldiagram != nil { - if Globaldiagram.DummyBox != nil { - Globaldiagram.DummyBox.Refresh() + if Globaldiagram.dummyBox != nil { + Globaldiagram.dummyBox.Refresh() } } } @@ -53,8 +53,8 @@ type DiagramWidget struct { selection map[string]DiagramElement diagramElementLinkDependencies map[string][]linkPinPair - // TODO Remove DummyBox when fyne rendering issue is resolved - DummyBox *canvas.Rectangle + // TODO Remove dummyBox when fyne rendering issue is resolved + dummyBox *canvas.Rectangle } func NewDiagramWidget(id string) *DiagramWidget { @@ -66,12 +66,12 @@ func NewDiagramWidget(id string) *DiagramWidget { Offset: fyne.Position{X: 0, Y: 0}, Nodes: map[string]*DiagramNode{}, Links: map[string]*DiagramLink{}, - DummyBox: canvas.NewRectangle(color.Transparent), + dummyBox: canvas.NewRectangle(color.Transparent), selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPinPair{}, } - dw.DummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) - dw.DummyBox.Move(fyne.Position{X: 50, Y: 50}) + dw.dummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) + dw.dummyBox.Move(fyne.Position{X: 50, Y: 50}) dw.ExtendBaseWidget(dw) @@ -237,7 +237,7 @@ func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { for _, e := range r.diagramWidget.Links { obj = append(obj, e) } - obj = append(obj, r.diagramWidget.DummyBox) + obj = append(obj, r.diagramWidget.dummyBox) return obj } From 562448b806615a455742f7656c54fd131decb26d Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 27 Apr 2023 14:32:15 -0400 Subject: [PATCH 18/53] Scrolling; ForceRefresh; factored out StringForceLayout Added Scrolling to DiagramWidget example, made ForceRefresh local to diagramwidget --- cmd/diagramdemo/main.go | 16 +++++++------ widget/diagramwidget/anchoredtext.go | 3 ++- widget/diagramwidget/connectionpad.go | 2 +- widget/diagramwidget/diagram.go | 23 ++++++++----------- widget/diagramwidget/explicitlayout | 8 +++++++ widget/diagramwidget/handle.go | 4 ++-- widget/diagramwidget/link.go | 5 +++- widget/diagramwidget/linksegment.go | 2 +- widget/diagramwidget/node.go | 8 +++---- .../{forcelayout.go => springforcelayout.go} | 10 ++++---- 10 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 widget/diagramwidget/explicitlayout rename widget/diagramwidget/{forcelayout.go => springforcelayout.go} (85%) diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index 71d9ec4a..b5a3107a 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -15,15 +15,15 @@ import ( var forceticks int = 0 -func forceanim() { +func forceanim(diagramWidget *diagramwidget.DiagramWidget) { // XXX: very naughty -- accesses shared memory in potentially unsafe // ways, this almost certainly has race conditions... don't do this! for { if forceticks > 0 { - diagramwidget.Globaldiagram.StepForceLayout(300) - diagramwidget.Globaldiagram.Refresh() + diagramwidget.StepForceLayout(diagramWidget, 300) + diagramWidget.Refresh() forceticks-- fmt.Printf("forceticks=%d\n", forceticks) } @@ -39,9 +39,10 @@ func main() { w.SetMaster() diagramWidget := diagramwidget.NewDiagramWidget("Diagram1") - diagramwidget.Globaldiagram = diagramWidget - go forceanim() + scrollContainer := container.NewScroll(diagramWidget) + + go forceanim(diagramWidget) // Node 0 node0Label := widget.NewLabel("Node0") @@ -81,7 +82,7 @@ func main() { // Node 3 node3 := diagramwidget.NewDiagramNode(diagramWidget, widget.NewButton("Node3: Force layout step", func() { - diagramWidget.StepForceLayout(300) + diagramwidget.StepForceLayout(diagramWidget, 300) diagramWidget.Refresh() }), "Node3") node3.Move(fyne.Position{X: 400, Y: 100}) @@ -136,8 +137,9 @@ func main() { link5.AddMidpointAnchoredText("linkName", "Link 5") link5.AddTargetDecoration(diagramwidget.NewArrowhead()) - w.SetContent(diagramWidget) + w.SetContent(scrollContainer) + w.Resize(fyne.NewSize(600, 400)) w.ShowAndRun() } diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index a7a42563..eec4a344 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -18,6 +18,7 @@ import ( // move by the same amount type AnchoredText struct { widget.BaseWidget + link *DiagramLink offset r2.Vec2 referencePosition fyne.Position displayedText string @@ -66,7 +67,7 @@ func (at *AnchoredText) Dragged(event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} at.Move(at.Position().Add(delta)) at.Refresh() - ForceRepaint() + at.link.diagramElement.GetDiagram().forceRepaint() } // MinSize returns a fixed minimum size for the anchored text. diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index ef7c8ed5..050f36fb 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -240,5 +240,5 @@ func (rpr *rectanglePadRenderer) Refresh() { rpr.rect.StrokeColor = rpr.rp.padOwner.GetDiagram().GetForegroundColor() rpr.rect.FillColor = color.Transparent rpr.rect.StrokeWidth = padLineWidth - ForceRepaint() + rpr.rp.connectionPad.padOwner.GetDiagram().forceRepaint() } diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 33f4fa73..4e728a4b 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -10,17 +10,13 @@ import ( "fyne.io/fyne/v2/widget" ) -var Globaldiagram *DiagramWidget - -// ForceRepaint is a workaround for a Fyne bug (Issue #2205) in which moving a canvas object does not +// forceRepaint is a workaround for a Fyne bug (Issue #2205) in which moving a canvas object does not // trigger repainting. When the issue is resolved, this function and all references to it should be // removed. The DummyBox on the GlobalDiagram should also be removed. // The conditionals here are required during initialization. -func ForceRepaint() { - if Globaldiagram != nil { - if Globaldiagram.dummyBox != nil { - Globaldiagram.dummyBox.Refresh() - } +func (dw *DiagramWidget) forceRepaint() { + if dw != nil && dw.dummyBox != nil { + dw.dummyBox.Refresh() } } @@ -44,8 +40,7 @@ type DiagramWidget struct { ThemeVariant fyne.ThemeVariant Offset fyne.Position - // DesiredSize specifies the size which the graph widget should take - // up, defaults to 800 x 600 + // DesiredSize specifies the size of the displayed diagram. Defaults to 800 x 600 DesiredSize fyne.Size Nodes map[string]*DiagramNode @@ -128,7 +123,7 @@ func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.Poi if !dw.IsSelected(de) { dw.addElementToSelection(de) } - ForceRepaint() + dw.forceRepaint() } func (dw *DiagramWidget) DragEnd() { @@ -150,13 +145,13 @@ func (dw *DiagramWidget) GetHoverColor() color.Color { func (dw *DiagramWidget) DiagramNodeDragged(node *DiagramNode, event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} dw.DisplaceNode(node, delta) - ForceRepaint() + dw.forceRepaint() } func (dw *DiagramWidget) DisplaceNode(node *DiagramNode, delta fyne.Position) { node.Move(node.Position().Add(delta)) dw.refreshDependentLinks(node) - ForceRepaint() + dw.forceRepaint() } func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { @@ -210,7 +205,7 @@ func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { for _, de := range dw.selection { dw.removeElementFromSelection(de) } - ForceRepaint() + dw.forceRepaint() } // diagramWidgetRenderer diff --git a/widget/diagramwidget/explicitlayout b/widget/diagramwidget/explicitlayout new file mode 100644 index 00000000..31dcfe27 --- /dev/null +++ b/widget/diagramwidget/explicitlayout @@ -0,0 +1,8 @@ +package diagramwidget + +// ExplicitLayout is an interface for layout algorithms that can be applied to a DiagramWidget. +// The algorithm execution is initiated programmatically, i.e. is not automatically applied when +// the contents of the DiagramWidget are changed. +interface ExplicitLayout { + PerformLayout(diagramWidget *DiagramWidget) +} \ No newline at end of file diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go index 70af5383..c2b8388f 100644 --- a/widget/diagramwidget/handle.go +++ b/widget/diagramwidget/handle.go @@ -37,7 +37,7 @@ func (h *Handle) CreateRenderer() fyne.WidgetRenderer { } hr.rect.FillColor = color.Transparent hr.Refresh() - ForceRepaint() + h.de.GetDiagram().forceRepaint() return hr } @@ -103,5 +103,5 @@ func (hr *handleRenderer) Refresh() { hr.rect.StrokeColor = hr.handle.getStrokeColor() hr.rect.FillColor = color.Transparent hr.rect.StrokeWidth = hr.handle.getStrokeWidth() - ForceRepaint() + hr.handle.de.GetDiagram().forceRepaint() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 703fa2ca..676b9aea 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -99,6 +99,7 @@ func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { // Multiple AnchoredText widgets can be added. func (dl *DiagramLink) AddSourceAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) + at.link = dl dl.sourceAnchoredText[key] = at at.SetReferencePosition(dl.getSourcePosition()) at.Move(dl.getSourcePosition()) @@ -118,6 +119,7 @@ func (dl *DiagramLink) AddSourceDecoration(decoration Decoration) { // Multiple AnchoredText widgets can be added. func (dl *DiagramLink) AddMidpointAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) + at.link = dl dl.midpointAnchoredText[key] = at at.SetReferencePosition(dl.getMidPosition()) at.Move(dl.getMidPosition()) @@ -137,6 +139,7 @@ func (dl *DiagramLink) AddMidpointDecoration(decoration Decoration) { // Multiple AnchoredText widgets can be added. func (dl *DiagramLink) AddTargetAnchoredText(key string, displayedText string) { at := NewAnchoredText(displayedText) + at.link = dl dl.targetAnchoredText[key] = at at.SetReferencePosition(dl.getTargetPosition()) at.Move(dl.getTargetPosition()) @@ -357,5 +360,5 @@ func (dlr *diagramLinkRenderer) Refresh() { decoration.Refresh() } dlr.link.diagram.refreshDependentLinks(dlr.link) - ForceRepaint() + dlr.link.GetDiagram().forceRepaint() } diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index e238328f..ef2d5f1f 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -68,5 +68,5 @@ func (lsr *linkSegmentRenderer) Refresh() { lsr.line.Position2 = lsr.ls.p2 lsr.line.StrokeColor = lsr.ls.link.LinkColor lsr.line.StrokeWidth = lsr.ls.link.strokeWidth - ForceRepaint() + lsr.ls.link.GetDiagram().forceRepaint() } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 679f047e..382e20ca 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -163,7 +163,7 @@ func (dn *DiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) { dn.InnerSize = dn.innerObject.MinSize().Max(trialInnerSize) dn.Resize(dn.Size().Add(sizeChange)) dn.Refresh() - ForceRepaint() + dn.GetDiagram().forceRepaint() } func (dn *DiagramNode) innerPos() fyne.Position { @@ -175,12 +175,12 @@ func (dn *DiagramNode) innerPos() fyne.Position { func (dn *DiagramNode) MouseIn(event *desktop.MouseEvent) { dn.HandleColor = dn.diagram.DiagramTheme.Color(theme.ColorNameFocus, dn.diagram.ThemeVariant) - ForceRepaint() + dn.GetDiagram().forceRepaint() } func (dn *DiagramNode) MouseOut() { dn.HandleColor = dn.diagram.DiagramTheme.Color(theme.ColorNameForeground, dn.diagram.ThemeVariant) - ForceRepaint() + dn.GetDiagram().forceRepaint() } func (dn *DiagramNode) MouseMoved(event *desktop.MouseEvent) { @@ -300,5 +300,5 @@ func (dnr *diagramNodeRenderer) Refresh() { dnr.box.StrokeColor = dnr.node.diagram.GetForegroundColor() dnr.node.edgePad.Refresh() dnr.node.diagram.refreshDependentLinks(dnr.node) - ForceRepaint() + dnr.node.GetDiagram().forceRepaint() } diff --git a/widget/diagramwidget/forcelayout.go b/widget/diagramwidget/springforcelayout.go similarity index 85% rename from widget/diagramwidget/forcelayout.go rename to widget/diagramwidget/springforcelayout.go index df8b754a..bba709ff 100644 --- a/widget/diagramwidget/forcelayout.go +++ b/widget/diagramwidget/springforcelayout.go @@ -9,7 +9,7 @@ import ( ) // adjacent returns true if there is at least one edge between n1 and n2 -func (dw *DiagramWidget) adjacent(n1, n2 *DiagramNode) bool { +func adjacent(dw *DiagramWidget, n1, n2 *DiagramNode) bool { // TODO: expensive, may be worth caching? for _, e := range dw.Links { if ((e.sourcePad.GetPadOwner() == n1) && (e.targetPad.GetPadOwner() == n2)) || ((e.sourcePad.GetPadOwner() == n2) && (e.targetPad.GetPadOwner() == n1)) { @@ -27,14 +27,14 @@ func calculateDistance(n1, n2 *DiagramNode) float64 { // calculateForce calculates the force between the given pair of nodes. // // The force is calculated at n1. -func (dw *DiagramWidget) calculateForce(n1, n2 *DiagramNode, targetLength float64) r2.Vec2 { +func calculateForce(dw *DiagramWidget, n1, n2 *DiagramNode, targetLength float64) r2.Vec2 { // spring constant for linear spring k := float64(0.01) d := calculateDistance(n1, n2) v := n2.R2Center().Add(n1.R2Center().Scale(-1)).Unit().Scale(-1) - if dw.adjacent(n1, n2) { + if adjacent(dw, n1, n2) { // adjacent nodes act like springs, and want to be close to the given // length. @@ -61,7 +61,7 @@ func (dw *DiagramWidget) calculateForce(n1, n2 *DiagramNode, targetLength float6 // StepForceLayout calculates one step of force directed graph layout, with // the target distance between adjacent nodes being targetLength. -func (dw *DiagramWidget) StepForceLayout(targetLength float64) { +func StepForceLayout(dw *DiagramWidget, targetLength float64) { deltas := make(map[string]r2.Vec2) // calculate all the deltas from the current state @@ -72,7 +72,7 @@ func (dw *DiagramWidget) StepForceLayout(targetLength float64) { if j == k { continue } - deltas[k] = deltas[k].Add(dw.calculateForce(nk, nj, targetLength)) + deltas[k] = deltas[k].Add(calculateForce(dw, nk, nj, targetLength)) } } From 2d17ea65ded4c47daa7d5492670b7e49c0043ca5 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 27 Apr 2023 14:33:39 -0400 Subject: [PATCH 19/53] Delete explicitlayout --- widget/diagramwidget/explicitlayout | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 widget/diagramwidget/explicitlayout diff --git a/widget/diagramwidget/explicitlayout b/widget/diagramwidget/explicitlayout deleted file mode 100644 index 31dcfe27..00000000 --- a/widget/diagramwidget/explicitlayout +++ /dev/null @@ -1,8 +0,0 @@ -package diagramwidget - -// ExplicitLayout is an interface for layout algorithms that can be applied to a DiagramWidget. -// The algorithm execution is initiated programmatically, i.e. is not automatically applied when -// the contents of the DiagramWidget are changed. -interface ExplicitLayout { - PerformLayout(diagramWidget *DiagramWidget) -} \ No newline at end of file From f408904bb5a98d690517156f63c5058d123bd197 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 28 Apr 2023 14:58:36 -0400 Subject: [PATCH 20/53] Refined Polygon point positioning; Added pad interfaces. --- widget/diagramwidget/arrowhead.go | 5 ++--- widget/diagramwidget/decoration.go | 4 ++++ widget/diagramwidget/diagram.go | 9 ++++++++ widget/diagramwidget/diagramElement.go | 6 ++++++ widget/diagramwidget/link.go | 5 +++++ widget/diagramwidget/node.go | 6 ++++++ widget/diagramwidget/polygon.go | 29 ++++++++++++++------------ 7 files changed, 48 insertions(+), 16 deletions(-) diff --git a/widget/diagramwidget/arrowhead.go b/widget/diagramwidget/arrowhead.go index 3d17b474..39685383 100644 --- a/widget/diagramwidget/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -13,9 +13,8 @@ import ( ) const ( - defaultTheta float64 = 0.5235 // 30 degrees in radians - defaultStrokeWidth float32 = 2 - defaultLength int = 15 + defaultTheta float64 = 0.5235 // 30 degrees in radians + defaultLength int = 15 ) // Arrowhead defines a canvas object which renders an arrow. The arrowhead is defined with reference diff --git a/widget/diagramwidget/decoration.go b/widget/diagramwidget/decoration.go index 250927cf..7630c473 100644 --- a/widget/diagramwidget/decoration.go +++ b/widget/diagramwidget/decoration.go @@ -6,6 +6,10 @@ import ( "fyne.io/fyne/v2" ) +const ( + defaultStrokeWidth float32 = 1 +) + // Decoration is a widget intended to be used as a decoration on a Link widget // The graphical representation of the widget is defined along a reference axis with // one point on that axis designated as the reference point (generally the origin). diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 4e728a4b..8f8f8b3b 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -134,6 +134,15 @@ func (dw *DiagramWidget) GetBackgroundColor() color.Color { return dw.DiagramTheme.Color(theme.ColorNameBackground, dw.ThemeVariant) } +func (dw *DiagramWidget) GetDiagramElement(elementID string) DiagramElement { + var de DiagramElement + de = dw.Nodes[elementID] + if de == nil { + de = dw.Links[elementID] + } + return de +} + func (dw *DiagramWidget) GetForegroundColor() color.Color { return dw.DiagramTheme.Color(theme.ColorNameForeground, dw.ThemeVariant) } diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index 7a59cc64..f6dafe02 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -6,10 +6,16 @@ import "fyne.io/fyne/v2" // elements are Node and Link widgets. type DiagramElement interface { fyne.Widget + // GetDefaultConnectionPad returns the default pad for the DiagramElement + GetDefaultConnectionPad() ConnectionPad + // GetDiagram returns the DiagramWidget to which the DiagramElement belongs GetDiagram() *DiagramWidget + // GetDiagramElementID returns the string identifier provided at the time the DiagramElement was created GetDiagramElementID() string handleDragged(handle *Handle, event *fyne.DragEvent) + // HideHandles hides the handles on the DiagramElement HideHandles() + // ShowHandles shows the handles on the DiagramElement ShowHandles() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 676b9aea..b42d0d71 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -154,6 +154,11 @@ func (dl *DiagramLink) AddTargetDecoration(decoration Decoration) { dl.Refresh() } +// GetDefaultConnectionPad returns the midPad of the Link +func (dl *DiagramLink) GetDefaultConnectionPad() ConnectionPad { + return dl.GetMidPad() +} + // GetMidPad returns the PointPad at the midpoint so that it can be used as either the Source or Target // pad for another Link. func (dl *DiagramLink) GetMidPad() ConnectionPad { diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 382e20ca..6cfcf614 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -121,6 +121,12 @@ func (dn *DiagramNode) findKeyForHandle(handle *Handle) string { return "" } +// GetDefaultConnectionPad returns the edge pad for the node +func (dn *DiagramNode) GetDefaultConnectionPad() ConnectionPad { + return dn.GetEdgePad() +} + +// GetEdgePad returns the edge pad for the node func (dn *DiagramNode) GetEdgePad() ConnectionPad { return dn.edgePad } diff --git a/widget/diagramwidget/polygon.go b/widget/diagramwidget/polygon.go index bc04c4ac..1299f4b9 100644 --- a/widget/diagramwidget/polygon.go +++ b/widget/diagramwidget/polygon.go @@ -104,16 +104,18 @@ func (p *Polygon) getRenderingData() ([]fyne.Position, fyne.Position) { xMin = math.Min(xMin, 0) yMin = math.Min(yMin, 0) // the inverse of xMin and yMin now represent the point translation required to - // normalize the coordinates for rendering into a pixmap - normalizationVector := r2.MakeVec2(float64(-xMin), float64(-yMin)) + // normalize the coordinates for rendering into a pixmap, ignoring the stroke width. + // We have to further shift coordinates by the stroke width so that the points render + // at the correct positions in a pixmap. We have to add the stroke width as well + normalizationVectorWithStrokeOfset := r2.MakeVec2(float64(-xMin)+float64(p.StrokeWidth/2), float64(-yMin)+float64(p.StrokeWidth/2)) renderingPoints := []fyne.Position{} for _, point := range rotatedPoints { pointVector := r2.MakeVec2(float64(point.X), float64(point.Y)) - normalizedPointVector := pointVector.Add(normalizationVector) + normalizedPointVector := pointVector.Add(normalizationVectorWithStrokeOfset) normalizedPoint := fyne.Position{X: float32(normalizedPointVector.X), Y: float32(normalizedPointVector.Y)} renderingPoints = append(renderingPoints, normalizedPoint) } - // The offset vector is the inverse of the normalization vector + // The offset vector is the inverse of the normalization vector without the stroke width offsetVector := fyne.Position{X: float32(xMin), Y: float32(yMin)} return renderingPoints, offsetVector } @@ -145,10 +147,11 @@ func (p *Polygon) getRotatedPoints() []fyne.Position { return rotatedPoints } -// MinSize returns the minimum size based on nominal polygon points and base angle +// MinSize returns the minimum size based on nominal polygon points, base angle, and stroke width func (p *Polygon) MinSize() fyne.Size { - // The origin is always one of the points regardless of whether the polygon uses that point - points := []r2.Vec2{{X: 0.0, Y: 0.0}} + // // The origin is always one of the points regardless of whether the polygon uses that point + // points := []r2.Vec2{{X: 0.0, Y: 0.0}} + points := []r2.Vec2{} for _, point := range p.getRotatedPoints() { points = append(points, r2.Vec2{X: float64(point.X), Y: float64(point.Y)}) } @@ -227,9 +230,9 @@ func (pr *polygonRenderer) Refresh() { renderingPoints, offsetVector := pr.polygon.getRenderingData() // For efficiency, only get the size once polygonSize := pr.polygon.MinSize() - width := int(polygonSize.Width) - height := int(polygonSize.Height) stroke := pr.polygon.StrokeWidth + width := int(polygonSize.Width + pr.polygon.StrokeWidth) + height := int(polygonSize.Height + pr.polygon.StrokeWidth) pr.polygon.Resize(polygonSize) raw := image.NewRGBA(image.Rect(0, 0, width, height)) scanner := rasterx.NewScannerGV(int(polygonSize.Width), int(polygonSize.Height), raw, raw.Bounds()) @@ -243,9 +246,9 @@ func (pr *polygonRenderer) Refresh() { } for i, point := range renderingPoints { if i == 0 { - filler.Start(rasterx.ToFixedP(float64(point.X+stroke/2), float64(point.Y+stroke/2))) + filler.Start(rasterx.ToFixedP(float64(point.X), float64(point.Y))) } else { - filler.Line(rasterx.ToFixedP(float64(point.X+stroke/2), float64(point.Y+stroke/2))) + filler.Line(rasterx.ToFixedP(float64(point.X), float64(point.Y))) } } filler.Stop(true) @@ -258,9 +261,9 @@ func (pr *polygonRenderer) Refresh() { dasher.SetStroke(fixed.Int26_6(float64(stroke)*64), 0, nil, nil, nil, 0, nil, 0) for i, point := range renderingPoints { if i == 0 { - dasher.Start(rasterx.ToFixedP(float64(point.X+stroke/2), float64(point.Y+stroke/2))) + dasher.Start(rasterx.ToFixedP(float64(point.X), float64(point.Y))) } else { - dasher.Line(rasterx.ToFixedP(float64(point.X+stroke/2), float64(point.Y+stroke/2))) + dasher.Line(rasterx.ToFixedP(float64(point.X), float64(point.Y))) } } dasher.Stop(true) From 34cd696ba94eb9c9864294de02a0b48cdd45a9b3 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Sun, 30 Apr 2023 15:54:25 -0400 Subject: [PATCH 21/53] Added documentation --- widget/diagramwidget/diagram.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 8f8f8b3b..9da54530 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -28,6 +28,9 @@ type linkPinPair struct { pin *LinkPoint } +// DiagramWidget maintains a diagram consisting of DiagramNodes and DiagramLinks. The layout of +// the nodes and links does not change when the DiagramWidget is resized: they are either positioned +// manually (interactively) or programmatically. type DiagramWidget struct { widget.BaseWidget @@ -52,6 +55,8 @@ type DiagramWidget struct { dummyBox *canvas.Rectangle } +// NewDiagramWidget creates a DiagramWidget. The user-supplied ID can be used to map the diagram +// 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, @@ -73,6 +78,7 @@ func NewDiagramWidget(id string) *DiagramWidget { return dw } +// AddLink adds a link to the diagram func (dw *DiagramWidget) AddLink(link *DiagramLink) { dw.Links[link.id] = link link.Refresh() @@ -95,12 +101,14 @@ func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link * } } +// AddNode adds a node to the diagram func (dw *DiagramWidget) AddNode(node *DiagramNode) { dw.Nodes[node.id] = node node.Refresh() // TODO add logic to rezise diagram if necessary } +// CreateRenderer creates the renderer for the diagram func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { r := diagramWidgetRenderer{ diagramWidget: dw, @@ -115,10 +123,12 @@ func (dw *DiagramWidget) addElementToSelection(de DiagramElement) { } } +// Cursor returns the default cursor func (dw *DiagramWidget) Cursor() desktop.Cursor { return desktop.DefaultCursor } +// DiagramElementTapped adds the element to the selection when the element is tapped func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.PointEvent) { if !dw.IsSelected(de) { dw.addElementToSelection(de) @@ -126,14 +136,19 @@ func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.Poi dw.forceRepaint() } +// DragEnd is called when the drag comes to an end. It refreshes the widget func (dw *DiagramWidget) DragEnd() { dw.Refresh() } +// GetBackgroundColor returns the background color for the widget from the diagram's theme, which +// may be different from the application's theme. func (dw *DiagramWidget) GetBackgroundColor() color.Color { return dw.DiagramTheme.Color(theme.ColorNameBackground, dw.ThemeVariant) } +// 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] @@ -143,26 +158,36 @@ func (dw *DiagramWidget) GetDiagramElement(elementID string) DiagramElement { return de } +// GetForegroundColor returns the foreground color from the diagram's theme, which may +// be different from the application's theme func (dw *DiagramWidget) GetForegroundColor() color.Color { return dw.DiagramTheme.Color(theme.ColorNameForeground, dw.ThemeVariant) } +// GetHoverColor returns the hover color from the diagram's theme, which may may +// be different from the application's theme func (dw *DiagramWidget) GetHoverColor() color.Color { return dw.DiagramTheme.Color(theme.ColorNameHover, dw.ThemeVariant) } +// DiagramNodeDragged moves the indicated node and refreshes any links that may be attached +// to it func (dw *DiagramWidget) DiagramNodeDragged(node *DiagramNode, event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} dw.DisplaceNode(node, delta) dw.forceRepaint() } +// DisplaceNode moves the indicated node and refreshes any links that may be attached +// to it func (dw *DiagramWidget) DisplaceNode(node *DiagramNode, delta fyne.Position) { node.Move(node.Position().Add(delta)) dw.refreshDependentLinks(node) dw.forceRepaint() } +// Dragged responds to a drag movement in the background of the diagram. It moves all nodes +// in the diagram and refreshes all links. func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} for _, n := range dw.Nodes { @@ -171,16 +196,20 @@ func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { dw.Refresh() } +// IsSelected returns true if the indicated element is currently part of the selection func (dw *DiagramWidget) IsSelected(de DiagramElement) bool { return dw.selection[de.GetDiagramElementID()] != nil } +// MouseIn responds to the mouse moving into the diagram. It presently is a noop func (dw *DiagramWidget) MouseIn(event *desktop.MouseEvent) { } +// MouseOut responds to the mouse leaving the diagram. It presently is a noop func (dw *DiagramWidget) MouseOut() { } +// MouseMoved responds to mouse movements in the diagram. It presently is a noop func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { } @@ -210,6 +239,8 @@ func (dw *DiagramWidget) refreshDependentLinks(de DiagramElement) { } } +// Tapped respondss to taps in the diagram background. It removes all diagram elements +// from the selection func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { for _, de := range dw.selection { dw.removeElementFromSelection(de) @@ -226,7 +257,6 @@ func (r *diagramWidgetRenderer) Destroy() { } func (r *diagramWidgetRenderer) Layout(size fyne.Size) { - // r.diagramWidget.at.Move(fyne.Position{X: 100, Y: 100}) } func (r *diagramWidgetRenderer) MinSize() fyne.Size { From 5b4c073844f9d7f9763231fe16859e905a9dd928 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 2 May 2023 16:02:12 -0400 Subject: [PATCH 22/53] Handling of DiagramElement deletion --- widget/diagramwidget/diagram.go | 69 ++++++++++++++++++++++------ widget/diagramwidget/diagram_test.go | 43 +++++++++++++++++ widget/diagramwidget/link.go | 6 +-- widget/diagramwidget/node.go | 2 +- 4 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 widget/diagramwidget/diagram_test.go diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 9da54530..768ae06d 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -2,6 +2,7 @@ package diagramwidget import ( "image/color" + "reflect" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" @@ -23,9 +24,9 @@ func (dw *DiagramWidget) forceRepaint() { // Verify that interfaces are fully implemented var _ fyne.Tappable = (*DiagramWidget)(nil) -type linkPinPair struct { +type linkPadPair struct { link *DiagramLink - pin *LinkPoint + pad ConnectionPad } // DiagramWidget maintains a diagram consisting of DiagramNodes and DiagramLinks. The layout of @@ -49,7 +50,7 @@ type DiagramWidget struct { Nodes map[string]*DiagramNode Links map[string]*DiagramLink selection map[string]DiagramElement - diagramElementLinkDependencies map[string][]linkPinPair + diagramElementLinkDependencies map[string][]linkPadPair // TODO Remove dummyBox when fyne rendering issue is resolved dummyBox *canvas.Rectangle @@ -68,7 +69,7 @@ func NewDiagramWidget(id string) *DiagramWidget { Links: map[string]*DiagramLink{}, dummyBox: canvas.NewRectangle(color.Transparent), selection: map[string]DiagramElement{}, - diagramElementLinkDependencies: map[string][]linkPinPair{}, + diagramElementLinkDependencies: map[string][]linkPadPair{}, } dw.dummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) dw.dummyBox.Move(fyne.Position{X: 50, Y: 50}) @@ -78,31 +79,31 @@ func NewDiagramWidget(id string) *DiagramWidget { return dw } -// AddLink adds a link to the diagram -func (dw *DiagramWidget) AddLink(link *DiagramLink) { +// addLink adds a link to the diagram +func (dw *DiagramWidget) addLink(link *DiagramLink) { dw.Links[link.id] = link link.Refresh() // TODO add logic to rezise diagram if necessary } -func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link *DiagramLink, pin *LinkPoint) { +func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link *DiagramLink, pad ConnectionPad) { deID := diagramElement.GetDiagramElementID() currentDependencies := dw.diagramElementLinkDependencies[deID] if currentDependencies == nil { - dw.diagramElementLinkDependencies[deID] = []linkPinPair{{link, pin}} + dw.diagramElementLinkDependencies[deID] = []linkPadPair{{link, pad}} } else { for _, pair := range currentDependencies { - if pair.link == link && pair.pin == pin { + if pair.link == link && pair.pad == pad { // it's already there return } } - dw.diagramElementLinkDependencies[deID] = append(currentDependencies, linkPinPair{link, pin}) + dw.diagramElementLinkDependencies[deID] = append(currentDependencies, linkPadPair{link, pad}) } } -// AddNode adds a node to the diagram -func (dw *DiagramWidget) AddNode(node *DiagramNode) { +// addNode adds a node to the diagram +func (dw *DiagramWidget) addNode(node *DiagramNode) { dw.Nodes[node.id] = node node.Refresh() // TODO add logic to rezise diagram if necessary @@ -152,7 +153,7 @@ func (dw *DiagramWidget) GetBackgroundColor() color.Color { func (dw *DiagramWidget) GetDiagramElement(elementID string) DiagramElement { var de DiagramElement de = dw.Nodes[elementID] - if de == nil { + if reflect.ValueOf(de).IsNil() { de = dw.Links[elementID] } return de @@ -213,19 +214,38 @@ func (dw *DiagramWidget) MouseOut() { func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { } +// removeDependenciesInvolvingLink re-creates the diagram's dependencies, omitting any +// that involve the indicated link. This is a convoluted way of removing any entries +// involving the link. +func (dw *DiagramWidget) removeDependenciesInvolvingLink(linkID string) { + newMap := map[string][]linkPadPair{} + for elementID, dependencies := range dw.diagramElementLinkDependencies { + newDependencies := []linkPadPair{} + for _, pair := range dependencies { + if pair.link.id != linkID { + newDependencies = append(newDependencies, pair) + } + } + if len(newDependencies) > 0 { + newMap[elementID] = newDependencies + } + } + dw.diagramElementLinkDependencies = newMap +} + func (dw *DiagramWidget) removeElementFromSelection(de DiagramElement) { delete(dw.selection, de.GetDiagramElementID()) de.HideHandles() } -func (dw *DiagramWidget) removeLinkDependency(diagramElement DiagramElement, link *DiagramLink, pin *LinkPoint) { +func (dw *DiagramWidget) removeLinkDependency(diagramElement DiagramElement, link *DiagramLink, pad ConnectionPad) { deID := diagramElement.GetDiagramElementID() currentDependencies := dw.diagramElementLinkDependencies[deID] if currentDependencies == nil { return } for i, pair := range currentDependencies { - if pair.link == link && pair.pin == pin { + if pair.link == link && pair.pad == pad { dw.diagramElementLinkDependencies[deID] = append(currentDependencies[:i], currentDependencies[i+1:]...) return } @@ -239,6 +259,25 @@ func (dw *DiagramWidget) refreshDependentLinks(de DiagramElement) { } } +// RemoveElement removes the element from the diagram. It also removes any linkss to the element +func (dw *DiagramWidget) RemoveElement(elementID string) { + element := dw.GetDiagramElement(elementID) + // We make a copy of the dependencies because the array can get modified during the iteration + currentDependencies := append([]linkPadPair(nil), dw.diagramElementLinkDependencies[elementID]...) + for _, pair := range currentDependencies { + dw.RemoveElement(pair.link.id) + } + delete(dw.diagramElementLinkDependencies, elementID) + switch element.(type) { + case *DiagramNode: + delete(dw.Nodes, elementID) + case *DiagramLink: + delete(dw.Links, elementID) + dw.removeDependenciesInvolvingLink(elementID) + } + dw.Refresh() +} + // Tapped respondss to taps in the diagram background. It removes all diagram elements // from the selection func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { diff --git a/widget/diagramwidget/diagram_test.go b/widget/diagramwidget/diagram_test.go new file mode 100644 index 00000000..987b0131 --- /dev/null +++ b/widget/diagramwidget/diagram_test.go @@ -0,0 +1,43 @@ +package diagramwidget + +import ( + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/test" + "github.com/stretchr/testify/assert" +) + +func TestDependencies(t *testing.T) { + app := test.NewApp() + assert.NotNil(t, app) + diagram := NewDiagramWidget("Diagram1") + node1ID := "Node1" + node1 := NewDiagramNode(diagram, nil, node1ID) + node1.Move(fyne.NewPos(100, 100)) + node2ID := "Node2" + node2 := NewDiagramNode(diagram, nil, node2ID) + node2.Move(fyne.NewPos(200, 100)) + assert.Equal(t, 0, len(diagram.diagramElementLinkDependencies)) + linkID := "Link1" + link := NewDiagramLink(diagram, node1.edgePad, node2.edgePad, linkID) + 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.edgePad, node1Dependencies[0].pad) + + node2Dependencies := diagram.diagramElementLinkDependencies[node2ID] + assert.Equal(t, 1, len(node2Dependencies)) + assert.Equal(t, link, node2Dependencies[0].link) + assert.Equal(t, node2.edgePad, 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.Equal(t, 0, len(diagram.diagramElementLinkDependencies)) + +} diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index b42d0d71..127762ae 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -76,9 +76,9 @@ func NewDiagramLink(diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, dl.midPad.Move(dl.getMidPosition()) dl.ExtendBaseWidget(dl) - dl.diagram.AddLink(dl) - dl.diagram.addLinkDependency(dl.sourcePad.GetPadOwner(), dl, dl.linkPoints[0]) - dl.diagram.addLinkDependency(dl.targetPad.GetPadOwner(), dl, dl.linkPoints[1]) + dl.diagram.addLink(dl) + dl.diagram.addLinkDependency(dl.sourcePad.GetPadOwner(), dl, dl.sourcePad) + dl.diagram.addLinkDependency(dl.targetPad.GetPadOwner(), dl, dl.targetPad) dl.Refresh() return dl } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 6cfcf614..4717dc5d 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -71,7 +71,7 @@ func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string newHandle.Hide() } dn.ExtendBaseWidget(dn) - dn.diagram.AddNode(dn) + dn.diagram.addNode(dn) dn.Refresh() return dn } From 456a9ce42afed697205d384970864dc3d4aa1f65 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 17 May 2023 11:18:12 -0400 Subject: [PATCH 23/53] Interaction Work Added binding to anchored text; Made Node and Link extensible; Fixed handles so that resizing does not move node --- widget/diagramwidget/anchoredtext.go | 54 ++++-- widget/diagramwidget/arrowhead.go | 4 +- widget/diagramwidget/connectionpad.go | 4 +- widget/diagramwidget/decoration.go | 2 +- widget/diagramwidget/diagram.go | 44 ++--- widget/diagramwidget/diagramElement.go | 63 +++++- widget/diagramwidget/handle.go | 4 +- widget/diagramwidget/link.go | 211 +++++++++++--------- widget/diagramwidget/linkpoint.go | 2 +- widget/diagramwidget/linksegment.go | 6 +- widget/diagramwidget/node.go | 222 +++++++++++++--------- widget/diagramwidget/polygon.go | 4 +- widget/diagramwidget/springforcelayout.go | 8 +- 13 files changed, 391 insertions(+), 237 deletions(-) diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index eec4a344..08e8e205 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -4,7 +4,7 @@ import ( "image/color" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -18,11 +18,12 @@ import ( // move by the same amount type AnchoredText struct { widget.BaseWidget - link *DiagramLink - offset r2.Vec2 - referencePosition fyne.Position - displayedText string - ForegroundColor color.Color + link *BaseDiagramLink + offset r2.Vec2 + referencePosition fyne.Position + displayedTextBinding binding.String + ForegroundColor color.Color + textEntry *widget.Entry } // NewAnchoredText creates an textual annotation for a link. After it is created, one of the @@ -30,11 +31,16 @@ type AnchoredText struct { // anchored text with the appropriate reference point on the link. func NewAnchoredText(text string) *AnchoredText { at := &AnchoredText{ - displayedText: text, offset: r2.MakeVec2(0, 0), ForegroundColor: theme.ForegroundColor(), referencePosition: fyne.Position{X: 0, Y: 0}, } + at.displayedTextBinding = binding.NewString() + at.displayedTextBinding.Set(text) + at.textEntry = widget.NewEntryWithData(at.displayedTextBinding) + at.textEntry.Wrapping = fyne.TextWrapOff + at.textEntry.Validator = nil + at.textEntry.Enable() at.ExtendBaseWidget(at) return at } @@ -42,10 +48,8 @@ func NewAnchoredText(text string) *AnchoredText { // CreateRenderer is the required method for a widget extension func (at *AnchoredText) CreateRenderer() fyne.WidgetRenderer { atr := &anchoredTextRenderer{ - widget: at, - textObject: canvas.NewText(at.displayedText, color.Black), + widget: at, } - atr.Refresh() return atr @@ -67,12 +71,23 @@ func (at *AnchoredText) Dragged(event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} at.Move(at.Position().Add(delta)) at.Refresh() - at.link.diagramElement.GetDiagram().forceRepaint() + at.link.diagramElement.GetDiagram().ForceRepaint() +} + +// GetDisplayedTextBinding returns the binding for the displayed text +func (at *AnchoredText) GetDisplayedTextBinding() binding.String { + return at.displayedTextBinding +} + +// GetTextEntry returns the entry widget +func (at *AnchoredText) GetTextEntry() *widget.Entry { + return at.textEntry } -// MinSize returns a fixed minimum size for the anchored text. +// MinSize returns the size of the entry widget plus a one-pixel border func (at *AnchoredText) MinSize() fyne.Size { - minSize := fyne.Size{Height: 25, Width: 50} + textEntryMinSize := at.textEntry.MinSize() + minSize := fyne.NewSize(textEntryMinSize.Width+10, textEntryMinSize.Height+10) return minSize } @@ -118,8 +133,7 @@ func (at *AnchoredText) SetReferencePosition(position fyne.Position) { // anchoredTextRenderer type anchoredTextRenderer struct { - widget *AnchoredText - textObject *canvas.Text + widget *AnchoredText } func (atr *anchoredTextRenderer) Destroy() { @@ -127,20 +141,22 @@ func (atr *anchoredTextRenderer) Destroy() { } func (atr *anchoredTextRenderer) Layout(size fyne.Size) { - atr.widget.Resize(atr.textObject.MinSize()) } func (atr *anchoredTextRenderer) MinSize() fyne.Size { - return atr.textObject.MinSize() + return atr.widget.textEntry.MinSize() } func (atr *anchoredTextRenderer) Objects() []fyne.CanvasObject { canvasObjects := []fyne.CanvasObject{ - atr.textObject, + atr.widget.textEntry, } return canvasObjects } func (atr *anchoredTextRenderer) Refresh() { - // atr.widget.textObject.Color = atr.widget.ForegroundColor + atr.widget.Resize(atr.widget.MinSize()) + atr.widget.textEntry.Resize(atr.widget.textEntry.MinSize()) + atr.widget.textEntry.Move(fyne.NewPos(5, 5)) + atr.widget.textEntry.Refresh() } diff --git a/widget/diagramwidget/arrowhead.go b/widget/diagramwidget/arrowhead.go index 39685383..4144f4da 100644 --- a/widget/diagramwidget/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -22,7 +22,7 @@ const ( // to match the angle of the link's line segment with which it is oriented, indicated by the baseAngle. type Arrowhead struct { widget.BaseWidget - link *DiagramLink + link *BaseDiagramLink // baseAngle is used to define direction in which the arrowhead points // Base fyne.Position baseAngle float64 @@ -115,7 +115,7 @@ func (a *Arrowhead) setBaseAngle(angle float64) { } // setLink sets the DiagramLink on which this arrowhead appears -func (a *Arrowhead) setLink(link *DiagramLink) { +func (a *Arrowhead) setLink(link *BaseDiagramLink) { a.link = link } diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index 050f36fb..2e81a36f 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -237,8 +237,8 @@ func (rpr *rectanglePadRenderer) Objects() []fyne.CanvasObject { } func (rpr *rectanglePadRenderer) Refresh() { - rpr.rect.StrokeColor = rpr.rp.padOwner.GetDiagram().GetForegroundColor() + rpr.rect.StrokeColor = rpr.rp.padOwner.GetForegroundColor() rpr.rect.FillColor = color.Transparent rpr.rect.StrokeWidth = padLineWidth - rpr.rp.connectionPad.padOwner.GetDiagram().forceRepaint() + rpr.rp.connectionPad.padOwner.GetDiagram().ForceRepaint() } diff --git a/widget/diagramwidget/decoration.go b/widget/diagramwidget/decoration.go index 7630c473..22139ae1 100644 --- a/widget/diagramwidget/decoration.go +++ b/widget/diagramwidget/decoration.go @@ -24,7 +24,7 @@ const ( // so that it can adjust the position of the next decoration appropriately. type Decoration interface { fyne.Widget - setLink(link *DiagramLink) + setLink(link *BaseDiagramLink) // setBaseAngle sets the angle of the reference axis setBaseAngle(angle float64) // Angle in radians SetFillColor(color color.Color) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 768ae06d..3a119e9a 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -11,11 +11,11 @@ import ( "fyne.io/fyne/v2/widget" ) -// forceRepaint is a workaround for a Fyne bug (Issue #2205) in which moving a canvas object does not +// ForceRepaint is a workaround for a Fyne bug (Issue #2205) in which moving a canvas object does not // trigger repainting. When the issue is resolved, this function and all references to it should be // removed. The DummyBox on the GlobalDiagram should also be removed. // The conditionals here are required during initialization. -func (dw *DiagramWidget) forceRepaint() { +func (dw *DiagramWidget) ForceRepaint() { if dw != nil && dw.dummyBox != nil { dw.dummyBox.Refresh() } @@ -25,7 +25,7 @@ func (dw *DiagramWidget) forceRepaint() { var _ fyne.Tappable = (*DiagramWidget)(nil) type linkPadPair struct { - link *DiagramLink + link *BaseDiagramLink pad ConnectionPad } @@ -47,8 +47,8 @@ type DiagramWidget struct { // DesiredSize specifies the size of the displayed diagram. Defaults to 800 x 600 DesiredSize fyne.Size - Nodes map[string]*DiagramNode - Links map[string]*DiagramLink + Nodes map[string]DiagramNode + Links map[string]DiagramLink selection map[string]DiagramElement diagramElementLinkDependencies map[string][]linkPadPair @@ -65,8 +65,8 @@ func NewDiagramWidget(id string) *DiagramWidget { ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), DesiredSize: fyne.Size{Width: 800, Height: 600}, Offset: fyne.Position{X: 0, Y: 0}, - Nodes: map[string]*DiagramNode{}, - Links: map[string]*DiagramLink{}, + Nodes: map[string]DiagramNode{}, + Links: map[string]DiagramLink{}, dummyBox: canvas.NewRectangle(color.Transparent), selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, @@ -80,13 +80,13 @@ func NewDiagramWidget(id string) *DiagramWidget { } // addLink adds a link to the diagram -func (dw *DiagramWidget) addLink(link *DiagramLink) { - dw.Links[link.id] = link +func (dw *DiagramWidget) addLink(link DiagramLink) { + dw.Links[link.GetDiagramElementID()] = link link.Refresh() // TODO add logic to rezise diagram if necessary } -func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link *DiagramLink, pad ConnectionPad) { +func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link *BaseDiagramLink, pad ConnectionPad) { deID := diagramElement.GetDiagramElementID() currentDependencies := dw.diagramElementLinkDependencies[deID] if currentDependencies == nil { @@ -103,8 +103,8 @@ func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link * } // addNode adds a node to the diagram -func (dw *DiagramWidget) addNode(node *DiagramNode) { - dw.Nodes[node.id] = node +func (dw *DiagramWidget) addNode(node DiagramNode) { + dw.Nodes[node.GetDiagramElementID()] = node node.Refresh() // TODO add logic to rezise diagram if necessary } @@ -134,7 +134,7 @@ func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.Poi if !dw.IsSelected(de) { dw.addElementToSelection(de) } - dw.forceRepaint() + dw.ForceRepaint() } // DragEnd is called when the drag comes to an end. It refreshes the widget @@ -153,7 +153,7 @@ func (dw *DiagramWidget) GetBackgroundColor() color.Color { func (dw *DiagramWidget) GetDiagramElement(elementID string) DiagramElement { var de DiagramElement de = dw.Nodes[elementID] - if reflect.ValueOf(de).IsNil() { + if de == nil || reflect.ValueOf(de).IsNil() { de = dw.Links[elementID] } return de @@ -173,18 +173,18 @@ func (dw *DiagramWidget) GetHoverColor() color.Color { // DiagramNodeDragged moves the indicated node and refreshes any links that may be attached // to it -func (dw *DiagramWidget) DiagramNodeDragged(node *DiagramNode, event *fyne.DragEvent) { +func (dw *DiagramWidget) DiagramNodeDragged(node *BaseDiagramNode, event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} dw.DisplaceNode(node, delta) - dw.forceRepaint() + dw.ForceRepaint() } // DisplaceNode moves the indicated node and refreshes any links that may be attached // to it -func (dw *DiagramWidget) DisplaceNode(node *DiagramNode, delta fyne.Position) { +func (dw *DiagramWidget) DisplaceNode(node DiagramNode, delta fyne.Position) { node.Move(node.Position().Add(delta)) dw.refreshDependentLinks(node) - dw.forceRepaint() + dw.ForceRepaint() } // Dragged responds to a drag movement in the background of the diagram. It moves all nodes @@ -238,7 +238,7 @@ func (dw *DiagramWidget) removeElementFromSelection(de DiagramElement) { de.HideHandles() } -func (dw *DiagramWidget) removeLinkDependency(diagramElement DiagramElement, link *DiagramLink, pad ConnectionPad) { +func (dw *DiagramWidget) removeLinkDependency(diagramElement DiagramElement, link *BaseDiagramLink, pad ConnectionPad) { deID := diagramElement.GetDiagramElementID() currentDependencies := dw.diagramElementLinkDependencies[deID] if currentDependencies == nil { @@ -269,9 +269,9 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { } delete(dw.diagramElementLinkDependencies, elementID) switch element.(type) { - case *DiagramNode: + case *BaseDiagramNode: delete(dw.Nodes, elementID) - case *DiagramLink: + case *BaseDiagramLink: delete(dw.Links, elementID) dw.removeDependenciesInvolvingLink(elementID) } @@ -284,7 +284,7 @@ func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { for _, de := range dw.selection { dw.removeElementFromSelection(de) } - dw.forceRepaint() + dw.ForceRepaint() } // diagramWidgetRenderer diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index f6dafe02..832aab3a 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -1,28 +1,51 @@ package diagramwidget -import "fyne.io/fyne/v2" +import ( + "image/color" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) // A DiagramElement is a widget that can be placed directly in a diagram. The most common // elements are Node and Link widgets. type DiagramElement interface { fyne.Widget + // GetBackgroundColor returns the background color for the widget + GetBackgroundColor() color.Color + // GetForegroundColor returns the foreground color for the widget + GetForegroundColor() color.Color // GetDefaultConnectionPad returns the default pad for the DiagramElement GetDefaultConnectionPad() ConnectionPad // GetDiagram returns the DiagramWidget to which the DiagramElement belongs GetDiagram() *DiagramWidget // GetDiagramElementID returns the string identifier provided at the time the DiagramElement was created GetDiagramElementID() string + // GetHandle returns the handle with the indicated index name + GetHandle(string) *Handle + // GetHandleColor returns the color for the element's handles + GetHandleColor() color.Color + // handleDragged responds to drag events handleDragged(handle *Handle, event *fyne.DragEvent) // HideHandles hides the handles on the DiagramElement HideHandles() // ShowHandles shows the handles on the DiagramElement ShowHandles() + // SetForegroundColor sets the foreground color for the widget + SetForegroundColor(color.Color) + // SetBackgroundColor sets the background color for the widget + SetBackgroundColor(color.Color) } type diagramElement struct { - diagram *DiagramWidget - id string - handles map[string]*Handle + widget.BaseWidget + diagram *DiagramWidget + foregroundColor color.Color + backgroundColor color.Color + handleColor color.Color + id string + handles map[string]*Handle } func (de *diagramElement) GetDiagram() *DiagramWidget { @@ -33,6 +56,22 @@ func (de *diagramElement) GetDiagramElementID() string { return de.id } +func (de *diagramElement) GetBackgroundColor() color.Color { + return de.backgroundColor +} + +func (de *diagramElement) GetForegroundColor() color.Color { + return de.foregroundColor +} + +func (de *diagramElement) GetHandle(handleName string) *Handle { + return de.handles[handleName] +} + +func (de *diagramElement) GetHandleColor() color.Color { + return de.handleColor +} + func (de *diagramElement) HideHandles() { for _, handle := range de.handles { handle.Hide() @@ -43,6 +82,22 @@ func (de *diagramElement) initialize(diagram *DiagramWidget, id string) { de.diagram = diagram de.id = id de.handles = make(map[string]*Handle) + de.handleColor = de.diagram.DiagramTheme.Color(theme.ColorNameForeground, de.diagram.ThemeVariant) +} + +func (de *diagramElement) SetBackgroundColor(backgroundColor color.Color) { + de.backgroundColor = backgroundColor + de.Refresh() +} + +func (de *diagramElement) SetForegroundColor(foregroundColor color.Color) { + de.foregroundColor = foregroundColor + de.Refresh() +} + +func (de *diagramElement) SetHandleColor(handleColor color.Color) { + de.handleColor = handleColor + de.Refresh() } func (de *diagramElement) ShowHandles() { diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go index c2b8388f..82dd0749 100644 --- a/widget/diagramwidget/handle.go +++ b/widget/diagramwidget/handle.go @@ -37,7 +37,7 @@ func (h *Handle) CreateRenderer() fyne.WidgetRenderer { } hr.rect.FillColor = color.Transparent hr.Refresh() - h.de.GetDiagram().forceRepaint() + h.de.GetDiagram().ForceRepaint() return hr } @@ -103,5 +103,5 @@ func (hr *handleRenderer) Refresh() { hr.rect.StrokeColor = hr.handle.getStrokeColor() hr.rect.FillColor = color.Transparent hr.rect.StrokeWidth = hr.handle.getStrokeWidth() - hr.handle.de.GetDiagram().forceRepaint() + hr.handle.de.GetDiagram().ForceRepaint() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 127762ae..986046e5 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -8,14 +8,20 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/driver/desktop" - "fyne.io/fyne/v2/widget" ) // Validate Hoverable Implementation -var _ desktop.Hoverable = (*DiagramLink)(nil) -var _ DiagramElement = (*DiagramLink)(nil) +var _ desktop.Hoverable = (*BaseDiagramLink)(nil) +var _ DiagramElement = (*BaseDiagramLink)(nil) + +type DiagramLink interface { + DiagramElement + getBaseDiagramLink() *BaseDiagramLink + GetSourcePad() ConnectionPad + GetTargetPad() ConnectionPad +} -// DiagramLink is a directed graphic connection between two DiagramElements that are referred to as the Source +// BaseDiagramLink is a directed graphic connection between two DiagramElements that are referred to as the Source // and Target. The link consists of one or more line segments. By default a single line segment connects the // Source and Target. The Link connects to ConnectionPads on the DiagramElements. // There are three key points on a Link: the Source connection point, the Target connection point, and a MidPoint. @@ -31,8 +37,7 @@ var _ DiagramElement = (*DiagramLink)(nil) // can be used to retrieve the AnchoredText widget so that the displayed text value (among other things) can be set programatically. // By default, there is a single ConnectionPad (a PointPad) associated with a Link and located at the MidPoint. Thus a // Link can connect to another Link using this ConnectionPad. -type DiagramLink struct { - widget.BaseWidget +type BaseDiagramLink struct { diagramElement linkPoints []*LinkPoint linkSegments []*LinkSegment @@ -55,38 +60,43 @@ type DiagramLink struct { // It can be used to retrieve the DiagramLink from the Diagram. The ID is intended to be used to facilitate mapping the // DiagramLink to the information it represents in the application. The DiagramLink uses the DiagramWidget's ForegroundColor // as the default color for the line segments. -func NewDiagramLink(diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, linkID string) *DiagramLink { - dl := &DiagramLink{ - linkPoints: []*LinkPoint{}, - linkSegments: []*LinkSegment{}, - LinkColor: diagram.GetForegroundColor(), - strokeWidth: 2, - sourcePad: sourcePad, - targetPad: targetPad, - sourceAnchoredText: make(map[string]*AnchoredText), - midpointAnchoredText: make(map[string]*AnchoredText), - targetAnchoredText: make(map[string]*AnchoredText), - showHandles: false, - } - dl.diagramElement.initialize(diagram, linkID) - dl.linkPoints = append(dl.linkPoints, NewLinkPoint(dl)) - dl.linkPoints = append(dl.linkPoints, NewLinkPoint(dl)) - dl.linkSegments = append(dl.linkSegments, NewLinkSegment(dl, dl.linkPoints[0].Position(), dl.linkPoints[1].Position())) - dl.midPad = NewPointPad(dl) - dl.midPad.Move(dl.getMidPosition()) - dl.ExtendBaseWidget(dl) +func NewDiagramLink(diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, linkID string) *BaseDiagramLink { + bdl := &BaseDiagramLink{} + InitializeBaseDiagramLink(bdl, diagram, sourcePad, targetPad, linkID) + return bdl +} - dl.diagram.addLink(dl) - dl.diagram.addLinkDependency(dl.sourcePad.GetPadOwner(), dl, dl.sourcePad) - dl.diagram.addLinkDependency(dl.targetPad.GetPadOwner(), dl, dl.targetPad) - dl.Refresh() - return dl +// InitializeBaseDiagramLink initializes the BaseDiagramLink. It must be called by any extensions to BaseDiagramLink +func InitializeBaseDiagramLink(diagramLink DiagramLink, diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, linkID string) { + bdl := diagramLink.getBaseDiagramLink() + bdl.linkPoints = []*LinkPoint{} + bdl.linkSegments = []*LinkSegment{} + bdl.LinkColor = diagram.GetForegroundColor() + bdl.strokeWidth = 2 + bdl.sourcePad = sourcePad + bdl.targetPad = targetPad + bdl.sourceAnchoredText = make(map[string]*AnchoredText) + bdl.midpointAnchoredText = make(map[string]*AnchoredText) + bdl.targetAnchoredText = make(map[string]*AnchoredText) + bdl.showHandles = false + bdl.diagramElement.initialize(diagram, linkID) + bdl.linkPoints = append(bdl.linkPoints, NewLinkPoint(bdl)) + bdl.linkPoints = append(bdl.linkPoints, NewLinkPoint(bdl)) + bdl.linkSegments = append(bdl.linkSegments, NewLinkSegment(bdl, bdl.linkPoints[0].Position(), bdl.linkPoints[1].Position())) + bdl.midPad = NewPointPad(bdl) + bdl.midPad.Move(bdl.getMidPosition()) + bdl.ExtendBaseWidget(diagramLink) + + bdl.diagram.addLink(diagramLink) + bdl.diagram.addLinkDependency(bdl.sourcePad.GetPadOwner(), bdl, bdl.sourcePad) + bdl.diagram.addLinkDependency(bdl.targetPad.GetPadOwner(), bdl, bdl.targetPad) + diagramLink.Refresh() } // CreateRenderer creates the WidgetRenderer for a DiagramLink -func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { +func (bdl *BaseDiagramLink) CreateRenderer() fyne.WidgetRenderer { dlr := diagramLinkRenderer{ - link: dl, + link: bdl, } (&dlr).Refresh() @@ -97,123 +107,151 @@ func (dl *DiagramLink) CreateRenderer() fyne.WidgetRenderer { // AddSourceAnchoredText creates a new AnchoredText widget and adds it to the DiagramLink at the Source // position. It uses the supplied key to index the widget so that it can be retrieved later. // Multiple AnchoredText widgets can be added. -func (dl *DiagramLink) AddSourceAnchoredText(key string, displayedText string) { +func (bdl *BaseDiagramLink) AddSourceAnchoredText(key string, displayedText string) *AnchoredText { at := NewAnchoredText(displayedText) - at.link = dl - dl.sourceAnchoredText[key] = at - at.SetReferencePosition(dl.getSourcePosition()) - at.Move(dl.getSourcePosition()) - dl.Refresh() + at.link = bdl + bdl.sourceAnchoredText[key] = at + at.SetReferencePosition(bdl.getSourcePosition()) + at.Move(bdl.getSourcePosition()) + bdl.Refresh() + return at } // AddSourceDecoration adds the supplied Decoration widget at the Source position. Multiple // calls to this function will stack the decorations along the line segment at the Source position. -func (dl *DiagramLink) AddSourceDecoration(decoration Decoration) { - decoration.setLink(dl) - dl.SourceDecorations = append(dl.SourceDecorations, decoration) - dl.Refresh() +func (bdl *BaseDiagramLink) AddSourceDecoration(decoration Decoration) { + decoration.setLink(bdl) + bdl.SourceDecorations = append(bdl.SourceDecorations, decoration) + bdl.Refresh() } // AddMidpointAnchoredText creates a new AnchoredText widget and adds it to the DiagramLink at the Midpoint // position. It uses the supplied key to index the widget so that it can be retrieved later. // Multiple AnchoredText widgets can be added. -func (dl *DiagramLink) AddMidpointAnchoredText(key string, displayedText string) { +func (bdl *BaseDiagramLink) AddMidpointAnchoredText(key string, displayedText string) *AnchoredText { at := NewAnchoredText(displayedText) - at.link = dl - dl.midpointAnchoredText[key] = at - at.SetReferencePosition(dl.getMidPosition()) - at.Move(dl.getMidPosition()) - dl.Refresh() + at.link = bdl + bdl.midpointAnchoredText[key] = at + at.SetReferencePosition(bdl.getMidPosition()) + at.Move(bdl.getMidPosition()) + bdl.Refresh() + return at } // AddMidpointDecoration adds the supplied Decoration widget at the Midpoint position. Multiple // calls to this function will stack the decorations along the line segment at the Midpoint position. -func (dl *DiagramLink) AddMidpointDecoration(decoration Decoration) { - decoration.setLink(dl) - dl.MidpointDecorations = append(dl.MidpointDecorations, decoration) - dl.Refresh() +func (bdl *BaseDiagramLink) AddMidpointDecoration(decoration Decoration) { + decoration.setLink(bdl) + bdl.MidpointDecorations = append(bdl.MidpointDecorations, decoration) + bdl.Refresh() } // AddTargetAnchoredText creates a new AnchoredText widget and adds it to the DiagramLink at the Target // position. It uses the supplied key to index the widget so that it can be retrieved later. // Multiple AnchoredText widgets can be added. -func (dl *DiagramLink) AddTargetAnchoredText(key string, displayedText string) { +func (bdl *BaseDiagramLink) AddTargetAnchoredText(key string, displayedText string) *AnchoredText { at := NewAnchoredText(displayedText) - at.link = dl - dl.targetAnchoredText[key] = at - at.SetReferencePosition(dl.getTargetPosition()) - at.Move(dl.getTargetPosition()) - dl.Refresh() + at.link = bdl + bdl.targetAnchoredText[key] = at + at.SetReferencePosition(bdl.getTargetPosition()) + at.Move(bdl.getTargetPosition()) + bdl.Refresh() + return at } // AddTargetDecoration adds the supplied Decoration widget at the Target position. Multiple // calls to this function will stack the decorations along the line segment at the Target position. -func (dl *DiagramLink) AddTargetDecoration(decoration Decoration) { - decoration.setLink(dl) - dl.TargetDecorations = append(dl.TargetDecorations, decoration) - dl.Refresh() +func (bdl *BaseDiagramLink) AddTargetDecoration(decoration Decoration) { + decoration.setLink(bdl) + bdl.TargetDecorations = append(bdl.TargetDecorations, decoration) + bdl.Refresh() +} + +// getBaseDiagramLink returns a pointer to the BaseDiagramLink +func (bdl *BaseDiagramLink) getBaseDiagramLink() *BaseDiagramLink { + return bdl } // GetDefaultConnectionPad returns the midPad of the Link -func (dl *DiagramLink) GetDefaultConnectionPad() ConnectionPad { - return dl.GetMidPad() +func (bdl *BaseDiagramLink) GetDefaultConnectionPad() ConnectionPad { + return bdl.GetMidPad() } // GetMidPad returns the PointPad at the midpoint so that it can be used as either the Source or Target // pad for another Link. -func (dl *DiagramLink) GetMidPad() ConnectionPad { - return dl.midPad +func (bdl *BaseDiagramLink) GetMidPad() ConnectionPad { + return bdl.midPad } -func (dl *DiagramLink) getMidPosition() fyne.Position { +func (bdl *BaseDiagramLink) getMidPosition() fyne.Position { // TODO update when additional points are introduced - sourcePoint := dl.linkPoints[0].Position() - targetPoint := dl.linkPoints[len(dl.linkPoints)-1].Position() + sourcePoint := bdl.linkPoints[0].Position() + targetPoint := bdl.linkPoints[len(bdl.linkPoints)-1].Position() midPoint := fyne.NewPos((sourcePoint.X+targetPoint.X)/2, (sourcePoint.Y+targetPoint.Y)/2) return midPoint } -func (dl *DiagramLink) getSourcePosition() fyne.Position { - return dl.linkPoints[0].Position() +func (bdl *BaseDiagramLink) GetMidpointAnchoredText(key string) *AnchoredText { + return bdl.midpointAnchoredText[key] +} + +func (bdl *BaseDiagramLink) GetSourceAnchoredText(key string) *AnchoredText { + return bdl.sourceAnchoredText[key] +} + +func (bdl *BaseDiagramLink) GetTargetAnchoredText(key string) *AnchoredText { + return bdl.targetAnchoredText[key] +} + +func (bdl *BaseDiagramLink) GetSourcePad() ConnectionPad { + return bdl.sourcePad +} + +func (bdl *BaseDiagramLink) getSourcePosition() fyne.Position { + return bdl.linkPoints[0].Position() +} + +func (bdl *BaseDiagramLink) GetTargetPad() ConnectionPad { + return bdl.targetPad } -func (dl *DiagramLink) getTargetPosition() fyne.Position { - return dl.linkPoints[len(dl.linkPoints)-1].Position() +func (bdl *BaseDiagramLink) getTargetPosition() fyne.Position { + return bdl.linkPoints[len(bdl.linkPoints)-1].Position() } -func (dl *DiagramLink) handleDragged(handle *Handle, event *fyne.DragEvent) { +func (bdl *BaseDiagramLink) handleDragged(handle *Handle, event *fyne.DragEvent) { // TODO implement this } // HideHandles prevents the handles from being displayed -func (dl *DiagramLink) HideHandles() { - dl.showHandles = false - dl.Refresh() +func (bdl *BaseDiagramLink) HideHandles() { + bdl.showHandles = false + bdl.Refresh() } // MouseIn responds to the mouse entering the bounding rectangle of the Link -func (dl *DiagramLink) MouseIn(event *desktop.MouseEvent) { +func (bdl *BaseDiagramLink) MouseIn(event *desktop.MouseEvent) { // TODO implement this } // MouseMoved responds to the mouse moving while within the bounding rectangle of the Link -func (dl *DiagramLink) MouseMoved(event *desktop.MouseEvent) { +func (bdl *BaseDiagramLink) MouseMoved(event *desktop.MouseEvent) { // TODO implement this } // MouseOut responds to the mouse leaving the bounding rectangle of the Link -func (dl *DiagramLink) MouseOut() { +func (bdl *BaseDiagramLink) MouseOut() { } // ShowHandles causes the handles of the Link to be displayed -func (dl *DiagramLink) ShowHandles() { - dl.showHandles = true - dl.Refresh() +func (bdl *BaseDiagramLink) ShowHandles() { + bdl.showHandles = true + bdl.Refresh() } // diagramLinkRenderer type diagramLinkRenderer struct { - link *DiagramLink + link *BaseDiagramLink } func (dlr *diagramLinkRenderer) Destroy() { @@ -274,6 +312,7 @@ func (dlr *diagramLinkRenderer) Objects() []fyne.CanvasObject { } func (dlr *diagramLinkRenderer) Refresh() { + dlr.link.Resize(dlr.MinSize()) padBasedSourceReferencePoint := dlr.link.sourcePad.GetCenter() padBasedTargetReferencePoint := dlr.link.targetPad.GetCenter() padBasedSourcePosition := dlr.link.sourcePad.getConnectionPoint(padBasedTargetReferencePoint) @@ -365,5 +404,5 @@ func (dlr *diagramLinkRenderer) Refresh() { decoration.Refresh() } dlr.link.diagram.refreshDependentLinks(dlr.link) - dlr.link.GetDiagram().forceRepaint() + dlr.link.GetDiagram().ForceRepaint() } diff --git a/widget/diagramwidget/linkpoint.go b/widget/diagramwidget/linkpoint.go index d15bd70e..8ab4b3af 100644 --- a/widget/diagramwidget/linkpoint.go +++ b/widget/diagramwidget/linkpoint.go @@ -10,7 +10,7 @@ type LinkPoint struct { widget.BaseWidget } -func NewLinkPoint(link *DiagramLink) *LinkPoint { +func NewLinkPoint(link *BaseDiagramLink) *LinkPoint { lp := &LinkPoint{} lp.BaseWidget.ExtendBaseWidget(lp) return lp diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index ef2d5f1f..0666aa78 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -10,12 +10,12 @@ import ( type LinkSegment struct { widget.BaseWidget - link *DiagramLink + link *BaseDiagramLink p1 fyne.Position p2 fyne.Position } -func NewLinkSegment(link *DiagramLink, p1 fyne.Position, p2 fyne.Position) *LinkSegment { +func NewLinkSegment(link *BaseDiagramLink, p1 fyne.Position, p2 fyne.Position) *LinkSegment { ls := &LinkSegment{ link: link, p1: p1, @@ -68,5 +68,5 @@ func (lsr *linkSegmentRenderer) Refresh() { lsr.line.Position2 = lsr.ls.p2 lsr.line.StrokeColor = lsr.ls.link.LinkColor lsr.line.StrokeWidth = lsr.ls.link.strokeWidth - lsr.ls.link.GetDiagram().forceRepaint() + lsr.ls.link.GetDiagram().ForceRepaint() } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 4717dc5d..4a989bb8 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -2,6 +2,7 @@ package diagramwidget import ( "image/color" + "log" "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" @@ -9,12 +10,24 @@ import ( "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" ) -// Validate that DiagramNode implements DiagramElement and Tappable -var _ DiagramElement = (*DiagramNode)(nil) -var _ fyne.Tappable = (*DiagramNode)(nil) +type DiagramNode interface { + DiagramElement + getBaseDiagramNode() *BaseDiagramNode + MouseIn(event *desktop.MouseEvent) + MouseOut() + MouseMoved(event *desktop.MouseEvent) + R2Center() r2.Vec2 +} + +// Validate that BaseDiagramNode implements DiagramElement and Tappable +var _ DiagramElement = (*BaseDiagramNode)(nil) +var _ fyne.Tappable = (*BaseDiagramNode)(nil) +var _ desktop.Hoverable = (*BaseDiagramNode)(nil) +var _ desktop.Hoverable = (DiagramNode)(nil) +var _ fyne.Widget = (*BaseDiagramNode)(nil) +var _ fyne.Widget = (DiagramNode)(nil) const ( // default inner size @@ -22,11 +35,10 @@ const ( defaultHeight float32 = 25 ) -// DiagramNode represents a node in the diagram widget. It contains an inner +// BaseDiagramNode represents a node in the diagram widget. It contains an inner // widget, and also draws a border, and a "handle" that can be used to drag it // around. -type DiagramNode struct { - widget.BaseWidget +type BaseDiagramNode struct { diagramElement // InnerSize stores size that the inner object should have, may not // be respected if not large enough for the object. @@ -40,80 +52,83 @@ type DiagramNode struct { // BoxStrokeWidth is the stroke width of the box which delineates the // node. Defaults to 1. BoxStrokeWidth float32 - // BoxFill is the fill color of the node, the inner object will be - // drawn on top of this. Defaults to the DiagramTheme's BackgroundColor. - HandleColor color.Color // HandleStrokeWidth is the stroke width of the node handle, defaults // to 3. HandleStroke float32 edgePad *RectanglePad + // MovedCallback, if present, is invoked when the node is moved + MovedCallback func() } // NewDiagramNode creates a DiagramNode widget and adds it to the DiagramWidget. The user-supplied // 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) *DiagramNode { - dn := &DiagramNode{ - InnerSize: fyne.Size{Width: defaultWidth, Height: defaultHeight}, - innerObject: obj, - Padding: diagram.DiagramTheme.Size(theme.SizeNamePadding), - BoxStrokeWidth: 1, - HandleColor: diagram.DiagramTheme.Color(theme.ColorNameForeground, diagram.ThemeVariant), - HandleStroke: 3, - } - dn.diagramElement.initialize(diagram, nodeID) - dn.edgePad = NewRectanglePad(dn) - dn.edgePad.Hide() +func NewDiagramNode(diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string) *BaseDiagramNode { + bdn := &BaseDiagramNode{} + InitializeBaseDiagramNode(bdn, diagram, obj, nodeID) + return bdn +} + +// InitializeBaseDiagramNode is used to initailize the BaseDiagramNode. It must be called by any extensions to the BaseDiagramNode +func InitializeBaseDiagramNode(diagramNode DiagramNode, diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string) { + bdn := diagramNode.getBaseDiagramNode() + bdn.InnerSize = fyne.Size{Width: defaultWidth, Height: defaultHeight} + bdn.innerObject = obj + bdn.Padding = diagram.DiagramTheme.Size(theme.SizeNamePadding) + bdn.BoxStrokeWidth = 1 + bdn.HandleStroke = 3 + bdn.diagramElement.initialize(diagram, nodeID) + bdn.edgePad = NewRectanglePad(bdn) + bdn.edgePad.Hide() for _, handleKey := range []string{"upperLeft", "upperMiddle", "upperRight", "leftMiddle", "rightMiddle", "lowerLeft", "lowerMiddle", "lowerRight"} { - newHandle := NewHandle(dn) - dn.handles[handleKey] = newHandle + newHandle := NewHandle(bdn) + bdn.handles[handleKey] = newHandle newHandle.Hide() } - dn.ExtendBaseWidget(dn) - dn.diagram.addNode(dn) - dn.Refresh() - return dn + bdn.ExtendBaseWidget(diagramNode) + bdn.diagram.addNode(diagramNode) + diagramNode.Refresh() } -func (dn *DiagramNode) CreateRenderer() fyne.WidgetRenderer { +func (bdn *BaseDiagramNode) CreateRenderer() fyne.WidgetRenderer { dnr := diagramNodeRenderer{ - node: dn, - box: canvas.NewRectangle(dn.diagram.GetForegroundColor()), + node: bdn, + box: canvas.NewRectangle(bdn.diagram.GetForegroundColor()), } - dnr.box.StrokeWidth = dn.BoxStrokeWidth - dnr.box.FillColor = dn.diagram.GetBackgroundColor() + dnr.box.StrokeWidth = bdn.BoxStrokeWidth + dnr.box.FillColor = bdn.diagram.GetBackgroundColor() (&dnr).Refresh() return &dnr } -func (dn *DiagramNode) Center() fyne.Position { - return fyne.Position{X: float32(dn.R2Center().X), Y: float32(dn.R2Center().Y)} +func (bdn *BaseDiagramNode) Center() fyne.Position { + return fyne.Position{X: float32(bdn.R2Center().X), Y: float32(bdn.R2Center().Y)} } -func (dn *DiagramNode) Cursor() desktop.Cursor { +func (bdn *BaseDiagramNode) Cursor() desktop.Cursor { return desktop.DefaultCursor } -func (dn *DiagramNode) DragEnd() { +func (bdn *BaseDiagramNode) DragEnd() { } -func (dn *DiagramNode) Dragged(event *fyne.DragEvent) { - dn.diagram.DiagramNodeDragged(dn, event) +func (bdn *BaseDiagramNode) Dragged(event *fyne.DragEvent) { + bdn.diagram.DiagramNodeDragged(bdn, event) } -func (dn *DiagramNode) effectiveInnerSize() fyne.Size { - if dn.innerObject == nil { - return dn.InnerSize +func (bdn *BaseDiagramNode) effectiveInnerSize() fyne.Size { + if bdn.innerObject == nil { + return bdn.InnerSize } - return dn.InnerSize.Max(dn.innerObject.MinSize()) + return bdn.InnerSize.Max(bdn.innerObject.MinSize()) } -func (dn *DiagramNode) findKeyForHandle(handle *Handle) string { - for k, v := range dn.handles { +func (bdn *BaseDiagramNode) findKeyForHandle(handle *Handle) string { + for k, v := range bdn.handles { if v == handle { return k } @@ -121,27 +136,32 @@ func (dn *DiagramNode) findKeyForHandle(handle *Handle) string { return "" } +func (bdn *BaseDiagramNode) getBaseDiagramNode() *BaseDiagramNode { + return bdn +} + // GetDefaultConnectionPad returns the edge pad for the node -func (dn *DiagramNode) GetDefaultConnectionPad() ConnectionPad { - return dn.GetEdgePad() +func (bdn *BaseDiagramNode) GetDefaultConnectionPad() ConnectionPad { + return bdn.GetEdgePad() } // GetEdgePad returns the edge pad for the node -func (dn *DiagramNode) GetEdgePad() ConnectionPad { - return dn.edgePad +func (bdn *BaseDiagramNode) GetEdgePad() ConnectionPad { + return bdn.edgePad } -func (dn *DiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) { +func (bdn *BaseDiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) { // determine which handle it is - handleKey := dn.findKeyForHandle(handle) + currentInnerSize := bdn.effectiveInnerSize() + handleKey := bdn.findKeyForHandle(handle) positionChange := fyne.Position{X: 0, Y: 0} sizeChange := fyne.Size{Height: 0, Width: 0} switch handleKey { case "upperLeft": positionChange.X = event.Dragged.DX + sizeChange.Width = -event.Dragged.DX positionChange.Y = event.Dragged.DY sizeChange.Height = -event.Dragged.DY - sizeChange.Width = -event.Dragged.DY case "upperMiddle": positionChange.Y = event.Dragged.DY sizeChange.Height = -event.Dragged.DY @@ -156,78 +176,98 @@ func (dn *DiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) { sizeChange.Width = event.Dragged.DX case "lowerLeft": positionChange.X = event.Dragged.DX - sizeChange.Height = event.Dragged.DY sizeChange.Width = -event.Dragged.DX + sizeChange.Height = event.Dragged.DY case "lowerMiddle": sizeChange.Height = event.Dragged.DY case "lowerRight": sizeChange.Height = event.Dragged.DY sizeChange.Width = event.Dragged.DX } - dn.Move(dn.Position().Add(positionChange)) - trialInnerSize := dn.InnerSize.Add(sizeChange) - dn.InnerSize = dn.innerObject.MinSize().Max(trialInnerSize) - dn.Resize(dn.Size().Add(sizeChange)) - dn.Refresh() - dn.GetDiagram().forceRepaint() + trialInnerSize := bdn.InnerSize.Add(sizeChange) + bdn.InnerSize = bdn.innerObject.MinSize().Max(trialInnerSize) + if trialInnerSize.Height < bdn.InnerSize.Height { + sizeChange.Height = bdn.InnerSize.Height - currentInnerSize.Height + if positionChange.Y != 0 { + positionChange.Y = -sizeChange.Height + } + } + if trialInnerSize.Width < bdn.InnerSize.Width { + sizeChange.Width = bdn.InnerSize.Width - currentInnerSize.Width + if positionChange.X != 0 { + positionChange.X = -sizeChange.Width + } + } + bdn.Resize(bdn.Size().Add(sizeChange)) + bdn.Move(bdn.Position().Add(positionChange)) + bdn.Refresh() + bdn.GetDiagram().ForceRepaint() } -func (dn *DiagramNode) innerPos() fyne.Position { +func (bdn *BaseDiagramNode) innerPos() fyne.Position { return fyne.Position{ - X: float32(dn.Padding), - Y: float32(dn.Padding), + X: float32(bdn.Padding), + Y: float32(bdn.Padding), } } -func (dn *DiagramNode) MouseIn(event *desktop.MouseEvent) { - dn.HandleColor = dn.diagram.DiagramTheme.Color(theme.ColorNameFocus, dn.diagram.ThemeVariant) - dn.GetDiagram().forceRepaint() +func (bdn *BaseDiagramNode) MouseIn(event *desktop.MouseEvent) { + log.Print("Node MouseIn") + bdn.handleColor = bdn.diagram.DiagramTheme.Color(theme.ColorNameFocus, bdn.diagram.ThemeVariant) + bdn.Refresh() + bdn.GetDiagram().ForceRepaint() } -func (dn *DiagramNode) MouseOut() { - dn.HandleColor = dn.diagram.DiagramTheme.Color(theme.ColorNameForeground, dn.diagram.ThemeVariant) - dn.GetDiagram().forceRepaint() +func (bdn *BaseDiagramNode) MouseOut() { + log.Print("Node MouseIn") + bdn.handleColor = bdn.foregroundColor + bdn.Refresh() + bdn.GetDiagram().ForceRepaint() } -func (dn *DiagramNode) MouseMoved(event *desktop.MouseEvent) { +func (bdn *BaseDiagramNode) MouseMoved(event *desktop.MouseEvent) { } -func (dn *DiagramNode) Move(position fyne.Position) { - dn.BaseWidget.Move(position) - dn.Refresh() +func (bdn *BaseDiagramNode) Move(position fyne.Position) { + bdn.BaseWidget.Move(position) + if bdn.MovedCallback != nil { + bdn.MovedCallback() + } + bdn.Refresh() + bdn.diagram.ForceRepaint() } -func (dn *DiagramNode) R2Box() r2.Box { - inner := dn.effectiveInnerSize() +func (bdn *BaseDiagramNode) R2Box() r2.Box { + inner := bdn.effectiveInnerSize() s := r2.V2( - float64(inner.Width+float32(2*dn.Padding)), - float64(inner.Height+float32(2*dn.Padding)), + float64(inner.Width+float32(2*bdn.Padding)), + float64(inner.Height+float32(2*bdn.Padding)), ) - return r2.MakeBox(dn.R2Position(), s) + return r2.MakeBox(bdn.R2Position(), s) } -func (dn *DiagramNode) R2Center() r2.Vec2 { - return dn.R2Box().Center() +func (bdn *BaseDiagramNode) R2Center() r2.Vec2 { + return bdn.R2Box().Center() } -func (dn *DiagramNode) R2Position() r2.Vec2 { - return r2.V2(float64(dn.Position().X), float64(dn.Position().Y)) +func (bdn *BaseDiagramNode) R2Position() r2.Vec2 { + return r2.V2(float64(bdn.Position().X), float64(bdn.Position().Y)) } -func (dn *DiagramNode) SetInnerObject(obj fyne.CanvasObject) { - dn.innerObject = obj - dn.Refresh() - dn.diagram.refreshDependentLinks(dn) +func (bdn *BaseDiagramNode) SetInnerObject(obj fyne.CanvasObject) { + bdn.innerObject = obj + bdn.Refresh() + bdn.diagram.refreshDependentLinks(bdn) } -func (dn *DiagramNode) Tapped(event *fyne.PointEvent) { - dn.diagram.DiagramElementTapped(dn, event) +func (bdn *BaseDiagramNode) Tapped(event *fyne.PointEvent) { + bdn.diagram.DiagramElementTapped(bdn, event) } // diagramNodeRenderer type diagramNodeRenderer struct { - node *DiagramNode + node *BaseDiagramNode box *canvas.Rectangle } @@ -269,6 +309,7 @@ func (dnr *diagramNodeRenderer) Refresh() { dnr.node.Resize(nodeSize) dnr.node.edgePad.Resize(nodeSize) dnr.node.edgePad.Move(fyne.NewPos(0, 0)) + dnr.node.edgePad.Refresh() if dnr.node.innerObject != nil { dnr.node.innerObject.Move(dnr.node.innerPos()) @@ -299,12 +340,15 @@ func (dnr *diagramNodeRenderer) Refresh() { case "lowerRight": handle.Move(fyne.Position{X: width, Y: height}) } + handle.Resize(fyne.NewSize(handle.handleSize, handle.handleSize)) + handle.Refresh() } dnr.box.StrokeWidth = dnr.node.BoxStrokeWidth dnr.box.FillColor = color.Transparent dnr.box.StrokeColor = dnr.node.diagram.GetForegroundColor() + dnr.node.edgePad.Refresh() dnr.node.diagram.refreshDependentLinks(dnr.node) - dnr.node.GetDiagram().forceRepaint() + dnr.node.GetDiagram().ForceRepaint() } diff --git a/widget/diagramwidget/polygon.go b/widget/diagramwidget/polygon.go index 1299f4b9..07d37dc1 100644 --- a/widget/diagramwidget/polygon.go +++ b/widget/diagramwidget/polygon.go @@ -29,7 +29,7 @@ var _ Decoration = (*Polygon)(nil) // By default, stroke color and fill color are color.Black type Polygon struct { widget.BaseWidget - link *DiagramLink + link *BaseDiagramLink // baseAngle is used to define the rotation of the polygon from the nominal position // Base fyne.Position baseAngle float64 @@ -181,7 +181,7 @@ func (p *Polygon) SetFillColor(fillColor color.Color) { } // setLink sets the Link with which the polygon is associated -func (p *Polygon) setLink(link *DiagramLink) { +func (p *Polygon) setLink(link *BaseDiagramLink) { p.link = link } diff --git a/widget/diagramwidget/springforcelayout.go b/widget/diagramwidget/springforcelayout.go index bba709ff..63b81921 100644 --- a/widget/diagramwidget/springforcelayout.go +++ b/widget/diagramwidget/springforcelayout.go @@ -9,10 +9,10 @@ import ( ) // adjacent returns true if there is at least one edge between n1 and n2 -func adjacent(dw *DiagramWidget, n1, n2 *DiagramNode) bool { +func adjacent(dw *DiagramWidget, n1, n2 DiagramNode) bool { // TODO: expensive, may be worth caching? for _, e := range dw.Links { - if ((e.sourcePad.GetPadOwner() == n1) && (e.targetPad.GetPadOwner() == n2)) || ((e.sourcePad.GetPadOwner() == n2) && (e.targetPad.GetPadOwner() == n1)) { + if ((e.GetSourcePad().GetPadOwner() == n1) && (e.GetTargetPad().GetPadOwner() == n2)) || ((e.GetSourcePad().GetPadOwner() == n2) && (e.GetTargetPad().GetPadOwner() == n1)) { return true } } @@ -20,14 +20,14 @@ func adjacent(dw *DiagramWidget, n1, n2 *DiagramNode) bool { return false } -func calculateDistance(n1, n2 *DiagramNode) float64 { +func calculateDistance(n1, n2 DiagramNode) float64 { return r2.MakeLineFromEndpoints(n1.R2Center(), n2.R2Center()).Length() } // calculateForce calculates the force between the given pair of nodes. // // The force is calculated at n1. -func calculateForce(dw *DiagramWidget, n1, n2 *DiagramNode, targetLength float64) r2.Vec2 { +func calculateForce(dw *DiagramWidget, n1, n2 DiagramNode, targetLength float64) r2.Vec2 { // spring constant for linear spring k := float64(0.01) d := calculateDistance(n1, n2) From b2f00fca83f054fb4191acd71719fdf73942242c Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 23 May 2023 10:45:03 -0400 Subject: [PATCH 24/53] Link end movement implemented --- go.mod | 1 + go.sum | 100 +++++++++++- widget/diagramwidget/connectionpad.go | 85 ++++++---- widget/diagramwidget/diagram.go | 38 +++++ widget/diagramwidget/diagramElement.go | 10 ++ widget/diagramwidget/diagram_test.go | 6 +- widget/diagramwidget/handle.go | 25 +-- widget/diagramwidget/link.go | 216 +++++++++++++++++++++---- widget/diagramwidget/linkpoint.go | 12 +- widget/diagramwidget/linksegment.go | 39 ++++- widget/diagramwidget/node.go | 76 +++++---- 11 files changed, 492 insertions(+), 116 deletions(-) diff --git a/go.mod b/go.mod index 794385c8..1636373f 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 github.com/stretchr/testify v1.8.2 + github.com/twpayne/go-geom v1.5.2 github.com/wagslane/go-password-validator v0.3.0 golang.org/x/image v0.0.0-20220601225756-64ec528b34cd ) diff --git a/go.sum b/go.sum index 7f8d7400..58738f9a 100644 --- a/go.sum +++ b/go.sum @@ -43,9 +43,17 @@ fyne.io/systray v1.10.1-0.20230312215936-7f71b037e260 h1:hNHShALSK9F0n6iJoBNmXs1 fyne.io/systray v1.10.1-0.20230312215936-7f71b037e260/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/Andrew-M-C/go.jsonvalue v1.3.3 h1:zPTwpjYwNmqHMyIWyES++xm4k5k1bkdtZoU620pxjEc= github.com/Andrew-M-C/go.jsonvalue v1.3.3/go.mod h1:IwnJ6++SYu/lYAvrJCGGCd9WaT0zTcsD6Li1X9IHcSU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -58,21 +66,35 @@ github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQj github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4= github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -83,6 +105,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM= github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -105,9 +128,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f h1:cWE//ddvZ7bZAYGtNi3+SPGvUFTeTRUL/TQ9LUnQOP0= github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -172,6 +197,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -205,8 +232,12 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= @@ -221,9 +252,12 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -231,6 +265,7 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -238,18 +273,32 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -261,13 +310,17 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= @@ -278,6 +331,7 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA= @@ -295,14 +349,31 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= +github.com/twpayne/go-geom v1.0.0/go.mod h1:RWsl+e3XSahOul/KH2BHCfF0QxSL4RMnMlFw/TNmET0= +github.com/twpayne/go-geom v1.5.2 h1:LyRfBX2W0LM7XN/bGqX0XxrJ7SZc3XwmxU4aj4kSoxw= +github.com/twpayne/go-geom v1.5.2/go.mod h1:3z6O2sAnGtGCXx4Q+5nPOLCA5e8WI2t3cthdb1P2HH8= +github.com/twpayne/go-gpx v1.2.0/go.mod h1:70xTQn0dGph3dgKIPxfl0K3XMVNpulC70/e383iHouA= +github.com/twpayne/go-kml v1.0.0/go.mod h1:LlvLIQSfMqYk2O7Nx8vYAbSLv4K9rjMvLlEdUKWdjq0= +github.com/twpayne/go-kml/v2 v2.0.0/go.mod h1:Y04zvGFNLZQwrWJS8pL5WvNHBibLHYlSN5EjrVUBEqE= +github.com/twpayne/go-polyline v1.0.0/go.mod h1:ICh24bcLYBX8CknfvNPKqoTbe+eg+MX1NPyJmSBo7pU= +github.com/twpayne/go-waypoint v0.0.0-20200706203930-b263a7f6e4e8/go.mod h1:qj5pHncxKhu9gxtZEYWypA/z097sxhFlbTyOyt9gcnU= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -330,6 +401,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -378,6 +450,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180824152047-4bcd98cce591/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -410,6 +483,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -417,8 +491,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -453,12 +528,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -474,12 +551,14 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -489,14 +568,22 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -506,8 +593,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -523,6 +611,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -671,6 +760,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= @@ -680,11 +770,15 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700= honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index 2e81a36f..7c7a1b10 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -21,12 +21,14 @@ type ConnectionPad interface { fyne.Widget desktop.Hoverable GetPadOwner() DiagramElement - GetCenter() fyne.Position - getConnectionPoint(referencePoint fyne.Position) fyne.Position + GetCenterInDiagramCoordinates() fyne.Position + getConnectionPointInDiagramCoordinates(referencePoint fyne.Position) fyne.Position } type connectionPad struct { - padOwner DiagramElement + padOwner DiagramElement + lineWidth float32 + padColor color.Color } func (cp *connectionPad) GetPadOwner() DiagramElement { @@ -52,6 +54,8 @@ func NewPointPad(padOwner DiagramElement) *PointPad { pp := &PointPad{} pp.connectionPad.padOwner = padOwner pp.BaseWidget.ExtendBaseWidget(pp) + pp.lineWidth = padLineWidth + pp.padColor = color.Transparent return pp } @@ -59,38 +63,51 @@ func NewPointPad(padOwner DiagramElement) *PointPad { func (pp *PointPad) CreateRenderer() fyne.WidgetRenderer { ppr := &pointPadRenderer{ pp: pp, - l1: canvas.NewLine(pp.padOwner.GetDiagram().GetHoverColor()), - l2: canvas.NewLine(pp.padOwner.GetDiagram().GetHoverColor()), + l1: canvas.NewLine(pp.padColor), + l2: canvas.NewLine(pp.padColor), } ppr.l1.StrokeWidth = padLineWidth ppr.l2.StrokeWidth = padLineWidth return ppr } -// GetCenter returns the position in diagram coordinates -func (pp *PointPad) GetCenter() fyne.Position { - return pp.padOwner.Position().Add(pp.Position()) +// GetCenterInDiagramCoordinates returns the position in diagram coordinates +func (pp *PointPad) GetCenterInDiagramCoordinates() fyne.Position { + return pp.padOwner.Position().Add(pp.Position().Add(fyne.NewPos(pointPadSize/2, pointPadSize/2))) } -// getConnectionPoint returns the point on the pad to which a connection will be made from the referencePoint. +// getConnectionPointInDiagramCoordinates returns the point on the pad to which a connection will be made from the referencePoint. // For a point pad, this is always the center. -func (pp *PointPad) getConnectionPoint(referencePoint fyne.Position) fyne.Position { - return pp.GetCenter() +func (pp *PointPad) getConnectionPointInDiagramCoordinates(referencePoint fyne.Position) fyne.Position { + return pp.GetCenterInDiagramCoordinates() } // MouseIn responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseIn(event *desktop.MouseEvent) { - // TODO implement this + conTrans := pp.padOwner.GetDiagram().connectionTransaction + if conTrans != nil && conTrans.link.IsConnectionAllowed(conTrans.linkPoint, pp) { + pp.padColor = pp.padOwner.GetDiagram().padColor + conTrans.pendingPad = pp + } else { + pp.padColor = color.Transparent + } + pp.Refresh() + pp.padOwner.GetDiagram().ForceRepaint() } // MouseMoved responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseMoved(event *desktop.MouseEvent) { - // TODO implement this } // MouseOut responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseOut() { - + pp.padColor = color.Transparent + conTrans := pp.padOwner.GetDiagram().connectionTransaction + if conTrans != nil && conTrans.pendingPad == pp { + conTrans.pendingPad = nil + } + pp.Refresh() + pp.padOwner.GetDiagram().ForceRepaint() } // pointPadRenderer @@ -124,9 +141,9 @@ func (ppr *pointPadRenderer) Objects() []fyne.CanvasObject { } func (ppr *pointPadRenderer) Refresh() { - ppr.l1.StrokeColor = ppr.pp.padOwner.GetDiagram().GetHoverColor() + ppr.l1.StrokeColor = ppr.pp.padColor ppr.l1.StrokeWidth = padLineWidth - ppr.l2.StrokeColor = ppr.pp.padOwner.GetDiagram().GetHoverColor() + ppr.l2.StrokeColor = ppr.pp.padColor ppr.l2.StrokeWidth = padLineWidth } @@ -149,6 +166,8 @@ func NewRectanglePad(padOwner DiagramElement) *RectanglePad { rp := &RectanglePad{} rp.connectionPad.padOwner = padOwner rp.BaseWidget.ExtendBaseWidget(rp) + rp.lineWidth = padLineWidth + rp.padColor = color.Transparent return rp } @@ -156,25 +175,24 @@ func NewRectanglePad(padOwner DiagramElement) *RectanglePad { func (rp *RectanglePad) CreateRenderer() fyne.WidgetRenderer { rpr := &rectanglePadRenderer{ rp: rp, - rect: *canvas.NewRectangle(rp.padOwner.GetDiagram().GetForegroundColor()), + rect: *canvas.NewRectangle(rp.padColor), } - // rpr.rect.FillColor = color.Transparent rpr.rect.StrokeWidth = padLineWidth return rpr } -// GetCenter() returns the center of the pad in the diagram's coordinate system -func (rp *RectanglePad) GetCenter() fyne.Position { +// GetCenterInDiagramCoordinates() returns the center of the pad in the diagram's coordinate system +func (rp *RectanglePad) GetCenterInDiagramCoordinates() fyne.Position { box := rp.makeBox() r2Center := box.Center() return fyne.NewPos(float32(r2Center.X), float32(r2Center.Y)) } -// getConnectionPoint returns the point at which the connection should be made from a reference point. +// getConnectionPointInDiagramCoordinates returns the point at which the connection should be made from a reference point. // The reference point is in diagram coordinates and the returned point is also in diagram coordinates. // For a RectanglePad this point is the intersection of a line segment from the reference point to the center // of the rectangle pad and the rectangle bounding the pad. -func (rp *RectanglePad) getConnectionPoint(referencePoint fyne.Position) fyne.Position { +func (rp *RectanglePad) getConnectionPointInDiagramCoordinates(referencePoint fyne.Position) fyne.Position { box := rp.makeBox() r2ReferencePoint := r2.MakeVec2(float64(referencePoint.X), float64(referencePoint.Y)) linkLine := r2.MakeLineFromEndpoints(box.Center(), r2ReferencePoint) @@ -196,17 +214,30 @@ func (rp *RectanglePad) makeBox() r2.Box { // MouseIn responds to the mouse entering the bounds of the RectanglePad func (rp *RectanglePad) MouseIn(event *desktop.MouseEvent) { - // TODO implement this + conTrans := rp.padOwner.GetDiagram().connectionTransaction + if conTrans != nil && conTrans.link.IsConnectionAllowed(conTrans.linkPoint, rp) { + rp.padColor = rp.padOwner.GetDiagram().padColor + conTrans.pendingPad = rp + } else { + rp.padColor = color.Transparent + } + rp.Refresh() + rp.padOwner.GetDiagram().ForceRepaint() } // MouseMoved responds to mouse movements within the rectangle pad func (rp *RectanglePad) MouseMoved(event *desktop.MouseEvent) { - // TODO implement this } // MouseOut responds to mouse movements leaving the rectangle pad func (rp *RectanglePad) MouseOut() { - // TODO implement this + rp.padColor = color.Transparent + conTrans := rp.padOwner.GetDiagram().connectionTransaction + if conTrans != nil && conTrans.pendingPad == rp { + conTrans.pendingPad = nil + } + rp.Refresh() + rp.padOwner.GetDiagram().ForceRepaint() } // rectanglePadRenderer @@ -237,8 +268,8 @@ func (rpr *rectanglePadRenderer) Objects() []fyne.CanvasObject { } func (rpr *rectanglePadRenderer) Refresh() { - rpr.rect.StrokeColor = rpr.rp.padOwner.GetForegroundColor() + rpr.rect.StrokeColor = rpr.rp.padColor rpr.rect.FillColor = color.Transparent - rpr.rect.StrokeWidth = padLineWidth + rpr.rect.StrokeWidth = rpr.rp.lineWidth rpr.rp.connectionPad.padOwner.GetDiagram().ForceRepaint() } diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 3a119e9a..07ef9936 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -24,6 +24,9 @@ func (dw *DiagramWidget) ForceRepaint() { // Verify that interfaces are fully implemented var _ fyne.Tappable = (*DiagramWidget)(nil) +// Default values +var defaultPadColor = color.RGBA{121, 237, 119, 255} + type linkPadPair struct { link *BaseDiagramLink pad ConnectionPad @@ -51,6 +54,8 @@ type DiagramWidget struct { Links map[string]DiagramLink selection map[string]DiagramElement diagramElementLinkDependencies map[string][]linkPadPair + connectionTransaction *connectionTransaction + padColor color.Color // TODO Remove dummyBox when fyne rendering issue is resolved dummyBox *canvas.Rectangle @@ -70,6 +75,7 @@ func NewDiagramWidget(id string) *DiagramWidget { dummyBox: canvas.NewRectangle(color.Transparent), selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, + padColor: defaultPadColor, } dw.dummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) dw.dummyBox.Move(fyne.Position{X: 50, Y: 50}) @@ -197,6 +203,22 @@ func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { dw.Refresh() } +// hideAllPads is a work-around for fyne Issue #3906 in which a child's Hoverable interface +// (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() { + pad.Hide() + } + } +} + // IsSelected returns true if the indicated element is currently part of the selection func (dw *DiagramWidget) IsSelected(de DiagramElement) bool { return dw.selection[de.GetDiagramElementID()] != nil @@ -278,6 +300,22 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { dw.Refresh() } +// showAllPads is a work-around for fyne Issue #3906 in which a child's Hoverable interface +// (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() { + pad.Show() + } + } +} + // Tapped respondss to taps in the diagram background. It removes all diagram elements // from the selection func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index 832aab3a..96d23228 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -14,6 +14,8 @@ type DiagramElement interface { fyne.Widget // GetBackgroundColor returns the background color for the widget GetBackgroundColor() color.Color + // GetConnectionPads() returns all of the connection pads on the element + GetConnectionPads() map[string]ConnectionPad // GetForegroundColor returns the foreground color for the widget GetForegroundColor() color.Color // GetDefaultConnectionPad returns the default pad for the DiagramElement @@ -28,6 +30,8 @@ type DiagramElement interface { GetHandleColor() color.Color // handleDragged responds to drag events handleDragged(handle *Handle, event *fyne.DragEvent) + // handleDragEnd responds to the end of a drag + handleDragEnd(handle *Handle) // HideHandles hides the handles on the DiagramElement HideHandles() // ShowHandles shows the handles on the DiagramElement @@ -46,6 +50,7 @@ type diagramElement struct { handleColor color.Color id string handles map[string]*Handle + pads map[string]ConnectionPad } func (de *diagramElement) GetDiagram() *DiagramWidget { @@ -60,6 +65,10 @@ func (de *diagramElement) GetBackgroundColor() color.Color { return de.backgroundColor } +func (de *diagramElement) GetConnectionPads() map[string]ConnectionPad { + return de.pads +} + func (de *diagramElement) GetForegroundColor() color.Color { return de.foregroundColor } @@ -83,6 +92,7 @@ func (de *diagramElement) initialize(diagram *DiagramWidget, id string) { de.id = id de.handles = make(map[string]*Handle) de.handleColor = de.diagram.DiagramTheme.Color(theme.ColorNameForeground, de.diagram.ThemeVariant) + de.pads = make(map[string]ConnectionPad) } func (de *diagramElement) SetBackgroundColor(backgroundColor color.Color) { diff --git a/widget/diagramwidget/diagram_test.go b/widget/diagramwidget/diagram_test.go index 987b0131..593ad111 100644 --- a/widget/diagramwidget/diagram_test.go +++ b/widget/diagramwidget/diagram_test.go @@ -20,19 +20,19 @@ func TestDependencies(t *testing.T) { node2.Move(fyne.NewPos(200, 100)) assert.Equal(t, 0, len(diagram.diagramElementLinkDependencies)) linkID := "Link1" - link := NewDiagramLink(diagram, node1.edgePad, node2.edgePad, linkID) + link := NewDiagramLink(diagram, node1.pads["default"], node2.pads["default"], linkID) 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.edgePad, node1Dependencies[0].pad) + assert.Equal(t, node1.pads["default"], node1Dependencies[0].pad) node2Dependencies := diagram.diagramElementLinkDependencies[node2ID] assert.Equal(t, 1, len(node2Dependencies)) assert.Equal(t, link, node2Dependencies[0].link) - assert.Equal(t, node2.edgePad, node2Dependencies[0].pad) + assert.Equal(t, node2.pads["default"], node2Dependencies[0].pad) // Now test the dependency management when a node is deleted diagram.RemoveElement(node2ID) diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go index 82dd0749..1a5a18ca 100644 --- a/widget/diagramwidget/handle.go +++ b/widget/diagramwidget/handle.go @@ -5,13 +5,11 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" ) -// Validate implementation of Draggable and Hoverable +// Validate implementation of Draggable var _ fyne.Draggable = (*Handle)(nil) -var _ desktop.Hoverable = (*Handle)(nil) var defaultHandleSize float32 = 10.0 @@ -46,7 +44,7 @@ func (h *Handle) Dragged(event *fyne.DragEvent) { } func (h *Handle) DragEnd() { - + h.de.handleDragEnd(h) } func (h *Handle) getStrokeColor() color.Color { @@ -57,17 +55,6 @@ func (h *Handle) getStrokeWidth() float32 { return 1.0 } -func (h *Handle) MouseIn(*desktop.MouseEvent) { -} - -// MouseMoved is a hook that is called if the mouse pointer moved over the element. -func (h *Handle) MouseMoved(*desktop.MouseEvent) { -} - -// MouseOut is a hook that is called if the mouse pointer leaves the element. -func (h *Handle) MouseOut() { -} - func (h *Handle) Move(position fyne.Position) { delta := fyne.Position{X: -h.handleSize / 2, Y: -h.handleSize / 2} h.BaseWidget.Move(position.Add(delta)) @@ -83,15 +70,15 @@ func (hr *handleRenderer) Destroy() { } -func (hr *handleRenderer) MinSize() fyne.Size { - return fyne.Size{Height: hr.handle.handleSize, Width: hr.handle.handleSize} -} - func (hr *handleRenderer) Layout(size fyne.Size) { hr.rect.Resize(hr.MinSize()) hr.handle.Resize(hr.MinSize()) } +func (hr *handleRenderer) MinSize() fyne.Size { + return fyne.Size{Height: hr.handle.handleSize, Width: hr.handle.handleSize} +} + func (hr *handleRenderer) Objects() []fyne.CanvasObject { obj := []fyne.CanvasObject{ hr.rect, diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 986046e5..8d98b4c6 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -2,6 +2,7 @@ package diagramwidget import ( "image/color" + "log" "math" "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" @@ -10,7 +11,7 @@ import ( "fyne.io/fyne/v2/driver/desktop" ) -// Validate Hoverable Implementation +var _ fyne.Tappable = (*BaseDiagramLink)(nil) var _ desktop.Hoverable = (*BaseDiagramLink)(nil) var _ DiagramElement = (*BaseDiagramLink)(nil) @@ -19,6 +20,7 @@ type DiagramLink interface { getBaseDiagramLink() *BaseDiagramLink GetSourcePad() ConnectionPad GetTargetPad() ConnectionPad + IsConnectionAllowed(*LinkPoint, ConnectionPad) bool } // BaseDiagramLink is a directed graphic connection between two DiagramElements that are referred to as the Source @@ -44,7 +46,6 @@ type BaseDiagramLink struct { LinkColor color.Color strokeWidth float32 sourcePad ConnectionPad - midPad *PointPad targetPad ConnectionPad SourceDecorations []Decoration sourceAnchoredText map[string]*AnchoredText @@ -52,7 +53,6 @@ type BaseDiagramLink struct { targetAnchoredText map[string]*AnchoredText MidpointDecorations []Decoration midpointAnchoredText map[string]*AnchoredText - showHandles bool } // NewDiagramLink creates a DiagramLink widget connecting the two indicated ConnectionPads. It adds itself to the @@ -78,14 +78,19 @@ func InitializeBaseDiagramLink(diagramLink DiagramLink, diagram *DiagramWidget, bdl.sourceAnchoredText = make(map[string]*AnchoredText) bdl.midpointAnchoredText = make(map[string]*AnchoredText) bdl.targetAnchoredText = make(map[string]*AnchoredText) - bdl.showHandles = false bdl.diagramElement.initialize(diagram, linkID) bdl.linkPoints = append(bdl.linkPoints, NewLinkPoint(bdl)) bdl.linkPoints = append(bdl.linkPoints, NewLinkPoint(bdl)) bdl.linkSegments = append(bdl.linkSegments, NewLinkSegment(bdl, bdl.linkPoints[0].Position(), bdl.linkPoints[1].Position())) - bdl.midPad = NewPointPad(bdl) - bdl.midPad.Move(bdl.getMidPosition()) + bdl.pads["default"] = NewPointPad(bdl) + bdl.pads["default"].Move(bdl.getMidPosition()) + bdl.pads["default"].Hide() bdl.ExtendBaseWidget(diagramLink) + for _, handleKey := range []string{"source", "target"} { + newHandle := NewHandle(bdl) + bdl.handles[handleKey] = newHandle + newHandle.Hide() + } bdl.diagram.addLink(diagramLink) bdl.diagram.addLinkDependency(bdl.sourcePad.GetPadOwner(), bdl, bdl.sourcePad) @@ -174,13 +179,23 @@ func (bdl *BaseDiagramLink) getBaseDiagramLink() *BaseDiagramLink { // GetDefaultConnectionPad returns the midPad of the Link func (bdl *BaseDiagramLink) GetDefaultConnectionPad() ConnectionPad { - return bdl.GetMidPad() + return bdl.pads["default"] +} + +// getHandleKey returns the key for the given handle +func (bdl *BaseDiagramLink) getHandleKey(handle *Handle) string { + for key, h := range bdl.handles { + if h == handle { + return key + } + } + return "" } // GetMidPad returns the PointPad at the midpoint so that it can be used as either the Source or Target // pad for another Link. func (bdl *BaseDiagramLink) GetMidPad() ConnectionPad { - return bdl.midPad + return bdl.pads["default"] } func (bdl *BaseDiagramLink) getMidPosition() fyne.Position { @@ -220,17 +235,92 @@ func (bdl *BaseDiagramLink) getTargetPosition() fyne.Position { } func (bdl *BaseDiagramLink) handleDragged(handle *Handle, event *fyne.DragEvent) { - // TODO implement this + handleKey := bdl.getHandleKey(handle) + var linkPoint *LinkPoint + var pad ConnectionPad + switch handleKey { + case "source": + linkPoint = bdl.linkPoints[0] + pad = bdl.sourcePad + bdl.sourcePad = nil + case "target": + linkPoint = bdl.linkPoints[len(bdl.linkPoints)-1] + pad = bdl.targetPad + bdl.targetPad = nil + } + if linkPoint == nil { + return + } + connTrans := bdl.diagram.connectionTransaction + if connTrans == nil { + connTrans = NewConnectionTransaction(linkPoint, bdl, pad, linkPoint.Position()) + bdl.diagram.connectionTransaction = connTrans + // TODO remove this after fyne Issue #3906 has been resolved + bdl.diagram.showAllPads() + + } else if connTrans.linkPoint != linkPoint { + // The existing transaction is for a different linkPoint + return + } + currentPosition := linkPoint.Position() + newPosition := fyne.NewPos(currentPosition.X+event.Dragged.DX, currentPosition.Y+event.Dragged.DY) + linkPoint.Move(newPosition) + bdl.Refresh() } -// HideHandles prevents the handles from being displayed -func (bdl *BaseDiagramLink) HideHandles() { - bdl.showHandles = false - bdl.Refresh() +func (bdl *BaseDiagramLink) handleDragEnd(handle *Handle) { + connTrans := bdl.diagram.connectionTransaction + handleKey := bdl.getHandleKey(handle) + if connTrans != nil { + if connTrans.pendingPad != nil { + // We have a new pad for connection + bdl.diagram.removeLinkDependency(connTrans.initialPad.GetPadOwner(), bdl, connTrans.initialPad) + bdl.diagram.addLinkDependency(connTrans.pendingPad.GetPadOwner(), bdl, connTrans.pendingPad) + bdl.pads[handleKey] = connTrans.pendingPad + switch handleKey { + case "source": + bdl.sourcePad = connTrans.pendingPad + case "target": + bdl.targetPad = connTrans.pendingPad + } + } else { + // We revert to the original pad. + bdl.pads[handleKey] = connTrans.initialPad + switch handleKey { + case "source": + bdl.sourcePad = connTrans.initialPad + case "target": + bdl.targetPad = connTrans.initialPad + } + } + bdl.diagram.connectionTransaction = nil + bdl.diagram.hideAllPads() + bdl.Refresh() + } +} + +func (bdl *BaseDiagramLink) IsConnectionAllowed(linkPoint *LinkPoint, pad ConnectionPad) bool { + pointIndex := -1 + for i, lp := range bdl.linkPoints { + if lp == linkPoint { + pointIndex = i + } + } + if pointIndex == -1 { + // the point doesn't belong to this link + return false + } + if pointIndex != 0 && pointIndex != len(bdl.linkPoints)-1 { + // the point is not the source or target point + return false + } + // By default, we accept any connection. Subclasses can override + return true } // MouseIn responds to the mouse entering the bounding rectangle of the Link func (bdl *BaseDiagramLink) MouseIn(event *desktop.MouseEvent) { + log.Print("Entered Link") // TODO implement this } @@ -241,12 +331,12 @@ func (bdl *BaseDiagramLink) MouseMoved(event *desktop.MouseEvent) { // MouseOut responds to the mouse leaving the bounding rectangle of the Link func (bdl *BaseDiagramLink) MouseOut() { + log.Printf("Left Link") } -// ShowHandles causes the handles of the Link to be displayed -func (bdl *BaseDiagramLink) ShowHandles() { - bdl.showHandles = true - bdl.Refresh() +// Tapped handles tap events +func (bdl *BaseDiagramLink) Tapped(event *fyne.PointEvent) { + log.Print("Link tapped") } // diagramLinkRenderer @@ -307,23 +397,59 @@ func (dlr *diagramLinkRenderer) Objects() []fyne.CanvasObject { for _, targetAnchoredText := range dlr.link.targetAnchoredText { obj = append(obj, targetAnchoredText) } - obj = append(obj, dlr.link.midPad) + for _, pad := range dlr.link.pads { + obj = append(obj, pad) + } + for _, handle := range dlr.link.handles { + obj = append(obj, handle) + } return obj } func (dlr *diagramLinkRenderer) Refresh() { - dlr.link.Resize(dlr.MinSize()) - padBasedSourceReferencePoint := dlr.link.sourcePad.GetCenter() - padBasedTargetReferencePoint := dlr.link.targetPad.GetCenter() - padBasedSourcePosition := dlr.link.sourcePad.getConnectionPoint(padBasedTargetReferencePoint) - padBasedTargetPosition := dlr.link.targetPad.getConnectionPoint(padBasedSourceReferencePoint) + // The pads to which the link is connected can be nil during a connection transaction, in which case we leave the end points + // at their present location. Note that the initial computations are done in diagram coordinates, + // then link coordinates + var sourceDiagramCoordinateReferencePoint fyne.Position + var targetDiagramCoordinateReferencePoint fyne.Position + var sourceDiagramCoordinatePosition fyne.Position + var targetDiagramCoordinatePosition fyne.Position + currentSourceDiagramCoordinatePosition := dlr.link.getSourcePosition().Add(dlr.link.Position()) + currentTargetDiagramCoordinatePosition := dlr.link.getTargetPosition().Add(dlr.link.Position()) + if dlr.link.sourcePad != nil { + sourceDiagramCoordinateReferencePoint = dlr.link.sourcePad.GetCenterInDiagramCoordinates() + } else { + // we have to translate the source position back to diagram coordinates + sourceDiagramCoordinateReferencePoint = currentSourceDiagramCoordinatePosition + } + if dlr.link.targetPad != nil { + targetDiagramCoordinateReferencePoint = dlr.link.targetPad.GetCenterInDiagramCoordinates() + } else { + // we have to translate the target position back to diagram coordinates + targetDiagramCoordinateReferencePoint = currentTargetDiagramCoordinatePosition + } + if dlr.link.sourcePad != nil { + sourceDiagramCoordinatePosition = dlr.link.sourcePad.getConnectionPointInDiagramCoordinates(targetDiagramCoordinateReferencePoint) + } else { + sourceDiagramCoordinatePosition = currentSourceDiagramCoordinatePosition + } + if dlr.link.targetPad != nil { + targetDiagramCoordinatePosition = dlr.link.targetPad.getConnectionPointInDiagramCoordinates(sourceDiagramCoordinateReferencePoint) + } else { + targetDiagramCoordinatePosition = currentTargetDiagramCoordinatePosition + } // The Position of the link is the upper left hand corner of a bounding box surrounding the source and target positions - linkPosition := fyne.NewPos(float32(math.Min(float64(padBasedSourcePosition.X), float64(padBasedTargetPosition.X))), - float32(math.Min(float64(padBasedSourcePosition.Y), float64(padBasedTargetPosition.Y)))) + linkPosition := fyne.NewPos(float32(math.Min(float64(sourceDiagramCoordinatePosition.X), float64(targetDiagramCoordinatePosition.X))), + float32(math.Min(float64(sourceDiagramCoordinatePosition.Y), float64(targetDiagramCoordinatePosition.Y)))) dlr.link.Move(linkPosition) - dlr.link.linkPoints[0].Move(padBasedSourcePosition.Subtract(linkPosition)) - dlr.link.linkPoints[len(dlr.link.linkPoints)-1].Move(padBasedTargetPosition.Subtract(linkPosition)) + // Now we put the source and target positions back into link coordinates by subtracting the linkPosition + dlr.link.linkPoints[0].Move(sourceDiagramCoordinatePosition.Subtract(linkPosition)) + dlr.link.linkPoints[len(dlr.link.linkPoints)-1].Move(targetDiagramCoordinatePosition.Subtract(linkPosition)) + // TODO adjust the position of the other points based on the change in link position + // Now resize the link - note that MinSize is derived from the point positions + dlr.link.Resize(dlr.MinSize()) + // Position segments only after all points have been positioned for i := 0; i < len(dlr.link.linkPoints)-1; i++ { linkSegment := dlr.link.linkSegments[i] @@ -359,7 +485,10 @@ func (dlr *diagramLinkRenderer) Refresh() { decoration.setBaseAngle(sourceAngle) midOffset = midOffset + float64(decoration.GetReferenceLength()) } - dlr.link.midPad.Move(dlr.link.getMidPosition()) + defaultPadPosition := dlr.link.getMidPosition().AddXY(-pointPadSize/2, -pointPadSize/2) + dlr.link.pads["default"].Move(defaultPadPosition) + dlr.link.pads["default"].Resize(fyne.NewSize(pointPadSize, pointPadSize)) + dlr.link.pads["default"].Refresh() targetOffset := 0.0 for _, decoration := range dlr.link.TargetDecorations { @@ -403,6 +532,37 @@ func (dlr *diagramLinkRenderer) Refresh() { decoration.SetFillColor(dlr.link.diagram.GetBackgroundColor()) decoration.Refresh() } + + // calculate the handle positions + for key, handle := range dlr.link.handles { + switch key { + case "source": + handle.Move(dlr.link.linkPoints[0].Position()) + case "target": + handle.Move(dlr.link.linkPoints[len(dlr.link.linkPoints)-1].Position()) + } + handle.Resize(fyne.NewSize(handle.handleSize, handle.handleSize)) + handle.Refresh() + } + dlr.link.diagram.refreshDependentLinks(dlr.link) dlr.link.GetDiagram().ForceRepaint() } + +type connectionTransaction struct { + linkPoint *LinkPoint + link DiagramLink + initialPad ConnectionPad + initialPosition fyne.Position + pendingPad ConnectionPad +} + +func NewConnectionTransaction(linkPoint *LinkPoint, link DiagramLink, initialPad ConnectionPad, initialPosition fyne.Position) *connectionTransaction { + ct := &connectionTransaction{ + linkPoint: linkPoint, + link: link, + initialPad: initialPad, + initialPosition: initialPosition, + } + return ct +} diff --git a/widget/diagramwidget/linkpoint.go b/widget/diagramwidget/linkpoint.go index 8ab4b3af..21a8229c 100644 --- a/widget/diagramwidget/linkpoint.go +++ b/widget/diagramwidget/linkpoint.go @@ -8,11 +8,13 @@ import ( // LinkPoint identifies the point at which a link end is connected to another diagram element's connection pad type LinkPoint struct { widget.BaseWidget + link DiagramLink } -func NewLinkPoint(link *BaseDiagramLink) *LinkPoint { +func NewLinkPoint(link DiagramLink) *LinkPoint { lp := &LinkPoint{} lp.BaseWidget.ExtendBaseWidget(lp) + lp.link = link return lp } @@ -21,6 +23,14 @@ func (lp *LinkPoint) CreateRenderer() fyne.WidgetRenderer { return lpr } +func (lp *LinkPoint) GetLink() DiagramLink { + return lp.link +} + +func (lp *LinkPoint) IsConnectionAllowed(connectionPad ConnectionPad) bool { + return lp.link.IsConnectionAllowed(lp, connectionPad) +} + // linkPointRenderer type linkPointRenderer struct { } diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index 0666aa78..faa0547e 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -5,14 +5,21 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" + "github.com/twpayne/go-geom" + "github.com/twpayne/go-geom/xy" ) +var _ fyne.Tappable = (*LinkSegment)(nil) +var _ desktop.Hoverable = (*LinkSegment)(nil) + type LinkSegment struct { widget.BaseWidget link *BaseDiagramLink - p1 fyne.Position - p2 fyne.Position + // p1 and p2 are coordinates in the link's coordinate space + p1 fyne.Position + p2 fyne.Position } func NewLinkSegment(link *BaseDiagramLink, p1 fyne.Position, p2 fyne.Position) *LinkSegment { @@ -22,6 +29,7 @@ func NewLinkSegment(link *BaseDiagramLink, p1 fyne.Position, p2 fyne.Position) * p2: p2, } ls.BaseWidget.ExtendBaseWidget(ls) + ls.Resize(ls.MinSize()) return ls } @@ -33,6 +41,24 @@ func (ls *LinkSegment) CreateRenderer() fyne.WidgetRenderer { return lsr } +func (ls *LinkSegment) MouseIn(event *desktop.MouseEvent) { +} + +func (ls *LinkSegment) MouseMoved(event *desktop.MouseEvent) { +} + +func (ls *LinkSegment) MouseOut() { +} + +func (ls *LinkSegment) Tapped(event *fyne.PointEvent) { + clickPoint := geom.Coord{float64(event.Position.X), float64(event.Position.Y)} + p1 := geom.Coord{float64(ls.p1.X), float64(ls.p1.Y)} + p2 := geom.Coord{float64(ls.p2.X), float64(ls.p2.Y)} + if xy.DistanceFromPointToLine(clickPoint, p1, p2) <= float64(ls.link.strokeWidth/2)+1 { + ls.link.diagram.DiagramElementTapped(ls.link, event) + } +} + func (ls *LinkSegment) SetPoints(p1 fyne.Position, p2 fyne.Position) { ls.p1 = p1 ls.p2 = p2 @@ -64,8 +90,13 @@ func (lsr *linkSegmentRenderer) Objects() []fyne.CanvasObject { } func (lsr *linkSegmentRenderer) Refresh() { - lsr.line.Position1 = lsr.ls.p1 - lsr.line.Position2 = lsr.ls.p2 + minX := math.Min(float64(lsr.ls.p1.X), float64(lsr.ls.p2.X)) + minY := math.Min(float64(lsr.ls.p1.Y), float64(lsr.ls.p2.Y)) + widgetPosition := fyne.NewPos(float32(minX), float32(minY)) + lsr.ls.Move(widgetPosition) + lsr.ls.Resize(lsr.MinSize()) + lsr.line.Position1 = lsr.ls.p1.AddXY(-widgetPosition.X, -widgetPosition.Y) + lsr.line.Position2 = lsr.ls.p2.AddXY(-widgetPosition.X, -widgetPosition.Y) lsr.line.StrokeColor = lsr.ls.link.LinkColor lsr.line.StrokeWidth = lsr.ls.link.strokeWidth lsr.ls.link.GetDiagram().ForceRepaint() diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 4a989bb8..676a8266 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -2,7 +2,6 @@ package diagramwidget import ( "image/color" - "log" "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" @@ -15,17 +14,18 @@ import ( type DiagramNode interface { DiagramElement getBaseDiagramNode() *BaseDiagramNode - MouseIn(event *desktop.MouseEvent) - MouseOut() - MouseMoved(event *desktop.MouseEvent) + // MouseIn(event *desktop.MouseEvent) + // MouseOut() + // MouseMoved(event *desktop.MouseEvent) R2Center() r2.Vec2 } // Validate that BaseDiagramNode implements DiagramElement and Tappable var _ DiagramElement = (*BaseDiagramNode)(nil) var _ fyne.Tappable = (*BaseDiagramNode)(nil) -var _ desktop.Hoverable = (*BaseDiagramNode)(nil) -var _ desktop.Hoverable = (DiagramNode)(nil) + +// var _ desktop.Hoverable = (*BaseDiagramNode)(nil) +// var _ desktop.Hoverable = (DiagramNode)(nil) var _ fyne.Widget = (*BaseDiagramNode)(nil) var _ fyne.Widget = (DiagramNode)(nil) @@ -55,7 +55,6 @@ type BaseDiagramNode struct { // HandleStrokeWidth is the stroke width of the node handle, defaults // to 3. HandleStroke float32 - edgePad *RectanglePad // MovedCallback, if present, is invoked when the node is moved MovedCallback func() } @@ -79,8 +78,8 @@ func InitializeBaseDiagramNode(diagramNode DiagramNode, diagram *DiagramWidget, bdn.BoxStrokeWidth = 1 bdn.HandleStroke = 3 bdn.diagramElement.initialize(diagram, nodeID) - bdn.edgePad = NewRectanglePad(bdn) - bdn.edgePad.Hide() + bdn.pads["default"] = NewRectanglePad(bdn) + bdn.pads["default"].Hide() for _, handleKey := range []string{"upperLeft", "upperMiddle", "upperRight", "leftMiddle", "rightMiddle", "lowerLeft", "lowerMiddle", "lowerRight"} { newHandle := NewHandle(bdn) bdn.handles[handleKey] = newHandle @@ -147,7 +146,7 @@ func (bdn *BaseDiagramNode) GetDefaultConnectionPad() ConnectionPad { // GetEdgePad returns the edge pad for the node func (bdn *BaseDiagramNode) GetEdgePad() ConnectionPad { - return bdn.edgePad + return bdn.pads["default"] } func (bdn *BaseDiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) { @@ -204,6 +203,10 @@ func (bdn *BaseDiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) bdn.GetDiagram().ForceRepaint() } +func (bdn *BaseDiagramNode) handleDragEnd(handle *Handle) { + +} + func (bdn *BaseDiagramNode) innerPos() fyne.Position { return fyne.Position{ X: float32(bdn.Padding), @@ -211,22 +214,29 @@ func (bdn *BaseDiagramNode) innerPos() fyne.Position { } } -func (bdn *BaseDiagramNode) MouseIn(event *desktop.MouseEvent) { - log.Print("Node MouseIn") - bdn.handleColor = bdn.diagram.DiagramTheme.Color(theme.ColorNameFocus, bdn.diagram.ThemeVariant) - bdn.Refresh() - bdn.GetDiagram().ForceRepaint() -} - -func (bdn *BaseDiagramNode) MouseOut() { - log.Print("Node MouseIn") - bdn.handleColor = bdn.foregroundColor - bdn.Refresh() - bdn.GetDiagram().ForceRepaint() -} - -func (bdn *BaseDiagramNode) MouseMoved(event *desktop.MouseEvent) { -} +// func (bdn *BaseDiagramNode) MouseIn(event *desktop.MouseEvent) { +// log.Print("Node MouseIn") +// // conTrans := bdn.GetDiagram().connectionTransaction +// // for _, pad := range bdn.pads { +// // if conTrans != nil && conTrans.link.IsConnectionAllowed(conTrans.linkPoint, pad) { +// // pad.Show() +// // } else { +// // pad.Hide() +// // } +// // } +// // bdn.Refresh() +// // bdn.diagram.ForceRepaint() +// } + +// func (bdn *BaseDiagramNode) MouseOut() { +// log.Print("Node MouseOut") +// // for _, pad := range bdn.pads { +// // pad.Hide() +// // } +// } + +// func (bdn *BaseDiagramNode) MouseMoved(event *desktop.MouseEvent) { +// } func (bdn *BaseDiagramNode) Move(position fyne.Position) { bdn.BaseWidget.Move(position) @@ -296,8 +306,10 @@ func (dnr *diagramNodeRenderer) Layout(size fyne.Size) { func (dnr *diagramNodeRenderer) Objects() []fyne.CanvasObject { obj := make([]fyne.CanvasObject, 0) obj = append(obj, dnr.box) - obj = append(obj, dnr.node.edgePad) obj = append(obj, dnr.node.innerObject) + for _, pad := range dnr.node.pads { + obj = append(obj, pad) + } for _, handle := range dnr.node.handles { obj = append(obj, handle) } @@ -307,9 +319,9 @@ func (dnr *diagramNodeRenderer) Objects() []fyne.CanvasObject { func (dnr *diagramNodeRenderer) Refresh() { nodeSize := dnr.MinSize() dnr.node.Resize(nodeSize) - dnr.node.edgePad.Resize(nodeSize) - dnr.node.edgePad.Move(fyne.NewPos(0, 0)) - dnr.node.edgePad.Refresh() + dnr.node.pads["default"].Resize(nodeSize) + dnr.node.pads["default"].Move(fyne.NewPos(0, 0)) + dnr.node.pads["default"].Refresh() if dnr.node.innerObject != nil { dnr.node.innerObject.Move(dnr.node.innerPos()) @@ -348,7 +360,9 @@ func (dnr *diagramNodeRenderer) Refresh() { dnr.box.FillColor = color.Transparent dnr.box.StrokeColor = dnr.node.diagram.GetForegroundColor() - dnr.node.edgePad.Refresh() + for _, pad := range dnr.node.pads { + pad.Refresh() + } dnr.node.diagram.refreshDependentLinks(dnr.node) dnr.node.GetDiagram().ForceRepaint() } From 9f4ef1c250485290153980728b993841bffcdac2 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 24 May 2023 12:31:52 -0400 Subject: [PATCH 25/53] Added primarySelection --- widget/diagramwidget/diagram.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 07ef9936..4a0ae934 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -50,12 +50,14 @@ type DiagramWidget struct { // DesiredSize specifies the size of the displayed diagram. Defaults to 800 x 600 DesiredSize fyne.Size - Nodes map[string]DiagramNode - Links map[string]DiagramLink - selection map[string]DiagramElement - diagramElementLinkDependencies map[string][]linkPadPair - connectionTransaction *connectionTransaction - padColor color.Color + Nodes map[string]DiagramNode + Links map[string]DiagramLink + primarySelection DiagramElement + selection map[string]DiagramElement + diagramElementLinkDependencies map[string][]linkPadPair + connectionTransaction *connectionTransaction + padColor color.Color + PrimaryDiagramElementSelectionChangedCallback func(string) // TODO Remove dummyBox when fyne rendering issue is resolved dummyBox *canvas.Rectangle @@ -125,6 +127,12 @@ func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { func (dw *DiagramWidget) addElementToSelection(de DiagramElement) { if !dw.IsSelected(de) { + if dw.primarySelection == nil { + dw.primarySelection = de + if dw.PrimaryDiagramElementSelectionChangedCallback != nil { + dw.PrimaryDiagramElementSelectionChangedCallback(de.GetDiagramElementID()) + } + } dw.selection[de.GetDiagramElementID()] = de de.ShowHandles() } @@ -256,8 +264,16 @@ func (dw *DiagramWidget) removeDependenciesInvolvingLink(linkID string) { } func (dw *DiagramWidget) removeElementFromSelection(de DiagramElement) { - delete(dw.selection, de.GetDiagramElementID()) - de.HideHandles() + if dw.IsSelected(de) { + delete(dw.selection, de.GetDiagramElementID()) + if dw.primarySelection == de { + dw.primarySelection = nil + if dw.PrimaryDiagramElementSelectionChangedCallback != nil { + dw.PrimaryDiagramElementSelectionChangedCallback("") + } + } + de.HideHandles() + } } func (dw *DiagramWidget) removeLinkDependency(diagramElement DiagramElement, link *BaseDiagramLink, pad ConnectionPad) { From 6001e0a7fbad32e67f2422286120fba3b16b0887 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 25 May 2023 13:08:33 -0400 Subject: [PATCH 26/53] Added selection functions --- widget/diagramwidget/diagram.go | 70 +++++++++++++++++++++++++-------- widget/diagramwidget/link.go | 3 ++ 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 4a0ae934..3be396ae 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -50,13 +50,16 @@ type DiagramWidget struct { // DesiredSize specifies the size of the displayed diagram. Defaults to 800 x 600 DesiredSize fyne.Size - Nodes map[string]DiagramNode - Links map[string]DiagramLink - primarySelection DiagramElement - selection map[string]DiagramElement - diagramElementLinkDependencies map[string][]linkPadPair - connectionTransaction *connectionTransaction - padColor color.Color + Nodes map[string]DiagramNode + Links map[string]DiagramLink + primarySelection DiagramElement + selection map[string]DiagramElement + diagramElementLinkDependencies map[string][]linkPadPair + connectionTransaction *connectionTransaction + padColor color.Color + // LinkConnectionChangedCallback is called when a link connection changes. The string can either be + // "source" or "target". The first pad is the old pad, the second one is the new pad + LinkConnectionChangedCallback func(DiagramLink, string, ConnectionPad, ConnectionPad) PrimaryDiagramElementSelectionChangedCallback func(string) // TODO Remove dummyBox when fyne rendering issue is resolved @@ -87,6 +90,19 @@ func NewDiagramWidget(id string) *DiagramWidget { return dw } +func (dw *DiagramWidget) addElementToSelection(de DiagramElement) { + if !dw.IsSelected(de) { + if dw.primarySelection == nil { + dw.primarySelection = de + if dw.PrimaryDiagramElementSelectionChangedCallback != nil { + dw.PrimaryDiagramElementSelectionChangedCallback(de.GetDiagramElementID()) + } + } + dw.selection[de.GetDiagramElementID()] = de + de.ShowHandles() + } +} + // addLink adds a link to the diagram func (dw *DiagramWidget) addLink(link DiagramLink) { dw.Links[link.GetDiagramElementID()] = link @@ -125,17 +141,13 @@ func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { return &r } -func (dw *DiagramWidget) addElementToSelection(de DiagramElement) { - if !dw.IsSelected(de) { - if dw.primarySelection == nil { - dw.primarySelection = de - if dw.PrimaryDiagramElementSelectionChangedCallback != nil { - dw.PrimaryDiagramElementSelectionChangedCallback(de.GetDiagramElementID()) - } - } - dw.selection[de.GetDiagramElementID()] = de - de.ShowHandles() +// ClearSelectionNoCallback clears the selection. It does not invoke the PrimaryDiagramElementSelectionChangedCallback +func (dw *DiagramWidget) ClearSelectionNoCallback() { + dw.primarySelection = nil + for _, element := range dw.selection { + element.HideHandles() } + dw.selection = map[string]DiagramElement{} } // Cursor returns the default cursor @@ -211,6 +223,18 @@ func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { dw.Refresh() } +// 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 + } + return diagramElements +} + // hideAllPads is a work-around for fyne Issue #3906 in which a child's Hoverable interface // (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 @@ -316,6 +340,18 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { dw.Refresh() } +// SelectDiagramElementNoCallback makes the indicated element the PrimarySelection. It does not invoke the +// PrimaryDiagramElementSelectionChangedCallback +func (dw *DiagramWidget) SelectDiagramElementNoCallback(id string) { + dw.ClearSelectionNoCallback() + element := dw.GetDiagramElement(id) + if element != nil { + dw.primarySelection = element + dw.selection[id] = element + element.ShowHandles() + } +} + // showAllPads is a work-around for fyne Issue #3906 in which a child's Hoverable interface // (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 diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 8d98b4c6..08186a53 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -283,6 +283,9 @@ func (bdl *BaseDiagramLink) handleDragEnd(handle *Handle) { case "target": bdl.targetPad = connTrans.pendingPad } + if bdl.diagram.LinkConnectionChangedCallback != nil { + bdl.diagram.LinkConnectionChangedCallback(bdl, handleKey, connTrans.initialPad, connTrans.pendingPad) + } } else { // We revert to the original pad. bdl.pads[handleKey] = connTrans.initialPad From c40991f079eafe6b7bfb0069e3dfc8742e891c65 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 26 May 2023 11:33:01 -0400 Subject: [PATCH 27/53] Added Tapped override --- widget/diagramwidget/diagram.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 3be396ae..28ceafc1 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -59,8 +59,16 @@ type DiagramWidget struct { padColor color.Color // LinkConnectionChangedCallback is called when a link connection changes. The string can either be // "source" or "target". The first pad is the old pad, the second one is the new pad - LinkConnectionChangedCallback func(DiagramLink, string, ConnectionPad, ConnectionPad) + LinkConnectionChangedCallback func(DiagramLink, string, ConnectionPad, ConnectionPad) + // OnTappedCallback is called when the diagram background is tapped. If present, it overrides the default + // diagram behavior for Tapped() + OnTapped func(*DiagramWidget, *fyne.PointEvent) + // PrimaryDiagramElementSelectionChangedCallback is called when the primary element selection changes PrimaryDiagramElementSelectionChangedCallback func(string) + // ElementTappedExtendsSelection determines the behavior when one or more elements are already selected and + // an element that is not currently selected is tapped. When true, the new element is added to the selection. + // When false, the selection is cleared and the new element is made the only selected element. + ElementTappedExtendsSelection bool // TODO Remove dummyBox when fyne rendering issue is resolved dummyBox *canvas.Rectangle @@ -141,6 +149,13 @@ func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { return &r } +// ClearSelection clears the selection and invokes the PrimaryDiagramElementSelectionChangedCallback +func (dw *DiagramWidget) ClearSelection() { + for _, de := range dw.selection { + dw.removeElementFromSelection(de) + } +} + // ClearSelectionNoCallback clears the selection. It does not invoke the PrimaryDiagramElementSelectionChangedCallback func (dw *DiagramWidget) ClearSelectionNoCallback() { dw.primarySelection = nil @@ -157,6 +172,9 @@ func (dw *DiagramWidget) Cursor() desktop.Cursor { // DiagramElementTapped adds the element to the selection when the element is tapped func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.PointEvent) { + if !dw.ElementTappedExtendsSelection { + dw.ClearSelectionNoCallback() + } if !dw.IsSelected(de) { dw.addElementToSelection(de) } @@ -371,8 +389,10 @@ func (dw *DiagramWidget) showAllPads() { // Tapped respondss to taps in the diagram background. It removes all diagram elements // from the selection func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { - for _, de := range dw.selection { - dw.removeElementFromSelection(de) + if dw.OnTapped != nil { + dw.OnTapped(dw, event) + } else { + dw.ClearSelection() } dw.ForceRepaint() } From 2d5e9f355cbeb024aa19bbd6c2cc5c180eb910cb Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 8 Jun 2023 13:28:03 -0400 Subject: [PATCH 28/53] Dynamic Link Creation; DiaagramElement kind checking --- cmd/diagramdemo/main.go | 24 +++- widget/diagramwidget/connectionpad.go | 74 ++++++++++- widget/diagramwidget/diagram.go | 27 +++- widget/diagramwidget/diagramElement.go | 4 + widget/diagramwidget/diagram_test.go | 4 +- widget/diagramwidget/geometry/r2/box.go | 62 ++++++++- widget/diagramwidget/geometry/r2/box_test.go | 73 +++++++++++ widget/diagramwidget/link.go | 131 +++++++++++++++---- widget/diagramwidget/linkpoint.go | 2 +- widget/diagramwidget/node.go | 10 ++ 10 files changed, 365 insertions(+), 46 deletions(-) create mode 100644 widget/diagramwidget/geometry/r2/box_test.go diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index b5a3107a..72220f14 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -98,7 +98,9 @@ func main() { node5.Move(fyne.NewPos(600, 200)) // Link0 - link0 := diagramwidget.NewDiagramLink(diagramWidget, node0.GetEdgePad(), node1.GetEdgePad(), "Link0") + link0 := diagramwidget.NewDiagramLink(diagramWidget, "Link0") + link0.SetSourcePad(node0.GetEdgePad()) + link0.SetTargetPad(node1.GetEdgePad()) link0.AddSourceAnchoredText("sourceRole", "sourceRole") link0.AddMidpointAnchoredText("linkName", "Link 0") solidDiamond := createDiamondDecoration() @@ -106,7 +108,9 @@ func main() { link0.AddSourceDecoration(solidDiamond) // Link1 - link1 := diagramwidget.NewDiagramLink(diagramWidget, node2.GetEdgePad(), node1.GetEdgePad(), "Link1") + link1 := diagramwidget.NewDiagramLink(diagramWidget, "Link1") + link1.SetSourcePad(node2.GetEdgePad()) + link1.SetTargetPad(node1.GetEdgePad()) link1.LinkColor = color.RGBA{255, 64, 64, 255} link1.AddTargetDecoration(diagramwidget.NewArrowhead()) link1.AddTargetDecoration(diagramwidget.NewArrowhead()) @@ -117,23 +121,31 @@ func main() { link1.AddSourceDecoration(diagramwidget.NewArrowhead()) // Link2 - link2 := diagramwidget.NewDiagramLink(diagramWidget, node0.GetEdgePad(), node3.GetEdgePad(), "Link2") + link2 := diagramwidget.NewDiagramLink(diagramWidget, "Link2") + link2.SetSourcePad(node0.GetEdgePad()) + link2.SetTargetPad(node3.GetEdgePad()) link2.AddMidpointAnchoredText("linkName", "Link 2") link2.AddSourceDecoration(createHalfArrowDecoration()) // Link3 - link3 := diagramwidget.NewDiagramLink(diagramWidget, node2.GetEdgePad(), node3.GetEdgePad(), "Link3") + link3 := diagramwidget.NewDiagramLink(diagramWidget, "Link3") + link3.SetSourcePad(node2.GetEdgePad()) + link3.SetTargetPad(node3.GetEdgePad()) link3.AddSourceAnchoredText("sourceRole", "sourceRole") link3.AddMidpointAnchoredText("linkName", "Link 3") link3.AddTargetAnchoredText("targetRole", "targetRole") link3.AddMidpointDecoration(createTriangleDecoration()) // Link4 - link4 := diagramwidget.NewDiagramLink(diagramWidget, node4.GetEdgePad(), node3.GetEdgePad(), "Link4") + link4 := diagramwidget.NewDiagramLink(diagramWidget, "Link4") + link4.SetSourcePad(node4.GetEdgePad()) + link4.SetTargetPad(node3.GetEdgePad()) link4.AddMidpointAnchoredText("linkName", "Link 4") // Link5 - link5 := diagramwidget.NewDiagramLink(diagramWidget, link4.GetMidPad(), node5.GetEdgePad(), "Link5") + link5 := diagramwidget.NewDiagramLink(diagramWidget, "Link5") + link5.SetSourcePad(link4.GetMidPad()) + link5.SetTargetPad(node5.GetEdgePad()) link5.AddMidpointAnchoredText("linkName", "Link 5") link5.AddTargetDecoration(diagramwidget.NewArrowhead()) diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index 7c7a1b10..94aee705 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -23,6 +23,8 @@ type ConnectionPad interface { GetPadOwner() DiagramElement GetCenterInDiagramCoordinates() fyne.Position getConnectionPointInDiagramCoordinates(referencePoint fyne.Position) fyne.Position + MouseDown(*desktop.MouseEvent) + MouseUp(*desktop.MouseEvent) } type connectionPad struct { @@ -35,6 +37,60 @@ func (cp *connectionPad) GetPadOwner() DiagramElement { return cp.padOwner } +// MouseDown responds to mouse down events +func (pp *PointPad) MouseDown(event *desktop.MouseEvent) { + connectionTransaction := pp.padOwner.GetDiagram().connectionTransaction + if connectionTransaction != nil { + link := connectionTransaction.link + if link.isConnectionAllowed(connectionTransaction.linkPoint, pp) { + padOwnerPosition := pp.padOwner.Position() + pseudoEvent := &fyne.DragEvent{ + PointEvent: fyne.PointEvent{}, + Dragged: fyne.NewDelta(event.Position.X+padOwnerPosition.X+10, event.Position.Y+padOwnerPosition.Y-10), + } + // the link point has to be changed before the handle is dragged + connectionTransaction.linkPoint = connectionTransaction.link.getLinkPoints()[1] + link.GetHandle(TARGET.ToString()).Dragged(pseudoEvent) + link.Refresh() + link.SetSourcePad(pp) + link.Refresh() + link.GetDiagram().SelectDiagramElement(link) + link.ShowHandles() + } + } +} + +// MouseDown responds to mouse down events +func (rp *RectanglePad) MouseDown(event *desktop.MouseEvent) { + connectionTransaction := rp.padOwner.GetDiagram().connectionTransaction + if connectionTransaction != nil { + link := connectionTransaction.link + if link.isConnectionAllowed(connectionTransaction.linkPoint, rp) { + padOwnerPosition := rp.padOwner.Position() + pseudoEvent := &fyne.DragEvent{ + PointEvent: fyne.PointEvent{}, + Dragged: fyne.NewDelta(event.Position.X+padOwnerPosition.X, event.Position.Y+padOwnerPosition.Y), + } + // the link point has to be changed before the handle is dragged + connectionTransaction.linkPoint = connectionTransaction.link.getLinkPoints()[1] + link.GetHandle(TARGET.ToString()).Dragged(pseudoEvent) + link.SetSourcePad(rp) + link.GetDiagram().SelectDiagramElement(link) + link.ShowHandles() + } + } +} + +// MouseUp responds to mouse up events +func (pp *PointPad) MouseUp(event *desktop.MouseEvent) { + +} + +// MouseUp responds to mouse up events +func (rp *RectanglePad) MouseUp(event *desktop.MouseEvent) { + +} + /****************************** PointPad *******************************/ @@ -85,7 +141,7 @@ func (pp *PointPad) getConnectionPointInDiagramCoordinates(referencePoint fyne.P // MouseIn responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseIn(event *desktop.MouseEvent) { conTrans := pp.padOwner.GetDiagram().connectionTransaction - if conTrans != nil && conTrans.link.IsConnectionAllowed(conTrans.linkPoint, pp) { + if conTrans != nil && conTrans.link.isConnectionAllowed(conTrans.linkPoint, pp) { pp.padColor = pp.padOwner.GetDiagram().padColor conTrans.pendingPad = pp } else { @@ -191,13 +247,19 @@ func (rp *RectanglePad) GetCenterInDiagramCoordinates() fyne.Position { // getConnectionPointInDiagramCoordinates returns the point at which the connection should be made from a reference point. // The reference point is in diagram coordinates and the returned point is also in diagram coordinates. // For a RectanglePad this point is the intersection of a line segment from the reference point to the center -// of the rectangle pad and the rectangle bounding the pad. +// of the rectangle pad and the rectangle bounding the pad. If the reference point is within the bounds of the rectangle, +// the returned point is the point on the perimeter that is nearest the reference point. func (rp *RectanglePad) getConnectionPointInDiagramCoordinates(referencePoint fyne.Position) fyne.Position { + var connectionPoint r2.Vec2 box := rp.makeBox() r2ReferencePoint := r2.MakeVec2(float64(referencePoint.X), float64(referencePoint.Y)) - linkLine := r2.MakeLineFromEndpoints(box.Center(), r2ReferencePoint) - r2Intersection, _ := box.Intersect(linkLine) - return fyne.NewPos(float32(r2Intersection.X), float32(r2Intersection.Y)) + if box.Contains(r2ReferencePoint) { + connectionPoint = box.FindPerimeterPointNearestContainedPoint(r2ReferencePoint) + } else { + linkLine := r2.MakeLineFromEndpoints(box.Center(), r2ReferencePoint) + connectionPoint, _ = box.Intersect(linkLine) + } + return fyne.NewPos(float32(connectionPoint.X), float32(connectionPoint.Y)) } // makeBox returns an r2 box representing the rectangle pad's position and size in the @@ -215,7 +277,7 @@ func (rp *RectanglePad) makeBox() r2.Box { // MouseIn responds to the mouse entering the bounds of the RectanglePad func (rp *RectanglePad) MouseIn(event *desktop.MouseEvent) { conTrans := rp.padOwner.GetDiagram().connectionTransaction - if conTrans != nil && conTrans.link.IsConnectionAllowed(conTrans.linkPoint, rp) { + if conTrans != nil && conTrans.link.isConnectionAllowed(conTrans.linkPoint, rp) { rp.padColor = rp.padOwner.GetDiagram().padColor conTrans.pendingPad = rp } else { diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 28ceafc1..58544bb4 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -57,12 +57,14 @@ type DiagramWidget struct { diagramElementLinkDependencies map[string][]linkPadPair connectionTransaction *connectionTransaction padColor color.Color + // IsConnectionAllowedCallback is called to determine whether a particular connection between a link and a pad is allowed + IsConnectionAllowedCallback func(DiagramLink, LinkEnd, ConnectionPad) bool // LinkConnectionChangedCallback is called when a link connection changes. The string can either be // "source" or "target". The first pad is the old pad, the second one is the new pad LinkConnectionChangedCallback func(DiagramLink, string, ConnectionPad, ConnectionPad) // OnTappedCallback is called when the diagram background is tapped. If present, it overrides the default // diagram behavior for Tapped() - OnTapped func(*DiagramWidget, *fyne.PointEvent) + OnTappedCallback func(*DiagramWidget, *fyne.PointEvent) // PrimaryDiagramElementSelectionChangedCallback is called when the primary element selection changes PrimaryDiagramElementSelectionChangedCallback func(string) // ElementTappedExtendsSelection determines the behavior when one or more elements are already selected and @@ -152,6 +154,7 @@ func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { // ClearSelection clears the selection and invokes the PrimaryDiagramElementSelectionChangedCallback func (dw *DiagramWidget) ClearSelection() { for _, de := range dw.selection { + de.HideHandles() dw.removeElementFromSelection(de) } } @@ -348,16 +351,22 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { dw.RemoveElement(pair.link.id) } delete(dw.diagramElementLinkDependencies, elementID) - switch element.(type) { - case *BaseDiagramNode: + if element.IsNode() { delete(dw.Nodes, elementID) - case *BaseDiagramLink: + } else if element.IsLink() { delete(dw.Links, elementID) dw.removeDependenciesInvolvingLink(elementID) } dw.Refresh() } +// SelectDiagramElement clears the selection, makes the indicated element the primary selection, and invokes +// the PrimaryDiagramElementSelectionChangedCallback +func (dw *DiagramWidget) SelectDiagramElement(element DiagramElement) { + dw.ClearSelection() + dw.addElementToSelection(element) +} + // SelectDiagramElementNoCallback makes the indicated element the PrimarySelection. It does not invoke the // PrimaryDiagramElementSelectionChangedCallback func (dw *DiagramWidget) SelectDiagramElementNoCallback(id string) { @@ -386,11 +395,17 @@ func (dw *DiagramWidget) showAllPads() { } } +// StartNewLinkConnectionTransaction starts the process of adding a link, setting up for the source connection +func (dw *DiagramWidget) StartNewLinkConnectionTransaction(link DiagramLink) { + dw.connectionTransaction = NewConnectionTransaction(link.getBaseDiagramLink().linkPoints[0], link, nil, fyne.NewPos(0, 0)) + dw.showAllPads() +} + // Tapped respondss to taps in the diagram background. It removes all diagram elements // from the selection func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { - if dw.OnTapped != nil { - dw.OnTapped(dw, event) + if dw.OnTappedCallback != nil { + dw.OnTappedCallback(dw, event) } else { dw.ClearSelection() } diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index 96d23228..e2d4917b 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -34,6 +34,10 @@ type DiagramElement interface { handleDragEnd(handle *Handle) // HideHandles hides the handles on the DiagramElement HideHandles() + // IsLink returns true if the diagram element is a link + IsLink() bool + // IsNode returns true of the diagram element is a node + IsNode() bool // ShowHandles shows the handles on the DiagramElement ShowHandles() // SetForegroundColor sets the foreground color for the widget diff --git a/widget/diagramwidget/diagram_test.go b/widget/diagramwidget/diagram_test.go index 593ad111..380a3e8c 100644 --- a/widget/diagramwidget/diagram_test.go +++ b/widget/diagramwidget/diagram_test.go @@ -20,7 +20,9 @@ func TestDependencies(t *testing.T) { node2.Move(fyne.NewPos(200, 100)) assert.Equal(t, 0, len(diagram.diagramElementLinkDependencies)) linkID := "Link1" - link := NewDiagramLink(diagram, node1.pads["default"], node2.pads["default"], linkID) + link := NewDiagramLink(diagram, linkID) + link.SetSourcePad(node1.pads["default"]) + link.SetTargetPad(node2.pads["default"]) assert.NotNil(t, link) assert.Equal(t, 2, len(diagram.diagramElementLinkDependencies)) diff --git a/widget/diagramwidget/geometry/r2/box.go b/widget/diagramwidget/geometry/r2/box.go index 3ffd8a37..c650e9bb 100644 --- a/widget/diagramwidget/geometry/r2/box.go +++ b/widget/diagramwidget/geometry/r2/box.go @@ -36,6 +36,64 @@ func (b Box) Area() float64 { return b.S.X * b.S.Y } +// FindPerimeterPointNearestContainedPoint +func (b Box) FindPerimeterPointNearestContainedPoint(containedPoint Vec2) Vec2 { + if !b.Contains(containedPoint) { + return MakeVec2(0, 0) + } + top := b.GetCorner1().Y + left := b.GetCorner1().X + bottom := b.GetCorner4().Y + right := b.GetCorner4().X + topDistance := containedPoint.Y - top + leftDistance := containedPoint.X - left + bottomDistance := bottom - containedPoint.Y + rightDistance := right - containedPoint.X + if bottomDistance > topDistance { + // top is closer + if rightDistance > leftDistance { + // left is closer + if leftDistance > topDistance { + // top is the closest + return MakeVec2(containedPoint.X, top) + } else { + // left is the closest + return MakeVec2(left, containedPoint.Y) + } + } else { + // right is closer + if rightDistance > topDistance { + // top is the closest + return MakeVec2(containedPoint.X, top) + } else { + // right is the closest + return MakeVec2(right, containedPoint.Y) + } + } + } else { + // bottom is closer + if rightDistance > leftDistance { + // left is closer + if leftDistance > bottomDistance { + // bottom is the closest + return MakeVec2(containedPoint.X, bottom) + } else { + // left is the closest + return MakeVec2(left, containedPoint.Y) + } + } else { + // right is closer + if rightDistance > bottomDistance { + // bottom is the closest + return MakeVec2(containedPoint.X, bottom) + } else { + // right is the closest + return MakeVec2(right, containedPoint.Y) + } + } + } +} + // GetCorner1 returns the top left corner of the box func (b Box) GetCorner1() Vec2 { return b.A @@ -126,11 +184,11 @@ func (b Box) Center() Vec2 { // Contains returns true if the point v is within the box b. func (b Box) Contains(v Vec2) bool { - if (v.X < b.GetCorner1().X) && (v.X > b.GetCorner2().X) { + if (v.X < b.GetCorner1().X) || (v.X > b.GetCorner2().X) { return false } - if (v.Y < b.GetCorner1().Y) && (v.Y > b.GetCorner3().Y) { + if (v.Y < b.GetCorner1().Y) || (v.Y > b.GetCorner3().Y) { return false } diff --git a/widget/diagramwidget/geometry/r2/box_test.go b/widget/diagramwidget/geometry/r2/box_test.go new file mode 100644 index 00000000..0370710f --- /dev/null +++ b/widget/diagramwidget/geometry/r2/box_test.go @@ -0,0 +1,73 @@ +package r2 + +import ( + "testing" +) + +func TestContains(t *testing.T) { + var top, bottom, left, right float64 + top = 100 + left = 100 + right = 200 + bottom = 200 + upperLeft := MakeVec2(left, top) + sVector := MakeVec2(right-left, bottom-top) + box := MakeBox(upperLeft, sVector) + // test point not being contained + cp := MakeVec2(50, 50) + if box.Contains(cp) { + // this should have returned false + t.Errorf("Contains returned true for a point not in the box") + } + cp.X = 150 + cp.Y = 150 + if !box.Contains(cp) { + // this should have returned true + t.Errorf("Contains returned false for a point in the box") + } +} + +func TestFindPerimeterPointNearestContainedPoint(t *testing.T) { + var top, bottom, left, right float64 + top = 100 + left = 100 + right = 200 + bottom = 200 + upperLeft := MakeVec2(left, top) + sVector := MakeVec2(right-left, bottom-top) + box := MakeBox(upperLeft, sVector) + // test point not being contained + cp := MakeVec2(50, 50) + result := box.FindPerimeterPointNearestContainedPoint(cp) + if result.X != 0 || result.Y != 0 { + t.Errorf("Non-contained point did not return 0,0, got %f, %f", result.X, result.Y) + } + // test top + cp.X = 140 + cp.Y = 125 + result = box.FindPerimeterPointNearestContainedPoint(cp) + if result.X != 140 || result.Y != top { + t.Errorf("Point on top not returned, expected 140, 100, got %f, %f", result.X, result.Y) + } + // test left + cp.X = 125 + cp.Y = 140 + result = box.FindPerimeterPointNearestContainedPoint(cp) + if result.X != left || result.Y != 140 { + t.Errorf("Point on left not returned, expected 100, 140, got %f, %f", result.X, result.Y) + } + // test bottom + cp.X = 140 + cp.Y = 175 + result = box.FindPerimeterPointNearestContainedPoint(cp) + if result.X != 140 || result.Y != bottom { + t.Errorf("Point on bottom not returned, expected 140, 200, got %f, %f", result.X, result.Y) + } + // test right + cp.X = 175 + cp.Y = 140 + result = box.FindPerimeterPointNearestContainedPoint(cp) + if result.X != right || result.Y != 140 { + t.Errorf("Point on right not returned, expected 200, 140, got %f, %f", result.X, result.Y) + } +} diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 08186a53..4d4c74b0 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -15,12 +15,34 @@ var _ fyne.Tappable = (*BaseDiagramLink)(nil) var _ desktop.Hoverable = (*BaseDiagramLink)(nil) var _ DiagramElement = (*BaseDiagramLink)(nil) +type LinkEnd int + +var LinkEnds [2]LinkEnd = [2]LinkEnd{SOURCE, TARGET} + +const ( + SOURCE LinkEnd = iota + TARGET +) + +func (le LinkEnd) ToString() string { + switch le { + case SOURCE: + return "source" + case TARGET: + return "target" + } + return "" +} + type DiagramLink interface { DiagramElement getBaseDiagramLink() *BaseDiagramLink + getLinkPoints() []*LinkPoint GetSourcePad() ConnectionPad GetTargetPad() ConnectionPad - IsConnectionAllowed(*LinkPoint, ConnectionPad) bool + isConnectionAllowed(*LinkPoint, ConnectionPad) bool + SetSourcePad(ConnectionPad) + SetTargetPad(ConnectionPad) } // BaseDiagramLink is a directed graphic connection between two DiagramElements that are referred to as the Source @@ -53,6 +75,8 @@ type BaseDiagramLink struct { targetAnchoredText map[string]*AnchoredText MidpointDecorations []Decoration midpointAnchoredText map[string]*AnchoredText + // We keep the typed link so that when extensions are created the callbacks are called with the correct type + typedLink DiagramLink } // NewDiagramLink creates a DiagramLink widget connecting the two indicated ConnectionPads. It adds itself to the @@ -60,21 +84,19 @@ type BaseDiagramLink struct { // It can be used to retrieve the DiagramLink from the Diagram. The ID is intended to be used to facilitate mapping the // DiagramLink to the information it represents in the application. The DiagramLink uses the DiagramWidget's ForegroundColor // as the default color for the line segments. -func NewDiagramLink(diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, linkID string) *BaseDiagramLink { +func NewDiagramLink(diagram *DiagramWidget, linkID string) *BaseDiagramLink { bdl := &BaseDiagramLink{} - InitializeBaseDiagramLink(bdl, diagram, sourcePad, targetPad, linkID) + InitializeBaseDiagramLink(bdl, diagram, linkID) return bdl } // InitializeBaseDiagramLink initializes the BaseDiagramLink. It must be called by any extensions to BaseDiagramLink -func InitializeBaseDiagramLink(diagramLink DiagramLink, diagram *DiagramWidget, sourcePad, targetPad ConnectionPad, linkID string) { +func InitializeBaseDiagramLink(diagramLink DiagramLink, diagram *DiagramWidget, linkID string) { bdl := diagramLink.getBaseDiagramLink() bdl.linkPoints = []*LinkPoint{} bdl.linkSegments = []*LinkSegment{} bdl.LinkColor = diagram.GetForegroundColor() bdl.strokeWidth = 2 - bdl.sourcePad = sourcePad - bdl.targetPad = targetPad bdl.sourceAnchoredText = make(map[string]*AnchoredText) bdl.midpointAnchoredText = make(map[string]*AnchoredText) bdl.targetAnchoredText = make(map[string]*AnchoredText) @@ -86,15 +108,13 @@ func InitializeBaseDiagramLink(diagramLink DiagramLink, diagram *DiagramWidget, bdl.pads["default"].Move(bdl.getMidPosition()) bdl.pads["default"].Hide() bdl.ExtendBaseWidget(diagramLink) - for _, handleKey := range []string{"source", "target"} { + bdl.typedLink = diagramLink + for _, linkEnd := range LinkEnds { newHandle := NewHandle(bdl) - bdl.handles[handleKey] = newHandle + bdl.handles[linkEnd.ToString()] = newHandle newHandle.Hide() } - bdl.diagram.addLink(diagramLink) - bdl.diagram.addLinkDependency(bdl.sourcePad.GetPadOwner(), bdl, bdl.sourcePad) - bdl.diagram.addLinkDependency(bdl.targetPad.GetPadOwner(), bdl, bdl.targetPad) diagramLink.Refresh() } @@ -192,6 +212,10 @@ func (bdl *BaseDiagramLink) getHandleKey(handle *Handle) string { return "" } +func (bdl *BaseDiagramLink) getLinkPoints() []*LinkPoint { + return bdl.linkPoints +} + // GetMidPad returns the PointPad at the midpoint so that it can be used as either the Source or Target // pad for another Link. func (bdl *BaseDiagramLink) GetMidPad() ConnectionPad { @@ -239,11 +263,11 @@ func (bdl *BaseDiagramLink) handleDragged(handle *Handle, event *fyne.DragEvent) var linkPoint *LinkPoint var pad ConnectionPad switch handleKey { - case "source": + case SOURCE.ToString(): linkPoint = bdl.linkPoints[0] pad = bdl.sourcePad bdl.sourcePad = nil - case "target": + case TARGET.ToString(): linkPoint = bdl.linkPoints[len(bdl.linkPoints)-1] pad = bdl.targetPad bdl.targetPad = nil @@ -274,35 +298,38 @@ func (bdl *BaseDiagramLink) handleDragEnd(handle *Handle) { if connTrans != nil { if connTrans.pendingPad != nil { // We have a new pad for connection - bdl.diagram.removeLinkDependency(connTrans.initialPad.GetPadOwner(), bdl, connTrans.initialPad) + if connTrans.initialPad != nil { + bdl.diagram.removeLinkDependency(connTrans.initialPad.GetPadOwner(), bdl, connTrans.initialPad) + } bdl.diagram.addLinkDependency(connTrans.pendingPad.GetPadOwner(), bdl, connTrans.pendingPad) bdl.pads[handleKey] = connTrans.pendingPad switch handleKey { - case "source": + case SOURCE.ToString(): bdl.sourcePad = connTrans.pendingPad - case "target": + case TARGET.ToString(): bdl.targetPad = connTrans.pendingPad } if bdl.diagram.LinkConnectionChangedCallback != nil { - bdl.diagram.LinkConnectionChangedCallback(bdl, handleKey, connTrans.initialPad, connTrans.pendingPad) + bdl.diagram.LinkConnectionChangedCallback(bdl.typedLink, handleKey, connTrans.initialPad, connTrans.pendingPad) } } else { // We revert to the original pad. bdl.pads[handleKey] = connTrans.initialPad switch handleKey { - case "source": + case SOURCE.ToString(): bdl.sourcePad = connTrans.initialPad - case "target": + case TARGET.ToString(): bdl.targetPad = connTrans.initialPad } } bdl.diagram.connectionTransaction = nil bdl.diagram.hideAllPads() + bdl.diagram.SelectDiagramElement(bdl) bdl.Refresh() } } -func (bdl *BaseDiagramLink) IsConnectionAllowed(linkPoint *LinkPoint, pad ConnectionPad) bool { +func (bdl *BaseDiagramLink) isConnectionAllowed(linkPoint *LinkPoint, pad ConnectionPad) bool { pointIndex := -1 for i, lp := range bdl.linkPoints { if lp == linkPoint { @@ -317,10 +344,29 @@ func (bdl *BaseDiagramLink) IsConnectionAllowed(linkPoint *LinkPoint, pad Connec // the point is not the source or target point return false } - // By default, we accept any connection. Subclasses can override + if bdl.diagram.IsConnectionAllowedCallback != nil { + var linkEnd LinkEnd + if pointIndex == 0 { + linkEnd = SOURCE + } else if pointIndex == len(bdl.linkPoints)-1 { + linkEnd = TARGET + } + return bdl.diagram.IsConnectionAllowedCallback(bdl, linkEnd, pad) + } + // By default, we accept any connection + return true +} + +// IsLink returns true because this is a link +func (bdl *BaseDiagramLink) IsLink() bool { return true } +// IsNode returns false because this is a link +func (bdl *BaseDiagramLink) IsNode() bool { + return false +} + // MouseIn responds to the mouse entering the bounding rectangle of the Link func (bdl *BaseDiagramLink) MouseIn(event *desktop.MouseEvent) { log.Print("Entered Link") @@ -337,6 +383,38 @@ func (bdl *BaseDiagramLink) MouseOut() { log.Printf("Left Link") } +// SetSourcePad sets the source pad and adds the link dependency to the diagram +func (bdl *BaseDiagramLink) SetSourcePad(pad ConnectionPad) { + oldPad := bdl.sourcePad + if oldPad != pad { + if oldPad != nil { + bdl.diagram.removeLinkDependency(oldPad.GetPadOwner(), bdl, oldPad) + } + bdl.sourcePad = pad + bdl.diagram.addLinkDependency(bdl.sourcePad.GetPadOwner(), bdl, bdl.sourcePad) + if bdl.diagram.LinkConnectionChangedCallback != nil { + bdl.diagram.LinkConnectionChangedCallback(bdl.typedLink, SOURCE.ToString(), oldPad, pad) + } + bdl.Refresh() + } +} + +// SetTargetPad sets the target pad and adds the link dependency to the diagram +func (bdl *BaseDiagramLink) SetTargetPad(pad ConnectionPad) { + oldPad := bdl.targetPad + if oldPad != pad { + if oldPad != nil { + bdl.diagram.removeLinkDependency(oldPad.GetPadOwner(), bdl, oldPad) + } + bdl.targetPad = pad + bdl.diagram.addLinkDependency(bdl.targetPad.GetPadOwner(), bdl, bdl.targetPad) + if bdl.diagram.LinkConnectionChangedCallback != nil { + bdl.diagram.LinkConnectionChangedCallback(bdl.typedLink, TARGET.ToString(), oldPad, pad) + } + bdl.Refresh() + } +} + // Tapped handles tap events func (bdl *BaseDiagramLink) Tapped(event *fyne.PointEvent) { log.Print("Link tapped") @@ -463,7 +541,12 @@ func (dlr *diagramLinkRenderer) Refresh() { // Have to change the sign of Y since the window inverts the Y axis lineVector := r2.Vec2{X: float64(dlr.link.linkPoints[1].Position().X - dlr.link.linkPoints[0].Position().X), Y: -float64(dlr.link.linkPoints[1].Position().Y - dlr.link.linkPoints[0].Position().Y)} - sourceAngle := lineVector.Angle() + var sourceAngle float64 + if lineVector.Length() == 0 { + sourceAngle = 0 + } else { + sourceAngle = lineVector.Angle() + } sourceOffset := 0.0 for _, decoration := range dlr.link.SourceDecorations { decorationReferencePoint := fyne.Position{ @@ -539,9 +622,9 @@ func (dlr *diagramLinkRenderer) Refresh() { // calculate the handle positions for key, handle := range dlr.link.handles { switch key { - case "source": + case SOURCE.ToString(): handle.Move(dlr.link.linkPoints[0].Position()) - case "target": + case TARGET.ToString(): handle.Move(dlr.link.linkPoints[len(dlr.link.linkPoints)-1].Position()) } handle.Resize(fyne.NewSize(handle.handleSize, handle.handleSize)) diff --git a/widget/diagramwidget/linkpoint.go b/widget/diagramwidget/linkpoint.go index 21a8229c..d78d1fef 100644 --- a/widget/diagramwidget/linkpoint.go +++ b/widget/diagramwidget/linkpoint.go @@ -28,7 +28,7 @@ func (lp *LinkPoint) GetLink() DiagramLink { } func (lp *LinkPoint) IsConnectionAllowed(connectionPad ConnectionPad) bool { - return lp.link.IsConnectionAllowed(lp, connectionPad) + return lp.link.isConnectionAllowed(lp, connectionPad) } // linkPointRenderer diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 676a8266..364b9729 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -214,6 +214,16 @@ func (bdn *BaseDiagramNode) innerPos() fyne.Position { } } +// IsLink returns false because this is a node +func (bdn *BaseDiagramNode) IsLink() bool { + return false +} + +// IsNode returns true because this is a node +func (bdn *BaseDiagramNode) IsNode() bool { + return true +} + // func (bdn *BaseDiagramNode) MouseIn(event *desktop.MouseEvent) { // log.Print("Node MouseIn") // // conTrans := bdn.GetDiagram().connectionTransaction From 16afb1f48e0208c4bcd9999bdf82807b50c04b81 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 16 Jun 2023 16:22:32 -0400 Subject: [PATCH 29/53] Added mouse callbacks; made ConnectionTransaction public --- widget/diagramwidget/connectionpad.go | 40 ++++++++-------- widget/diagramwidget/diagram.go | 53 +++++++++++++++++++-- widget/diagramwidget/link.go | 67 ++++++++++++++++----------- 3 files changed, 107 insertions(+), 53 deletions(-) diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index 94aee705..02353092 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -39,17 +39,17 @@ func (cp *connectionPad) GetPadOwner() DiagramElement { // MouseDown responds to mouse down events func (pp *PointPad) MouseDown(event *desktop.MouseEvent) { - connectionTransaction := pp.padOwner.GetDiagram().connectionTransaction + connectionTransaction := pp.padOwner.GetDiagram().ConnectionTransaction if connectionTransaction != nil { - link := connectionTransaction.link - if link.isConnectionAllowed(connectionTransaction.linkPoint, pp) { + link := connectionTransaction.Link + if link.isConnectionAllowed(connectionTransaction.LinkPoint, pp) { padOwnerPosition := pp.padOwner.Position() pseudoEvent := &fyne.DragEvent{ PointEvent: fyne.PointEvent{}, Dragged: fyne.NewDelta(event.Position.X+padOwnerPosition.X+10, event.Position.Y+padOwnerPosition.Y-10), } // the link point has to be changed before the handle is dragged - connectionTransaction.linkPoint = connectionTransaction.link.getLinkPoints()[1] + connectionTransaction.LinkPoint = connectionTransaction.Link.getLinkPoints()[1] link.GetHandle(TARGET.ToString()).Dragged(pseudoEvent) link.Refresh() link.SetSourcePad(pp) @@ -62,17 +62,17 @@ func (pp *PointPad) MouseDown(event *desktop.MouseEvent) { // MouseDown responds to mouse down events func (rp *RectanglePad) MouseDown(event *desktop.MouseEvent) { - connectionTransaction := rp.padOwner.GetDiagram().connectionTransaction + connectionTransaction := rp.padOwner.GetDiagram().ConnectionTransaction if connectionTransaction != nil { - link := connectionTransaction.link - if link.isConnectionAllowed(connectionTransaction.linkPoint, rp) { + link := connectionTransaction.Link + if link.isConnectionAllowed(connectionTransaction.LinkPoint, rp) { padOwnerPosition := rp.padOwner.Position() pseudoEvent := &fyne.DragEvent{ PointEvent: fyne.PointEvent{}, Dragged: fyne.NewDelta(event.Position.X+padOwnerPosition.X, event.Position.Y+padOwnerPosition.Y), } // the link point has to be changed before the handle is dragged - connectionTransaction.linkPoint = connectionTransaction.link.getLinkPoints()[1] + connectionTransaction.LinkPoint = connectionTransaction.Link.getLinkPoints()[1] link.GetHandle(TARGET.ToString()).Dragged(pseudoEvent) link.SetSourcePad(rp) link.GetDiagram().SelectDiagramElement(link) @@ -140,10 +140,10 @@ func (pp *PointPad) getConnectionPointInDiagramCoordinates(referencePoint fyne.P // MouseIn responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseIn(event *desktop.MouseEvent) { - conTrans := pp.padOwner.GetDiagram().connectionTransaction - if conTrans != nil && conTrans.link.isConnectionAllowed(conTrans.linkPoint, pp) { + conTrans := pp.padOwner.GetDiagram().ConnectionTransaction + if conTrans != nil && conTrans.Link.isConnectionAllowed(conTrans.LinkPoint, pp) { pp.padColor = pp.padOwner.GetDiagram().padColor - conTrans.pendingPad = pp + conTrans.PendingPad = pp } else { pp.padColor = color.Transparent } @@ -158,9 +158,9 @@ func (pp *PointPad) MouseMoved(event *desktop.MouseEvent) { // MouseOut responds to mouse movements within the pointPadSize distance of the center func (pp *PointPad) MouseOut() { pp.padColor = color.Transparent - conTrans := pp.padOwner.GetDiagram().connectionTransaction - if conTrans != nil && conTrans.pendingPad == pp { - conTrans.pendingPad = nil + conTrans := pp.padOwner.GetDiagram().ConnectionTransaction + if conTrans != nil && conTrans.PendingPad == pp { + conTrans.PendingPad = nil } pp.Refresh() pp.padOwner.GetDiagram().ForceRepaint() @@ -276,10 +276,10 @@ func (rp *RectanglePad) makeBox() r2.Box { // MouseIn responds to the mouse entering the bounds of the RectanglePad func (rp *RectanglePad) MouseIn(event *desktop.MouseEvent) { - conTrans := rp.padOwner.GetDiagram().connectionTransaction - if conTrans != nil && conTrans.link.isConnectionAllowed(conTrans.linkPoint, rp) { + conTrans := rp.padOwner.GetDiagram().ConnectionTransaction + if conTrans != nil && conTrans.Link.isConnectionAllowed(conTrans.LinkPoint, rp) { rp.padColor = rp.padOwner.GetDiagram().padColor - conTrans.pendingPad = rp + conTrans.PendingPad = rp } else { rp.padColor = color.Transparent } @@ -294,9 +294,9 @@ func (rp *RectanglePad) MouseMoved(event *desktop.MouseEvent) { // MouseOut responds to mouse movements leaving the rectangle pad func (rp *RectanglePad) MouseOut() { rp.padColor = color.Transparent - conTrans := rp.padOwner.GetDiagram().connectionTransaction - if conTrans != nil && conTrans.pendingPad == rp { - conTrans.pendingPad = nil + conTrans := rp.padOwner.GetDiagram().ConnectionTransaction + if conTrans != nil && conTrans.PendingPad == rp { + conTrans.PendingPad = nil } rp.Refresh() rp.padOwner.GetDiagram().ForceRepaint() diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 58544bb4..f774829a 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -2,6 +2,7 @@ package diagramwidget import ( "image/color" + "log" "reflect" "fyne.io/fyne/v2" @@ -55,13 +56,24 @@ type DiagramWidget struct { primarySelection DiagramElement selection map[string]DiagramElement diagramElementLinkDependencies map[string][]linkPadPair - connectionTransaction *connectionTransaction - padColor color.Color + // ConnectionTransaction holds transient data during the creation of a link. It is public for testing purposes + ConnectionTransaction *ConnectionTransaction + padColor color.Color // IsConnectionAllowedCallback is called to determine whether a particular connection between a link and a pad is allowed IsConnectionAllowedCallback func(DiagramLink, LinkEnd, ConnectionPad) bool // LinkConnectionChangedCallback is called when a link connection changes. The string can either be // "source" or "target". The first pad is the old pad, the second one is the new pad LinkConnectionChangedCallback func(DiagramLink, string, ConnectionPad, ConnectionPad) + // MouseDownCallback is called when a MouseDown occurs in the diagram + MouseDownCallback func(*desktop.MouseEvent) + // MouseInCallback is called when a MouseIn occurs in the diagram + MouseInCallback func(*desktop.MouseEvent) + // MouseMovedCallback is called when a MouseMove occurs in the diagram + MouseMovedCallback func(*desktop.MouseEvent) + // MouseOutCallback is called when a MouseOut occurs in the diagram + MouseOutCallback func() + // MouseUpCallback is invoked when a MouseUp occurs in the diagram + MouseUpCallback func(*desktop.MouseEvent) // OnTappedCallback is called when the diagram background is tapped. If present, it overrides the default // diagram behavior for Tapped() OnTappedCallback func(*DiagramWidget, *fyne.PointEvent) @@ -256,6 +268,11 @@ func (dw *DiagramWidget) GetDiagramElements() map[string]DiagramElement { return diagramElements } +// GetPrimarySelection returns the diagram element that is currently selected +func (dw *DiagramWidget) GetPrimarySelection() DiagramElement { + return dw.primarySelection +} + // hideAllPads is a work-around for fyne Issue #3906 in which a child's Hoverable interface // (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 @@ -277,16 +294,42 @@ func (dw *DiagramWidget) IsSelected(de DiagramElement) bool { return dw.selection[de.GetDiagramElementID()] != nil } +// MouseDown responds to MouseDown events. It invokes the callback, if present +func (dw *DiagramWidget) MouseDown(event *desktop.MouseEvent) { + log.Print("MouseDown called") + if dw.MouseDownCallback != nil { + dw.MouseDownCallback(event) + } +} + // MouseIn responds to the mouse moving into the diagram. It presently is a noop func (dw *DiagramWidget) MouseIn(event *desktop.MouseEvent) { + log.Print("MouseIn called") + if dw.MouseInCallback != nil { + dw.MouseInCallback(event) + } +} + +// MouseMoved responds to mouse movements in the diagram. It presently is a noop +func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { + log.Print("MouseMoved called") + if dw.MouseMovedCallback != nil { + dw.MouseMovedCallback(event) + } } // MouseOut responds to the mouse leaving the diagram. It presently is a noop func (dw *DiagramWidget) MouseOut() { + if dw.MouseOutCallback != nil { + dw.MouseOutCallback() + } } -// MouseMoved responds to mouse movements in the diagram. It presently is a noop -func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { +// MouseDown responds to MouseDown events. It invokes the callback, if present +func (dw *DiagramWidget) MouseUp(event *desktop.MouseEvent) { + if dw.MouseUpCallback != nil { + dw.MouseUpCallback(event) + } } // removeDependenciesInvolvingLink re-creates the diagram's dependencies, omitting any @@ -397,7 +440,7 @@ func (dw *DiagramWidget) showAllPads() { // StartNewLinkConnectionTransaction starts the process of adding a link, setting up for the source connection func (dw *DiagramWidget) StartNewLinkConnectionTransaction(link DiagramLink) { - dw.connectionTransaction = NewConnectionTransaction(link.getBaseDiagramLink().linkPoints[0], link, nil, fyne.NewPos(0, 0)) + dw.ConnectionTransaction = NewConnectionTransaction(link.getBaseDiagramLink().linkPoints[0], link, nil, fyne.NewPos(0, 0)) dw.showAllPads() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 4d4c74b0..1e5466fe 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -39,7 +39,9 @@ type DiagramLink interface { getBaseDiagramLink() *BaseDiagramLink getLinkPoints() []*LinkPoint GetSourcePad() ConnectionPad + GetSourceHandle() *Handle GetTargetPad() ConnectionPad + GetTargetHandle() *Handle isConnectionAllowed(*LinkPoint, ConnectionPad) bool SetSourcePad(ConnectionPad) SetTargetPad(ConnectionPad) @@ -242,6 +244,10 @@ func (bdl *BaseDiagramLink) GetTargetAnchoredText(key string) *AnchoredText { return bdl.targetAnchoredText[key] } +func (bdl *BaseDiagramLink) GetSourceHandle() *Handle { + return bdl.handles[SOURCE.ToString()] +} + func (bdl *BaseDiagramLink) GetSourcePad() ConnectionPad { return bdl.sourcePad } @@ -250,6 +256,10 @@ func (bdl *BaseDiagramLink) getSourcePosition() fyne.Position { return bdl.linkPoints[0].Position() } +func (bdl *BaseDiagramLink) GetTargetHandle() *Handle { + return bdl.handles[TARGET.ToString()] +} + func (bdl *BaseDiagramLink) GetTargetPad() ConnectionPad { return bdl.targetPad } @@ -275,14 +285,14 @@ func (bdl *BaseDiagramLink) handleDragged(handle *Handle, event *fyne.DragEvent) if linkPoint == nil { return } - connTrans := bdl.diagram.connectionTransaction + connTrans := bdl.diagram.ConnectionTransaction if connTrans == nil { connTrans = NewConnectionTransaction(linkPoint, bdl, pad, linkPoint.Position()) - bdl.diagram.connectionTransaction = connTrans + bdl.diagram.ConnectionTransaction = connTrans // TODO remove this after fyne Issue #3906 has been resolved bdl.diagram.showAllPads() - } else if connTrans.linkPoint != linkPoint { + } else if connTrans.LinkPoint != linkPoint { // The existing transaction is for a different linkPoint return } @@ -293,36 +303,36 @@ func (bdl *BaseDiagramLink) handleDragged(handle *Handle, event *fyne.DragEvent) } func (bdl *BaseDiagramLink) handleDragEnd(handle *Handle) { - connTrans := bdl.diagram.connectionTransaction + connTrans := bdl.diagram.ConnectionTransaction handleKey := bdl.getHandleKey(handle) if connTrans != nil { - if connTrans.pendingPad != nil { + if connTrans.PendingPad != nil { // We have a new pad for connection - if connTrans.initialPad != nil { - bdl.diagram.removeLinkDependency(connTrans.initialPad.GetPadOwner(), bdl, connTrans.initialPad) + if connTrans.InitialPad != nil { + bdl.diagram.removeLinkDependency(connTrans.InitialPad.GetPadOwner(), bdl, connTrans.InitialPad) } - bdl.diagram.addLinkDependency(connTrans.pendingPad.GetPadOwner(), bdl, connTrans.pendingPad) - bdl.pads[handleKey] = connTrans.pendingPad + bdl.diagram.addLinkDependency(connTrans.PendingPad.GetPadOwner(), bdl, connTrans.PendingPad) + bdl.pads[handleKey] = connTrans.PendingPad switch handleKey { case SOURCE.ToString(): - bdl.sourcePad = connTrans.pendingPad + bdl.sourcePad = connTrans.PendingPad case TARGET.ToString(): - bdl.targetPad = connTrans.pendingPad + bdl.targetPad = connTrans.PendingPad } if bdl.diagram.LinkConnectionChangedCallback != nil { - bdl.diagram.LinkConnectionChangedCallback(bdl.typedLink, handleKey, connTrans.initialPad, connTrans.pendingPad) + bdl.diagram.LinkConnectionChangedCallback(bdl.typedLink, handleKey, connTrans.InitialPad, connTrans.PendingPad) } } else { // We revert to the original pad. - bdl.pads[handleKey] = connTrans.initialPad + bdl.pads[handleKey] = connTrans.InitialPad switch handleKey { case SOURCE.ToString(): - bdl.sourcePad = connTrans.initialPad + bdl.sourcePad = connTrans.InitialPad case TARGET.ToString(): - bdl.targetPad = connTrans.initialPad + bdl.targetPad = connTrans.InitialPad } } - bdl.diagram.connectionTransaction = nil + bdl.diagram.ConnectionTransaction = nil bdl.diagram.hideAllPads() bdl.diagram.SelectDiagramElement(bdl) bdl.Refresh() @@ -635,20 +645,21 @@ func (dlr *diagramLinkRenderer) Refresh() { dlr.link.GetDiagram().ForceRepaint() } -type connectionTransaction struct { - linkPoint *LinkPoint - link DiagramLink - initialPad ConnectionPad - initialPosition fyne.Position - pendingPad ConnectionPad +// ConnectionTransaction holds transient data during the creation of a link. It is public for testing purposes only +type ConnectionTransaction struct { + LinkPoint *LinkPoint + Link DiagramLink + InitialPad ConnectionPad + InitialPosition fyne.Position + PendingPad ConnectionPad } -func NewConnectionTransaction(linkPoint *LinkPoint, link DiagramLink, initialPad ConnectionPad, initialPosition fyne.Position) *connectionTransaction { - ct := &connectionTransaction{ - linkPoint: linkPoint, - link: link, - initialPad: initialPad, - initialPosition: initialPosition, +func NewConnectionTransaction(linkPoint *LinkPoint, link DiagramLink, initialPad ConnectionPad, initialPosition fyne.Position) *ConnectionTransaction { + ct := &ConnectionTransaction{ + LinkPoint: linkPoint, + Link: link, + InitialPad: initialPad, + InitialPosition: initialPosition, } return ct } From d45d1fb255a68d1039f70ff35916b0ed25546761 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Mon, 26 Jun 2023 13:11:08 -0400 Subject: [PATCH 30/53] Removed debug log entries --- widget/diagramwidget/diagram.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index f774829a..dd4f6f09 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -2,7 +2,6 @@ package diagramwidget import ( "image/color" - "log" "reflect" "fyne.io/fyne/v2" @@ -296,7 +295,6 @@ func (dw *DiagramWidget) IsSelected(de DiagramElement) bool { // MouseDown responds to MouseDown events. It invokes the callback, if present func (dw *DiagramWidget) MouseDown(event *desktop.MouseEvent) { - log.Print("MouseDown called") if dw.MouseDownCallback != nil { dw.MouseDownCallback(event) } @@ -304,7 +302,6 @@ func (dw *DiagramWidget) MouseDown(event *desktop.MouseEvent) { // MouseIn responds to the mouse moving into the diagram. It presently is a noop func (dw *DiagramWidget) MouseIn(event *desktop.MouseEvent) { - log.Print("MouseIn called") if dw.MouseInCallback != nil { dw.MouseInCallback(event) } @@ -312,7 +309,6 @@ func (dw *DiagramWidget) MouseIn(event *desktop.MouseEvent) { // MouseMoved responds to mouse movements in the diagram. It presently is a noop func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { - log.Print("MouseMoved called") if dw.MouseMovedCallback != nil { dw.MouseMovedCallback(event) } From 9a29be039298c837802eee8ef30c6250b0f8fa1e Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 12 Jul 2023 14:58:51 -0400 Subject: [PATCH 31/53] Removed ForceRepaint workaround; consolidated properties --- cmd/diagramdemo/main.go | 2 +- go.mod | 4 +- go.sum | 26 ++++------ widget/diagramwidget/anchoredtext.go | 1 - widget/diagramwidget/arrowhead.go | 4 +- widget/diagramwidget/connectionpad.go | 5 -- widget/diagramwidget/diagram.go | 68 ++++++++++---------------- widget/diagramwidget/diagramElement.go | 53 ++++++++++++++------ widget/diagramwidget/handle.go | 2 - widget/diagramwidget/link.go | 41 +++++++++------- widget/diagramwidget/linksegment.go | 7 ++- widget/diagramwidget/node.go | 63 +++++------------------- 12 files changed, 117 insertions(+), 159 deletions(-) diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index 72220f14..835b03f8 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -111,7 +111,7 @@ func main() { link1 := diagramwidget.NewDiagramLink(diagramWidget, "Link1") link1.SetSourcePad(node2.GetEdgePad()) link1.SetTargetPad(node1.GetEdgePad()) - link1.LinkColor = color.RGBA{255, 64, 64, 255} + link1.SetForegroundColor(color.RGBA{255, 64, 64, 255}) link1.AddTargetDecoration(diagramwidget.NewArrowhead()) link1.AddTargetDecoration(diagramwidget.NewArrowhead()) link1.AddMidpointDecoration(diagramwidget.NewArrowhead()) diff --git a/go.mod b/go.mod index 1636373f..ad29c390 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module fyne.io/x/fyne go 1.14 require ( - fyne.io/fyne/v2 v2.3.3 + fyne.io/fyne/v2 v2.3.5 github.com/Andrew-M-C/go.jsonvalue v1.3.3 github.com/eclipse/paho.mqtt.golang v1.4.2 github.com/gorilla/websocket v1.5.0 @@ -12,5 +12,5 @@ require ( github.com/stretchr/testify v1.8.2 github.com/twpayne/go-geom v1.5.2 github.com/wagslane/go-password-validator v0.3.0 - golang.org/x/image v0.0.0-20220601225756-64ec528b34cd + golang.org/x/image v0.3.0 ) diff --git a/go.sum b/go.sum index 58738f9a..008ca07c 100644 --- a/go.sum +++ b/go.sum @@ -37,10 +37,10 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fyne.io/fyne/v2 v2.3.3 h1:nBr4yKaCjd3a8MMgn6/MvZ6CzFBN2WQ9HW4ZKzSPXQ0= -fyne.io/fyne/v2 v2.3.3/go.mod h1:KVTDLs6ce4a5vgg2Lj+dh2AHUL7gZYPzXf11y7gu6Qw= -fyne.io/systray v1.10.1-0.20230312215936-7f71b037e260 h1:hNHShALSK9F0n6iJoBNmXs1GW0AzOgkKvp8N4ECK59M= -fyne.io/systray v1.10.1-0.20230312215936-7f71b037e260/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= +fyne.io/fyne/v2 v2.3.5 h1:Q8WOtsms+esLrBKJGdj6P+klu+UXzRq63uPxFSQm4nc= +fyne.io/fyne/v2 v2.3.5/go.mod h1:fbrL+kwOQ6sdVhnURktTHIRIEXwysQSLeejyFyABmNI= +fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b h1:MP1cUnIdF1cxrMhK9iw9H0JP3zopyD1zi84BqU6WTsE= +fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/Andrew-M-C/go.jsonvalue v1.3.3 h1:zPTwpjYwNmqHMyIWyES++xm4k5k1bkdtZoU620pxjEc= github.com/Andrew-M-C/go.jsonvalue v1.3.3/go.mod h1:IwnJ6++SYu/lYAvrJCGGCd9WaT0zTcsD6Li1X9IHcSU= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -59,11 +59,6 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= -github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk= -github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= -github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk= -github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -129,8 +124,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOY github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f h1:cWE//ddvZ7bZAYGtNi3+SPGvUFTeTRUL/TQ9LUnQOP0= -github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= +github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 h1:DvHeDNqK8cxdZ7C6y88pt3uE7euZH7/LluzyfnUfH/Q= +github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16/go.mod h1:zvWM81wAVW6QfVDI6yxfbCuoLnobSYTuMsrXU/u11y8= +github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6 h1:zAAA1U4ykFwqPbcj6YDxvq3F2g0wc/ngPfLJjkR/8zs= +github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -418,11 +415,9 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw= -golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= +golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -488,7 +483,6 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index 08e8e205..e1d9a55f 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -71,7 +71,6 @@ func (at *AnchoredText) Dragged(event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} at.Move(at.Position().Add(delta)) at.Refresh() - at.link.diagramElement.GetDiagram().ForceRepaint() } // GetDisplayedTextBinding returns the binding for the displayed text diff --git a/widget/diagramwidget/arrowhead.go b/widget/diagramwidget/arrowhead.go index 4144f4da..72503bf5 100644 --- a/widget/diagramwidget/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -60,8 +60,8 @@ func NewArrowhead() *Arrowhead { func (a *Arrowhead) CreateRenderer() fyne.WidgetRenderer { ar := arrowheadRenderer{ arrowhead: a, - left: canvas.NewLine(a.link.LinkColor), - right: canvas.NewLine(a.link.LinkColor), + left: canvas.NewLine(a.link.properties.ForegroundColor), + right: canvas.NewLine(a.link.properties.ForegroundColor), } return &ar } diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index 02353092..d98092ce 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -148,7 +148,6 @@ func (pp *PointPad) MouseIn(event *desktop.MouseEvent) { pp.padColor = color.Transparent } pp.Refresh() - pp.padOwner.GetDiagram().ForceRepaint() } // MouseMoved responds to mouse movements within the pointPadSize distance of the center @@ -163,7 +162,6 @@ func (pp *PointPad) MouseOut() { conTrans.PendingPad = nil } pp.Refresh() - pp.padOwner.GetDiagram().ForceRepaint() } // pointPadRenderer @@ -284,7 +282,6 @@ func (rp *RectanglePad) MouseIn(event *desktop.MouseEvent) { rp.padColor = color.Transparent } rp.Refresh() - rp.padOwner.GetDiagram().ForceRepaint() } // MouseMoved responds to mouse movements within the rectangle pad @@ -299,7 +296,6 @@ func (rp *RectanglePad) MouseOut() { conTrans.PendingPad = nil } rp.Refresh() - rp.padOwner.GetDiagram().ForceRepaint() } // rectanglePadRenderer @@ -333,5 +329,4 @@ func (rpr *rectanglePadRenderer) Refresh() { rpr.rect.StrokeColor = rpr.rp.padColor rpr.rect.FillColor = color.Transparent rpr.rect.StrokeWidth = rpr.rp.lineWidth - rpr.rp.connectionPad.padOwner.GetDiagram().ForceRepaint() } diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index dd4f6f09..aa18e5a2 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -5,22 +5,11 @@ import ( "reflect" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) -// ForceRepaint is a workaround for a Fyne bug (Issue #2205) in which moving a canvas object does not -// trigger repainting. When the issue is resolved, this function and all references to it should be -// removed. The DummyBox on the GlobalDiagram should also be removed. -// The conditionals here are required during initialization. -func (dw *DiagramWidget) ForceRepaint() { - if dw != nil && dw.dummyBox != nil { - dw.dummyBox.Refresh() - } -} - // Verify that interfaces are fully implemented var _ fyne.Tappable = (*DiagramWidget)(nil) @@ -41,20 +30,17 @@ type DiagramWidget struct { // ID is expected to be unique across all DiagramWidgets in the application. ID string - // Diagrams may want to use a different theme and variant than the application. The default value is the - // applicaton's theme and variant - DiagramTheme fyne.Theme - ThemeVariant fyne.ThemeVariant - Offset fyne.Position + Offset fyne.Position // DesiredSize specifies the size of the displayed diagram. Defaults to 800 x 600 DesiredSize fyne.Size - Nodes map[string]DiagramNode - Links map[string]DiagramLink - primarySelection DiagramElement - selection map[string]DiagramElement - diagramElementLinkDependencies map[string][]linkPadPair + DefaultDiagramElementProperties DiagramElementProperties + 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 padColor color.Color @@ -82,29 +68,33 @@ type DiagramWidget struct { // an element that is not currently selected is tapped. When true, the new element is added to the selection. // When false, the selection is cleared and the new element is made the only selected element. ElementTappedExtendsSelection bool - - // TODO Remove dummyBox when fyne rendering issue is resolved - dummyBox *canvas.Rectangle } // NewDiagramWidget creates a DiagramWidget. The user-supplied ID can be used to map the diagram // 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, - DiagramTheme: fyne.CurrentApp().Settings().Theme(), - ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), + ID: id, + // DiagramTheme: fyne.CurrentApp().Settings().Theme(), + // ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), DesiredSize: fyne.Size{Width: 800, Height: 600}, Offset: fyne.Position{X: 0, Y: 0}, Nodes: map[string]DiagramNode{}, Links: map[string]DiagramLink{}, - dummyBox: canvas.NewRectangle(color.Transparent), selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, padColor: defaultPadColor, } - dw.dummyBox.SetMinSize(fyne.Size{Height: 50, Width: 50}) - dw.dummyBox.Move(fyne.Position{X: 50, Y: 50}) + appTheme := fyne.CurrentApp().Settings().Theme() + appVariant := fyne.CurrentApp().Settings().ThemeVariant() + dw.DefaultDiagramElementProperties.ForegroundColor = appTheme.Color(theme.ColorNameForeground, appVariant) + dw.DefaultDiagramElementProperties.HandleColor = appTheme.Color(theme.ColorNameForeground, appVariant) + dw.DefaultDiagramElementProperties.BackgroundColor = appTheme.Color(theme.ColorNameBackground, appVariant) + dw.DefaultDiagramElementProperties.TextSize = 12 + dw.DefaultDiagramElementProperties.CaptionTextSize = appTheme.Size(theme.SizeNameCaptionText) + dw.DefaultDiagramElementProperties.Padding = appTheme.Size(theme.SizeNamePadding) + dw.DefaultDiagramElementProperties.StrokeWidth = 1 + dw.DefaultDiagramElementProperties.HandleStrokeWidth = 1 dw.ExtendBaseWidget(dw) @@ -192,7 +182,6 @@ func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.Poi if !dw.IsSelected(de) { dw.addElementToSelection(de) } - dw.ForceRepaint() } // DragEnd is called when the drag comes to an end. It refreshes the widget @@ -203,7 +192,7 @@ func (dw *DiagramWidget) DragEnd() { // GetBackgroundColor returns the background color for the widget from the diagram's theme, which // may be different from the application's theme. func (dw *DiagramWidget) GetBackgroundColor() color.Color { - return dw.DiagramTheme.Color(theme.ColorNameBackground, dw.ThemeVariant) + return dw.DefaultDiagramElementProperties.BackgroundColor } // GetDiagramElement returns the diagram element with the specified ID, whether @@ -220,13 +209,7 @@ func (dw *DiagramWidget) GetDiagramElement(elementID string) DiagramElement { // GetForegroundColor returns the foreground color from the diagram's theme, which may // be different from the application's theme func (dw *DiagramWidget) GetForegroundColor() color.Color { - return dw.DiagramTheme.Color(theme.ColorNameForeground, dw.ThemeVariant) -} - -// GetHoverColor returns the hover color from the diagram's theme, which may may -// be different from the application's theme -func (dw *DiagramWidget) GetHoverColor() color.Color { - return dw.DiagramTheme.Color(theme.ColorNameHover, dw.ThemeVariant) + return dw.DefaultDiagramElementProperties.ForegroundColor } // DiagramNodeDragged moves the indicated node and refreshes any links that may be attached @@ -234,7 +217,6 @@ func (dw *DiagramWidget) GetHoverColor() color.Color { func (dw *DiagramWidget) DiagramNodeDragged(node *BaseDiagramNode, event *fyne.DragEvent) { delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} dw.DisplaceNode(node, delta) - dw.ForceRepaint() } // DisplaceNode moves the indicated node and refreshes any links that may be attached @@ -242,7 +224,6 @@ func (dw *DiagramWidget) DiagramNodeDragged(node *BaseDiagramNode, event *fyne.D func (dw *DiagramWidget) DisplaceNode(node DiagramNode, delta fyne.Position) { node.Move(node.Position().Add(delta)) dw.refreshDependentLinks(node) - dw.ForceRepaint() } // Dragged responds to a drag movement in the background of the diagram. It moves all nodes @@ -384,6 +365,9 @@ func (dw *DiagramWidget) refreshDependentLinks(de DiagramElement) { // RemoveElement removes the element from the diagram. It also removes any linkss to the element func (dw *DiagramWidget) RemoveElement(elementID string) { element := dw.GetDiagramElement(elementID) + if element == nil { + return + } // We make a copy of the dependencies because the array can get modified during the iteration currentDependencies := append([]linkPadPair(nil), dw.diagramElementLinkDependencies[elementID]...) for _, pair := range currentDependencies { @@ -448,7 +432,6 @@ func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { } else { dw.ClearSelection() } - dw.ForceRepaint() } // diagramWidgetRenderer @@ -474,7 +457,6 @@ func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { for _, e := range r.diagramWidget.Links { obj = append(obj, e) } - obj = append(obj, r.diagramWidget.dummyBox) return obj } diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index e2d4917b..ead881aa 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -4,10 +4,20 @@ import ( "image/color" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) +type DiagramElementProperties struct { + ForegroundColor color.Color + BackgroundColor color.Color + HandleColor color.Color + TextSize float32 + CaptionTextSize float32 + Padding float32 + StrokeWidth float32 + HandleStrokeWidth float32 +} + // A DiagramElement is a widget that can be placed directly in a diagram. The most common // elements are Node and Link widgets. type DiagramElement interface { @@ -28,6 +38,8 @@ type DiagramElement interface { GetHandle(string) *Handle // GetHandleColor returns the color for the element's handles GetHandleColor() color.Color + // GetProperties returns the properties of the DiagramElement + GetProperties() DiagramElementProperties // handleDragged responds to drag events handleDragged(handle *Handle, event *fyne.DragEvent) // handleDragEnd responds to the end of a drag @@ -44,17 +56,20 @@ type DiagramElement interface { SetForegroundColor(color.Color) // SetBackgroundColor sets the background color for the widget SetBackgroundColor(color.Color) + // SetProperties sets the foreground, background, and handle colors + SetProperties(DiagramElementProperties) } type diagramElement struct { widget.BaseWidget - diagram *DiagramWidget - foregroundColor color.Color - backgroundColor color.Color - handleColor color.Color - id string - handles map[string]*Handle - pads map[string]ConnectionPad + diagram *DiagramWidget + properties DiagramElementProperties + // foregroundColor color.Color + // backgroundColor color.Color + // handleColor color.Color + id string + handles map[string]*Handle + pads map[string]ConnectionPad } func (de *diagramElement) GetDiagram() *DiagramWidget { @@ -66,7 +81,7 @@ func (de *diagramElement) GetDiagramElementID() string { } func (de *diagramElement) GetBackgroundColor() color.Color { - return de.backgroundColor + return de.properties.BackgroundColor } func (de *diagramElement) GetConnectionPads() map[string]ConnectionPad { @@ -74,7 +89,7 @@ func (de *diagramElement) GetConnectionPads() map[string]ConnectionPad { } func (de *diagramElement) GetForegroundColor() color.Color { - return de.foregroundColor + return de.properties.ForegroundColor } func (de *diagramElement) GetHandle(handleName string) *Handle { @@ -82,7 +97,11 @@ func (de *diagramElement) GetHandle(handleName string) *Handle { } func (de *diagramElement) GetHandleColor() color.Color { - return de.handleColor + return de.properties.HandleColor +} + +func (de *diagramElement) GetProperties() DiagramElementProperties { + return de.properties } func (de *diagramElement) HideHandles() { @@ -95,25 +114,29 @@ func (de *diagramElement) initialize(diagram *DiagramWidget, id string) { de.diagram = diagram de.id = id de.handles = make(map[string]*Handle) - de.handleColor = de.diagram.DiagramTheme.Color(theme.ColorNameForeground, de.diagram.ThemeVariant) + de.properties = de.diagram.DefaultDiagramElementProperties de.pads = make(map[string]ConnectionPad) } func (de *diagramElement) SetBackgroundColor(backgroundColor color.Color) { - de.backgroundColor = backgroundColor + de.properties.BackgroundColor = backgroundColor de.Refresh() } func (de *diagramElement) SetForegroundColor(foregroundColor color.Color) { - de.foregroundColor = foregroundColor + de.properties.ForegroundColor = foregroundColor de.Refresh() } func (de *diagramElement) SetHandleColor(handleColor color.Color) { - de.handleColor = handleColor + de.properties.HandleColor = handleColor de.Refresh() } +func (de *diagramElement) SetProperties(properties DiagramElementProperties) { + de.properties = properties +} + func (de *diagramElement) ShowHandles() { for _, handle := range de.handles { handle.Show() diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go index 1a5a18ca..4ac739c8 100644 --- a/widget/diagramwidget/handle.go +++ b/widget/diagramwidget/handle.go @@ -35,7 +35,6 @@ func (h *Handle) CreateRenderer() fyne.WidgetRenderer { } hr.rect.FillColor = color.Transparent hr.Refresh() - h.de.GetDiagram().ForceRepaint() return hr } @@ -90,5 +89,4 @@ func (hr *handleRenderer) Refresh() { hr.rect.StrokeColor = hr.handle.getStrokeColor() hr.rect.FillColor = color.Transparent hr.rect.StrokeWidth = hr.handle.getStrokeWidth() - hr.handle.de.GetDiagram().ForceRepaint() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 1e5466fe..ebd72572 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -1,7 +1,6 @@ package diagramwidget import ( - "image/color" "log" "math" @@ -65,10 +64,10 @@ type DiagramLink interface { // Link can connect to another Link using this ConnectionPad. type BaseDiagramLink struct { diagramElement - linkPoints []*LinkPoint - linkSegments []*LinkSegment - LinkColor color.Color - strokeWidth float32 + linkPoints []*LinkPoint + linkSegments []*LinkSegment + // LinkColor color.Color + // strokeWidth float32 sourcePad ConnectionPad targetPad ConnectionPad SourceDecorations []Decoration @@ -97,12 +96,12 @@ func InitializeBaseDiagramLink(diagramLink DiagramLink, diagram *DiagramWidget, bdl := diagramLink.getBaseDiagramLink() bdl.linkPoints = []*LinkPoint{} bdl.linkSegments = []*LinkSegment{} - bdl.LinkColor = diagram.GetForegroundColor() - bdl.strokeWidth = 2 bdl.sourceAnchoredText = make(map[string]*AnchoredText) bdl.midpointAnchoredText = make(map[string]*AnchoredText) bdl.targetAnchoredText = make(map[string]*AnchoredText) bdl.diagramElement.initialize(diagram, linkID) + bdl.properties.ForegroundColor = diagram.DefaultDiagramElementProperties.ForegroundColor + bdl.properties.StrokeWidth = diagram.DefaultDiagramElementProperties.StrokeWidth bdl.linkPoints = append(bdl.linkPoints, NewLinkPoint(bdl)) bdl.linkPoints = append(bdl.linkPoints, NewLinkPoint(bdl)) bdl.linkSegments = append(bdl.linkSegments, NewLinkSegment(bdl, bdl.linkPoints[0].Position(), bdl.linkPoints[1].Position())) @@ -312,7 +311,12 @@ func (bdl *BaseDiagramLink) handleDragEnd(handle *Handle) { bdl.diagram.removeLinkDependency(connTrans.InitialPad.GetPadOwner(), bdl, connTrans.InitialPad) } bdl.diagram.addLinkDependency(connTrans.PendingPad.GetPadOwner(), bdl, connTrans.PendingPad) - bdl.pads[handleKey] = connTrans.PendingPad + switch handleKey { + case "source": + bdl.sourcePad = connTrans.PendingPad + case "target": + bdl.targetPad = connTrans.PendingPad + } switch handleKey { case SOURCE.ToString(): bdl.sourcePad = connTrans.PendingPad @@ -324,7 +328,12 @@ func (bdl *BaseDiagramLink) handleDragEnd(handle *Handle) { } } else { // We revert to the original pad. - bdl.pads[handleKey] = connTrans.InitialPad + switch handleKey { + case "source": + bdl.sourcePad = connTrans.InitialPad + case "target": + bdl.targetPad = connTrans.InitialPad + } switch handleKey { case SOURCE.ToString(): bdl.sourcePad = connTrans.InitialPad @@ -545,7 +554,6 @@ func (dlr *diagramLinkRenderer) Refresh() { for i := 0; i < len(dlr.link.linkPoints)-1; i++ { linkSegment := dlr.link.linkSegments[i] linkSegment.SetPoints(dlr.link.linkPoints[i].Position(), dlr.link.linkPoints[i+1].Position()) - // linkSegment.Refresh() } // Have to change the sign of Y since the window inverts the Y axis @@ -611,20 +619,20 @@ func (dlr *diagramLinkRenderer) Refresh() { linkSegment.Refresh() } for _, decoration := range dlr.link.SourceDecorations { - decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.strokeWidth) + decoration.SetStrokeColor(dlr.link.properties.ForegroundColor) + decoration.SetStrokeWidth(dlr.link.properties.StrokeWidth) decoration.SetFillColor(dlr.link.diagram.GetBackgroundColor()) decoration.Refresh() } for _, decoration := range dlr.link.MidpointDecorations { - decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.strokeWidth) + decoration.SetStrokeColor(dlr.link.properties.ForegroundColor) + decoration.SetStrokeWidth(dlr.link.properties.StrokeWidth) decoration.SetFillColor(dlr.link.diagram.GetBackgroundColor()) decoration.Refresh() } for _, decoration := range dlr.link.TargetDecorations { - decoration.SetStrokeColor(dlr.link.LinkColor) - decoration.SetStrokeWidth(dlr.link.strokeWidth) + decoration.SetStrokeColor(dlr.link.properties.ForegroundColor) + decoration.SetStrokeWidth(dlr.link.properties.StrokeWidth) decoration.SetFillColor(dlr.link.diagram.GetBackgroundColor()) decoration.Refresh() } @@ -642,7 +650,6 @@ func (dlr *diagramLinkRenderer) Refresh() { } dlr.link.diagram.refreshDependentLinks(dlr.link) - dlr.link.GetDiagram().ForceRepaint() } // ConnectionTransaction holds transient data during the creation of a link. It is public for testing purposes only diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index faa0547e..244a2a81 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -54,7 +54,7 @@ func (ls *LinkSegment) Tapped(event *fyne.PointEvent) { clickPoint := geom.Coord{float64(event.Position.X), float64(event.Position.Y)} p1 := geom.Coord{float64(ls.p1.X), float64(ls.p1.Y)} p2 := geom.Coord{float64(ls.p2.X), float64(ls.p2.Y)} - if xy.DistanceFromPointToLine(clickPoint, p1, p2) <= float64(ls.link.strokeWidth/2)+1 { + if xy.DistanceFromPointToLine(clickPoint, p1, p2) <= float64(ls.link.properties.StrokeWidth/2)+1 { ls.link.diagram.DiagramElementTapped(ls.link, event) } } @@ -97,7 +97,6 @@ func (lsr *linkSegmentRenderer) Refresh() { lsr.ls.Resize(lsr.MinSize()) lsr.line.Position1 = lsr.ls.p1.AddXY(-widgetPosition.X, -widgetPosition.Y) lsr.line.Position2 = lsr.ls.p2.AddXY(-widgetPosition.X, -widgetPosition.Y) - lsr.line.StrokeColor = lsr.ls.link.LinkColor - lsr.line.StrokeWidth = lsr.ls.link.strokeWidth - lsr.ls.link.GetDiagram().ForceRepaint() + lsr.line.StrokeColor = lsr.ls.link.properties.ForegroundColor + lsr.line.StrokeWidth = lsr.ls.link.properties.StrokeWidth } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 364b9729..87c41b71 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -8,7 +8,6 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" - "fyne.io/fyne/v2/theme" ) type DiagramNode interface { @@ -46,15 +45,6 @@ type BaseDiagramNode struct { // innerObject is the canvas object that should be drawn inside of // the diagram node. innerObject fyne.CanvasObject - // Padding is the distance between the inner object's drawing area - // and the box. - Padding float32 - // BoxStrokeWidth is the stroke width of the box which delineates the - // node. Defaults to 1. - BoxStrokeWidth float32 - // HandleStrokeWidth is the stroke width of the node handle, defaults - // to 3. - HandleStroke float32 // MovedCallback, if present, is invoked when the node is moved MovedCallback func() } @@ -74,9 +64,6 @@ func InitializeBaseDiagramNode(diagramNode DiagramNode, diagram *DiagramWidget, bdn := diagramNode.getBaseDiagramNode() bdn.InnerSize = fyne.Size{Width: defaultWidth, Height: defaultHeight} bdn.innerObject = obj - bdn.Padding = diagram.DiagramTheme.Size(theme.SizeNamePadding) - bdn.BoxStrokeWidth = 1 - bdn.HandleStroke = 3 bdn.diagramElement.initialize(diagram, nodeID) bdn.pads["default"] = NewRectanglePad(bdn) bdn.pads["default"].Hide() @@ -96,7 +83,7 @@ func (bdn *BaseDiagramNode) CreateRenderer() fyne.WidgetRenderer { box: canvas.NewRectangle(bdn.diagram.GetForegroundColor()), } - dnr.box.StrokeWidth = bdn.BoxStrokeWidth + dnr.box.StrokeWidth = bdn.properties.StrokeWidth dnr.box.FillColor = bdn.diagram.GetBackgroundColor() (&dnr).Refresh() @@ -200,7 +187,6 @@ func (bdn *BaseDiagramNode) handleDragged(handle *Handle, event *fyne.DragEvent) bdn.Resize(bdn.Size().Add(sizeChange)) bdn.Move(bdn.Position().Add(positionChange)) bdn.Refresh() - bdn.GetDiagram().ForceRepaint() } func (bdn *BaseDiagramNode) handleDragEnd(handle *Handle) { @@ -209,8 +195,8 @@ func (bdn *BaseDiagramNode) handleDragEnd(handle *Handle) { func (bdn *BaseDiagramNode) innerPos() fyne.Position { return fyne.Position{ - X: float32(bdn.Padding), - Y: float32(bdn.Padding), + X: float32(bdn.properties.Padding), + Y: float32(bdn.properties.Padding), } } @@ -224,44 +210,19 @@ func (bdn *BaseDiagramNode) IsNode() bool { return true } -// func (bdn *BaseDiagramNode) MouseIn(event *desktop.MouseEvent) { -// log.Print("Node MouseIn") -// // conTrans := bdn.GetDiagram().connectionTransaction -// // for _, pad := range bdn.pads { -// // if conTrans != nil && conTrans.link.IsConnectionAllowed(conTrans.linkPoint, pad) { -// // pad.Show() -// // } else { -// // pad.Hide() -// // } -// // } -// // bdn.Refresh() -// // bdn.diagram.ForceRepaint() -// } - -// func (bdn *BaseDiagramNode) MouseOut() { -// log.Print("Node MouseOut") -// // for _, pad := range bdn.pads { -// // pad.Hide() -// // } -// } - -// func (bdn *BaseDiagramNode) MouseMoved(event *desktop.MouseEvent) { -// } - func (bdn *BaseDiagramNode) Move(position fyne.Position) { bdn.BaseWidget.Move(position) if bdn.MovedCallback != nil { bdn.MovedCallback() } bdn.Refresh() - bdn.diagram.ForceRepaint() } func (bdn *BaseDiagramNode) R2Box() r2.Box { inner := bdn.effectiveInnerSize() s := r2.V2( - float64(inner.Width+float32(2*bdn.Padding)), - float64(inner.Height+float32(2*bdn.Padding)), + float64(inner.Width+float32(2*bdn.properties.Padding)), + float64(inner.Height+float32(2*bdn.properties.Padding)), ) return r2.MakeBox(bdn.R2Position(), s) @@ -295,7 +256,7 @@ func (dnr *diagramNodeRenderer) ApplyTheme(size fyne.Size) { } func (dnr *diagramNodeRenderer) BackgroundColor() color.Color { - return dnr.node.diagram.DiagramTheme.Color(theme.ColorNameBackground, dnr.node.diagram.ThemeVariant) + return dnr.node.properties.BackgroundColor } func (dnr *diagramNodeRenderer) Destroy() { @@ -305,8 +266,8 @@ func (dnr *diagramNodeRenderer) MinSize() fyne.Size { // space for the inner widget, plus padding on all sides. inner := dnr.node.effectiveInnerSize() return fyne.Size{ - Width: inner.Width + float32(2*dnr.node.Padding), - Height: inner.Height + float32(2*dnr.node.Padding), + Width: inner.Width + float32(2*dnr.node.properties.Padding), + Height: inner.Height + float32(2*dnr.node.properties.Padding), } } @@ -366,13 +327,13 @@ func (dnr *diagramNodeRenderer) Refresh() { handle.Refresh() } - dnr.box.StrokeWidth = dnr.node.BoxStrokeWidth - dnr.box.FillColor = color.Transparent - dnr.box.StrokeColor = dnr.node.diagram.GetForegroundColor() + dnr.box.StrokeWidth = dnr.node.properties.StrokeWidth + dnr.box.FillColor = dnr.node.properties.BackgroundColor + dnr.box.StrokeColor = dnr.node.properties.ForegroundColor + dnr.box.Refresh() for _, pad := range dnr.node.pads { pad.Refresh() } dnr.node.diagram.refreshDependentLinks(dnr.node) - dnr.node.GetDiagram().ForceRepaint() } From 25ae4e79e6750fe59df4492f4b46a634fa7d2f80 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 13 Jul 2023 15:19:03 -0400 Subject: [PATCH 32/53] Added link mouse callbacks --- widget/diagramwidget/diagram.go | 7 ++++- widget/diagramwidget/link.go | 6 ---- widget/diagramwidget/linksegment.go | 44 ++++++++++++++++------------- widget/diagramwidget/node.go | 2 +- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index aa18e5a2..4804543c 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -49,6 +49,11 @@ type DiagramWidget struct { // LinkConnectionChangedCallback is called when a link connection changes. The string can either be // "source" or "target". The first pad is the old pad, the second one is the new pad LinkConnectionChangedCallback func(DiagramLink, string, ConnectionPad, ConnectionPad) + // LinkSegmentMouseDownSecondaryCallback is called when a secondary button MouseDown occurs in a link segment + LinkSegmentMouseDownSecondaryCallback func(DiagramLink, *desktop.MouseEvent) + // LinkSegmentMouseUpCallback is called when a MouseUp occurs in a link segment and it was either the + // secondary button or it was the primary button but at a different location than the MouseDown + LinkSegmentMouseUpCallback func(DiagramLink, *desktop.MouseEvent) // MouseDownCallback is called when a MouseDown occurs in the diagram MouseDownCallback func(*desktop.MouseEvent) // MouseInCallback is called when a MouseIn occurs in the diagram @@ -175,7 +180,7 @@ func (dw *DiagramWidget) Cursor() desktop.Cursor { } // DiagramElementTapped adds the element to the selection when the element is tapped -func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement, event *fyne.PointEvent) { +func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement) { if !dw.ElementTappedExtendsSelection { dw.ClearSelectionNoCallback() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index ebd72572..72770e67 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -1,7 +1,6 @@ package diagramwidget import ( - "log" "math" "fyne.io/x/fyne/widget/diagramwidget/geometry/r2" @@ -388,18 +387,14 @@ func (bdl *BaseDiagramLink) IsNode() bool { // MouseIn responds to the mouse entering the bounding rectangle of the Link func (bdl *BaseDiagramLink) MouseIn(event *desktop.MouseEvent) { - log.Print("Entered Link") - // TODO implement this } // MouseMoved responds to the mouse moving while within the bounding rectangle of the Link func (bdl *BaseDiagramLink) MouseMoved(event *desktop.MouseEvent) { - // TODO implement this } // MouseOut responds to the mouse leaving the bounding rectangle of the Link func (bdl *BaseDiagramLink) MouseOut() { - log.Printf("Left Link") } // SetSourcePad sets the source pad and adds the link dependency to the diagram @@ -436,7 +431,6 @@ func (bdl *BaseDiagramLink) SetTargetPad(pad ConnectionPad) { // Tapped handles tap events func (bdl *BaseDiagramLink) Tapped(event *fyne.PointEvent) { - log.Print("Link tapped") } // diagramLinkRenderer diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index 244a2a81..f869e0d7 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -11,15 +11,13 @@ import ( "github.com/twpayne/go-geom/xy" ) -var _ fyne.Tappable = (*LinkSegment)(nil) -var _ desktop.Hoverable = (*LinkSegment)(nil) - type LinkSegment struct { widget.BaseWidget link *BaseDiagramLink // p1 and p2 are coordinates in the link's coordinate space - p1 fyne.Position - p2 fyne.Position + p1 fyne.Position + p2 fyne.Position + mouseDownPosition fyne.Position } func NewLinkSegment(link *BaseDiagramLink, p1 fyne.Position, p2 fyne.Position) *LinkSegment { @@ -36,26 +34,34 @@ func NewLinkSegment(link *BaseDiagramLink, p1 fyne.Position, p2 fyne.Position) * func (ls *LinkSegment) CreateRenderer() fyne.WidgetRenderer { lsr := &linkSegmentRenderer{ ls: ls, - line: canvas.NewLine(ls.link.diagram.GetForegroundColor()), + line: canvas.NewLine(ls.link.GetForegroundColor()), } return lsr } -func (ls *LinkSegment) MouseIn(event *desktop.MouseEvent) { -} - -func (ls *LinkSegment) MouseMoved(event *desktop.MouseEvent) { -} - -func (ls *LinkSegment) MouseOut() { +// MouseDown behavior depends upon the mouse event. If it is the primary button, it records the locateion of the +// MouseDown in preparation for a MouseUp at the same location, which will trigger Tapped() behavior. Otherwise, if +// it is the seconday button and a callback is present, it will invoke the callback +func (ls *LinkSegment) MouseDown(event *desktop.MouseEvent) { + if event.Button == desktop.MouseButtonPrimary { + ls.mouseDownPosition = event.Position + } else if event.Button == desktop.MouseButtonSecondary && ls.link.diagram.LinkSegmentMouseDownSecondaryCallback != nil { + ls.link.diagram.LinkSegmentMouseDownSecondaryCallback(ls.link.typedLink, event) + } } -func (ls *LinkSegment) Tapped(event *fyne.PointEvent) { - clickPoint := geom.Coord{float64(event.Position.X), float64(event.Position.Y)} - p1 := geom.Coord{float64(ls.p1.X), float64(ls.p1.Y)} - p2 := geom.Coord{float64(ls.p2.X), float64(ls.p2.Y)} - if xy.DistanceFromPointToLine(clickPoint, p1, p2) <= float64(ls.link.properties.StrokeWidth/2)+1 { - ls.link.diagram.DiagramElementTapped(ls.link, event) +// MouseUp behavior depends on the mouse event. If it is the primary button and it is at the same location as the MouseDown, +// the Tapped() behavior is invoked. Otherwise, if there is a callback present, the callback is invoked. +func (ls *LinkSegment) MouseUp(event *desktop.MouseEvent) { + if event.Button == desktop.MouseButtonPrimary && ls.mouseDownPosition == event.Position { + clickPoint := geom.Coord{float64(event.Position.X), float64(event.Position.Y)} + p1 := geom.Coord{float64(ls.p1.X), float64(ls.p1.Y)} + p2 := geom.Coord{float64(ls.p2.X), float64(ls.p2.Y)} + if xy.DistanceFromPointToLine(clickPoint, p1, p2) <= float64(ls.link.properties.StrokeWidth/2)+1 { + ls.link.diagram.DiagramElementTapped(ls.link) + } + } else if ls.link.diagram.LinkSegmentMouseUpCallback != nil { + ls.link.diagram.LinkSegmentMouseUpCallback(ls.link.typedLink, event) } } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 87c41b71..4a65f9bf 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -243,7 +243,7 @@ func (bdn *BaseDiagramNode) SetInnerObject(obj fyne.CanvasObject) { } func (bdn *BaseDiagramNode) Tapped(event *fyne.PointEvent) { - bdn.diagram.DiagramElementTapped(bdn, event) + bdn.diagram.DiagramElementTapped(bdn) } // diagramNodeRenderer From 820beec4db9f7e57ef64ff96870fb84395efa04f Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 18 Jul 2023 11:52:09 -0400 Subject: [PATCH 33/53] Ignore all executables --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 32c291a4..7abc59b0 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,4 @@ Session.vim tags # Persistent undo [._]*.un~ -cmd/diagramdemo/__debug_bin.exe +*.exe From 03d72df34fa9c7def055ceaba8bf74910a42e147 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 25 Jul 2023 11:39:54 -0400 Subject: [PATCH 34/53] Fixed Refresh() bugs --- go.mod | 2 ++ widget/diagramwidget/arrowhead.go | 9 ++---- widget/diagramwidget/connectionpad.go | 38 ++++++++++++++++++-------- widget/diagramwidget/diagram.go | 9 ++---- widget/diagramwidget/diagramElement.go | 8 ++++++ widget/diagramwidget/handle.go | 1 + widget/diagramwidget/link.go | 4 +-- widget/diagramwidget/linksegment.go | 1 + widget/diagramwidget/node.go | 5 ---- widget/diagramwidget/polygon.go | 5 ---- 10 files changed, 45 insertions(+), 37 deletions(-) diff --git a/go.mod b/go.mod index ad29c390..a65ac5eb 100644 --- a/go.mod +++ b/go.mod @@ -14,3 +14,5 @@ require ( github.com/wagslane/go-password-validator v0.3.0 golang.org/x/image v0.3.0 ) + +replace fyne.io./fyne/v2 v2.3.5 => C:\GoWorkspace\src\github.com\fyne diff --git a/widget/diagramwidget/arrowhead.go b/widget/diagramwidget/arrowhead.go index 72503bf5..9789169f 100644 --- a/widget/diagramwidget/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -188,11 +188,6 @@ func (ar *arrowheadRenderer) Refresh() { ar.right.StrokeWidth = ar.arrowhead.StrokeWidth ar.left.StrokeColor = ar.arrowhead.StrokeColor ar.right.StrokeColor = ar.arrowhead.StrokeColor - if ar.arrowhead.visible { - ar.left.Show() - ar.right.Show() - } else { - ar.left.Hide() - ar.right.Hide() - } + ar.left.Refresh() + ar.right.Refresh() } diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index d98092ce..5976de45 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -13,7 +13,6 @@ import ( const ( pointPadSize float32 = 10 - padLineWidth float32 = 3 ) // ConnectionPad is an interface to a connection shape on a DiagramElement. @@ -25,6 +24,7 @@ type ConnectionPad interface { getConnectionPointInDiagramCoordinates(referencePoint fyne.Position) fyne.Position MouseDown(*desktop.MouseEvent) MouseUp(*desktop.MouseEvent) + SetPadColor(color.Color) } type connectionPad struct { @@ -49,7 +49,7 @@ func (pp *PointPad) MouseDown(event *desktop.MouseEvent) { Dragged: fyne.NewDelta(event.Position.X+padOwnerPosition.X+10, event.Position.Y+padOwnerPosition.Y-10), } // the link point has to be changed before the handle is dragged - connectionTransaction.LinkPoint = connectionTransaction.Link.getLinkPoints()[1] + connectionTransaction.LinkPoint = connectionTransaction.Link.GetLinkPoints()[1] link.GetHandle(TARGET.ToString()).Dragged(pseudoEvent) link.Refresh() link.SetSourcePad(pp) @@ -72,7 +72,7 @@ func (rp *RectanglePad) MouseDown(event *desktop.MouseEvent) { Dragged: fyne.NewDelta(event.Position.X+padOwnerPosition.X, event.Position.Y+padOwnerPosition.Y), } // the link point has to be changed before the handle is dragged - connectionTransaction.LinkPoint = connectionTransaction.Link.getLinkPoints()[1] + connectionTransaction.LinkPoint = connectionTransaction.Link.GetLinkPoints()[1] link.GetHandle(TARGET.ToString()).Dragged(pseudoEvent) link.SetSourcePad(rp) link.GetDiagram().SelectDiagramElement(link) @@ -110,7 +110,7 @@ func NewPointPad(padOwner DiagramElement) *PointPad { pp := &PointPad{} pp.connectionPad.padOwner = padOwner pp.BaseWidget.ExtendBaseWidget(pp) - pp.lineWidth = padLineWidth + pp.lineWidth = padOwner.GetProperties().PadStrokeWidth pp.padColor = color.Transparent return pp } @@ -122,8 +122,8 @@ func (pp *PointPad) CreateRenderer() fyne.WidgetRenderer { l1: canvas.NewLine(pp.padColor), l2: canvas.NewLine(pp.padColor), } - ppr.l1.StrokeWidth = padLineWidth - ppr.l2.StrokeWidth = padLineWidth + ppr.l1.StrokeWidth = pp.padOwner.GetProperties().PadStrokeWidth + ppr.l2.StrokeWidth = pp.padOwner.GetProperties().PadStrokeWidth return ppr } @@ -142,7 +142,7 @@ func (pp *PointPad) getConnectionPointInDiagramCoordinates(referencePoint fyne.P func (pp *PointPad) MouseIn(event *desktop.MouseEvent) { conTrans := pp.padOwner.GetDiagram().ConnectionTransaction if conTrans != nil && conTrans.Link.isConnectionAllowed(conTrans.LinkPoint, pp) { - pp.padColor = pp.padOwner.GetDiagram().padColor + pp.padColor = pp.padOwner.GetProperties().PadColor conTrans.PendingPad = pp } else { pp.padColor = color.Transparent @@ -164,6 +164,11 @@ func (pp *PointPad) MouseOut() { pp.Refresh() } +func (pp *PointPad) SetPadColor(c color.Color) { + pp.padColor = c + pp.Refresh() +} + // pointPadRenderer type pointPadRenderer struct { pp *PointPad @@ -196,9 +201,11 @@ func (ppr *pointPadRenderer) Objects() []fyne.CanvasObject { func (ppr *pointPadRenderer) Refresh() { ppr.l1.StrokeColor = ppr.pp.padColor - ppr.l1.StrokeWidth = padLineWidth + ppr.l1.StrokeWidth = ppr.pp.padOwner.GetProperties().PadStrokeWidth ppr.l2.StrokeColor = ppr.pp.padColor - ppr.l2.StrokeWidth = padLineWidth + ppr.l2.StrokeWidth = ppr.pp.padOwner.GetProperties().PadStrokeWidth + ppr.l1.Refresh() + ppr.l2.Refresh() } /*********************************** @@ -220,7 +227,7 @@ func NewRectanglePad(padOwner DiagramElement) *RectanglePad { rp := &RectanglePad{} rp.connectionPad.padOwner = padOwner rp.BaseWidget.ExtendBaseWidget(rp) - rp.lineWidth = padLineWidth + rp.lineWidth = padOwner.GetProperties().PadStrokeWidth rp.padColor = color.Transparent return rp } @@ -231,7 +238,7 @@ func (rp *RectanglePad) CreateRenderer() fyne.WidgetRenderer { rp: rp, rect: *canvas.NewRectangle(rp.padColor), } - rpr.rect.StrokeWidth = padLineWidth + rpr.rect.StrokeWidth = rp.padOwner.GetProperties().PadStrokeWidth return rpr } @@ -276,8 +283,9 @@ func (rp *RectanglePad) makeBox() r2.Box { func (rp *RectanglePad) MouseIn(event *desktop.MouseEvent) { conTrans := rp.padOwner.GetDiagram().ConnectionTransaction if conTrans != nil && conTrans.Link.isConnectionAllowed(conTrans.LinkPoint, rp) { - rp.padColor = rp.padOwner.GetDiagram().padColor + rp.padColor = rp.padOwner.GetProperties().PadColor conTrans.PendingPad = rp + rp.Show() } else { rp.padColor = color.Transparent } @@ -298,6 +306,11 @@ func (rp *RectanglePad) MouseOut() { rp.Refresh() } +func (rp *RectanglePad) SetPadColor(c color.Color) { + rp.padColor = c + rp.Refresh() +} + // rectanglePadRenderer type rectanglePadRenderer struct { rp *RectanglePad @@ -329,4 +342,5 @@ func (rpr *rectanglePadRenderer) Refresh() { rpr.rect.StrokeColor = rpr.rp.padColor rpr.rect.FillColor = color.Transparent rpr.rect.StrokeWidth = rpr.rp.lineWidth + rpr.rect.Refresh() } diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 4804543c..8554e4d0 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -13,9 +13,6 @@ import ( // Verify that interfaces are fully implemented var _ fyne.Tappable = (*DiagramWidget)(nil) -// Default values -var defaultPadColor = color.RGBA{121, 237, 119, 255} - type linkPadPair struct { link *BaseDiagramLink pad ConnectionPad @@ -43,7 +40,6 @@ type DiagramWidget struct { diagramElementLinkDependencies map[string][]linkPadPair // ConnectionTransaction holds transient data during the creation of a link. It is public for testing purposes ConnectionTransaction *ConnectionTransaction - padColor color.Color // IsConnectionAllowedCallback is called to determine whether a particular connection between a link and a pad is allowed IsConnectionAllowedCallback func(DiagramLink, LinkEnd, ConnectionPad) bool // LinkConnectionChangedCallback is called when a link connection changes. The string can either be @@ -88,7 +84,6 @@ func NewDiagramWidget(id string) *DiagramWidget { Links: map[string]DiagramLink{}, selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, - padColor: defaultPadColor, } appTheme := fyne.CurrentApp().Settings().Theme() appVariant := fyne.CurrentApp().Settings().ThemeVariant() @@ -100,6 +95,8 @@ func NewDiagramWidget(id string) *DiagramWidget { dw.DefaultDiagramElementProperties.Padding = appTheme.Size(theme.SizeNamePadding) dw.DefaultDiagramElementProperties.StrokeWidth = 1 dw.DefaultDiagramElementProperties.HandleStrokeWidth = 1 + dw.DefaultDiagramElementProperties.PadStrokeWidth = 3 + dw.DefaultDiagramElementProperties.PadColor = color.RGBA{121, 237, 119, 255} dw.ExtendBaseWidget(dw) @@ -391,7 +388,7 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { // SelectDiagramElement clears the selection, makes the indicated element the primary selection, and invokes // the PrimaryDiagramElementSelectionChangedCallback func (dw *DiagramWidget) SelectDiagramElement(element DiagramElement) { - dw.ClearSelection() + dw.ClearSelectionNoCallback() dw.addElementToSelection(element) } diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index ead881aa..374c63c2 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -11,10 +11,12 @@ type DiagramElementProperties struct { ForegroundColor color.Color BackgroundColor color.Color HandleColor color.Color + PadColor color.Color TextSize float32 CaptionTextSize float32 Padding float32 StrokeWidth float32 + PadStrokeWidth float32 HandleStrokeWidth float32 } @@ -38,6 +40,8 @@ type DiagramElement interface { GetHandle(string) *Handle // GetHandleColor returns the color for the element's handles GetHandleColor() color.Color + // GetPadColor returns the color for the element's pads + GetPadColor() color.Color // GetProperties returns the properties of the DiagramElement GetProperties() DiagramElementProperties // handleDragged responds to drag events @@ -100,6 +104,10 @@ func (de *diagramElement) GetHandleColor() color.Color { return de.properties.HandleColor } +func (de *diagramElement) GetPadColor() color.Color { + return de.properties.PadColor +} + func (de *diagramElement) GetProperties() DiagramElementProperties { return de.properties } diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go index 4ac739c8..ded570c2 100644 --- a/widget/diagramwidget/handle.go +++ b/widget/diagramwidget/handle.go @@ -89,4 +89,5 @@ func (hr *handleRenderer) Refresh() { hr.rect.StrokeColor = hr.handle.getStrokeColor() hr.rect.FillColor = color.Transparent hr.rect.StrokeWidth = hr.handle.getStrokeWidth() + hr.rect.Refresh() } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 72770e67..c15a707b 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -35,7 +35,7 @@ func (le LinkEnd) ToString() string { type DiagramLink interface { DiagramElement getBaseDiagramLink() *BaseDiagramLink - getLinkPoints() []*LinkPoint + GetLinkPoints() []*LinkPoint GetSourcePad() ConnectionPad GetSourceHandle() *Handle GetTargetPad() ConnectionPad @@ -212,7 +212,7 @@ func (bdl *BaseDiagramLink) getHandleKey(handle *Handle) string { return "" } -func (bdl *BaseDiagramLink) getLinkPoints() []*LinkPoint { +func (bdl *BaseDiagramLink) GetLinkPoints() []*LinkPoint { return bdl.linkPoints } diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index f869e0d7..6d6e5d68 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -105,4 +105,5 @@ func (lsr *linkSegmentRenderer) Refresh() { lsr.line.Position2 = lsr.ls.p2.AddXY(-widgetPosition.X, -widgetPosition.Y) lsr.line.StrokeColor = lsr.ls.link.properties.ForegroundColor lsr.line.StrokeWidth = lsr.ls.link.properties.StrokeWidth + lsr.line.Refresh() } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 4a65f9bf..c85dd804 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -13,9 +13,6 @@ import ( type DiagramNode interface { DiagramElement getBaseDiagramNode() *BaseDiagramNode - // MouseIn(event *desktop.MouseEvent) - // MouseOut() - // MouseMoved(event *desktop.MouseEvent) R2Center() r2.Vec2 } @@ -23,8 +20,6 @@ type DiagramNode interface { var _ DiagramElement = (*BaseDiagramNode)(nil) var _ fyne.Tappable = (*BaseDiagramNode)(nil) -// var _ desktop.Hoverable = (*BaseDiagramNode)(nil) -// var _ desktop.Hoverable = (DiagramNode)(nil) var _ fyne.Widget = (*BaseDiagramNode)(nil) var _ fyne.Widget = (DiagramNode)(nil) diff --git a/widget/diagramwidget/polygon.go b/widget/diagramwidget/polygon.go index 07d37dc1..3a58e090 100644 --- a/widget/diagramwidget/polygon.go +++ b/widget/diagramwidget/polygon.go @@ -274,9 +274,4 @@ func (pr *polygonRenderer) Refresh() { pr.image.Resize(polygonSize) pr.image.Move(offsetVector) pr.image.Refresh() - if pr.polygon.visible { - pr.image.Show() - } else { - pr.image.Hide() - } } From db73a102a80f0c3331ca22da376668c8cafec6b2 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 26 Jul 2023 10:20:43 -0400 Subject: [PATCH 35/53] Removed replace directive --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index a65ac5eb..ad29c390 100644 --- a/go.mod +++ b/go.mod @@ -14,5 +14,3 @@ require ( github.com/wagslane/go-password-validator v0.3.0 golang.org/x/image v0.3.0 ) - -replace fyne.io./fyne/v2 v2.3.5 => C:\GoWorkspace\src\github.com\fyne From c6ee81f02c0558efe8b184dd841fc3915c99cc0e Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 26 Jul 2023 10:47:42 -0400 Subject: [PATCH 36/53] Merge changes --- go.mod | 6 +++--- go.sum | 43 +++++++++++++++++-------------------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index b45677ba..5e9b4840 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ go 1.14 require ( fyne.io/fyne/v2 v2.3.5 - github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 - github.com/twpayne/go-geom v1.5.2 github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 github.com/eclipse/paho.mqtt.golang v1.3.5 github.com/gorilla/websocket v1.4.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/stretchr/testify v1.8.0 + github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 + github.com/stretchr/testify v1.8.1 + github.com/twpayne/go-geom v1.5.2 github.com/wagslane/go-password-validator v0.3.0 golang.org/x/image v0.3.0 ) diff --git a/go.sum b/go.sum index 19d64d64..ce742d9e 100644 --- a/go.sum +++ b/go.sum @@ -41,10 +41,10 @@ fyne.io/fyne/v2 v2.3.5 h1:Q8WOtsms+esLrBKJGdj6P+klu+UXzRq63uPxFSQm4nc= fyne.io/fyne/v2 v2.3.5/go.mod h1:fbrL+kwOQ6sdVhnURktTHIRIEXwysQSLeejyFyABmNI= fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b h1:MP1cUnIdF1cxrMhK9iw9H0JP3zopyD1zi84BqU6WTsE= fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -90,8 +90,8 @@ github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05b github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4= -github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA= +github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= +github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -203,9 +203,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY= github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -309,8 +308,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -318,12 +315,10 @@ github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJV github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= -github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= -github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= @@ -345,11 +340,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= @@ -486,10 +479,9 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -513,7 +505,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -567,18 +558,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -588,10 +580,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From bc24ccd99ee58dd8124311d6a0252437ed12131a Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 28 Jul 2023 11:21:06 -0400 Subject: [PATCH 37/53] Changed diagram drag behavior; fixed dependencies --- go.mod | 2 +- go.sum | 97 ++------------------------------- widget/diagramwidget/diagram.go | 48 +++++++--------- 3 files changed, 26 insertions(+), 121 deletions(-) diff --git a/go.mod b/go.mod index 5e9b4840..711804af 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 github.com/stretchr/testify v1.8.1 - github.com/twpayne/go-geom v1.5.2 + github.com/twpayne/go-geom v1.0.0 github.com/wagslane/go-password-validator v0.3.0 golang.org/x/image v0.3.0 ) diff --git a/go.sum b/go.sum index ce742d9e..81941f60 100644 --- a/go.sum +++ b/go.sum @@ -43,17 +43,9 @@ fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b h1:MP1cUnIdF1cxrMhK9iw9H0J fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -61,35 +53,21 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -100,7 +78,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM= github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -123,13 +100,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 h1:DvHeDNqK8cxdZ7C6y88pt3uE7euZH7/LluzyfnUfH/Q= github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16/go.mod h1:zvWM81wAVW6QfVDI6yxfbCuoLnobSYTuMsrXU/u11y8= github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6 h1:zAAA1U4ykFwqPbcj6YDxvq3F2g0wc/ngPfLJjkR/8zs= github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -194,8 +169,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -228,12 +201,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= @@ -248,12 +217,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -261,7 +227,6 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -269,32 +234,18 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= -github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -306,15 +257,11 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -323,7 +270,6 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA= @@ -344,27 +290,15 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= +github.com/twpayne/go-geom v1.0.0 h1:ARrRnN4+rBX3LZFZQy9NFeXXlgQqVL4OOvAcnLdgy2g= github.com/twpayne/go-geom v1.0.0/go.mod h1:RWsl+e3XSahOul/KH2BHCfF0QxSL4RMnMlFw/TNmET0= -github.com/twpayne/go-geom v1.5.2 h1:LyRfBX2W0LM7XN/bGqX0XxrJ7SZc3XwmxU4aj4kSoxw= -github.com/twpayne/go-geom v1.5.2/go.mod h1:3z6O2sAnGtGCXx4Q+5nPOLCA5e8WI2t3cthdb1P2HH8= -github.com/twpayne/go-gpx v1.2.0/go.mod h1:70xTQn0dGph3dgKIPxfl0K3XMVNpulC70/e383iHouA= github.com/twpayne/go-kml v1.0.0/go.mod h1:LlvLIQSfMqYk2O7Nx8vYAbSLv4K9rjMvLlEdUKWdjq0= -github.com/twpayne/go-kml/v2 v2.0.0/go.mod h1:Y04zvGFNLZQwrWJS8pL5WvNHBibLHYlSN5EjrVUBEqE= github.com/twpayne/go-polyline v1.0.0/go.mod h1:ICh24bcLYBX8CknfvNPKqoTbe+eg+MX1NPyJmSBo7pU= -github.com/twpayne/go-waypoint v0.0.0-20200706203930-b263a7f6e4e8/go.mod h1:qj5pHncxKhu9gxtZEYWypA/z097sxhFlbTyOyt9gcnU= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -392,7 +326,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -439,7 +372,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180824152047-4bcd98cce591/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -472,16 +404,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -515,14 +445,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -538,14 +466,12 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -555,22 +481,14 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -580,9 +498,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -598,7 +515,6 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -747,7 +663,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= @@ -757,15 +672,11 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700= honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 8554e4d0..ff14f5ad 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -75,9 +75,7 @@ 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, - // DiagramTheme: fyne.CurrentApp().Settings().Theme(), - // ThemeVariant: fyne.CurrentApp().Settings().ThemeVariant(), + ID: id, DesiredSize: fyne.Size{Width: 800, Height: 600}, Offset: fyne.Position{X: 0, Y: 0}, Nodes: map[string]DiagramNode{}, @@ -186,11 +184,31 @@ func (dw *DiagramWidget) DiagramElementTapped(de DiagramElement) { } } +// DiagramNodeDragged moves the indicated node and refreshes any links that may be attached +// to it +func (dw *DiagramWidget) DiagramNodeDragged(node *BaseDiagramNode, event *fyne.DragEvent) { + delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} + dw.DisplaceNode(node, delta) +} + +// DisplaceNode moves the indicated node and refreshes any links that may be attached +// to it +func (dw *DiagramWidget) DisplaceNode(node DiagramNode, delta fyne.Position) { + node.Move(node.Position().Add(delta)) + dw.refreshDependentLinks(node) +} + // DragEnd is called when the drag comes to an end. It refreshes the widget func (dw *DiagramWidget) DragEnd() { dw.Refresh() } +// Dragged responds to a drag movement in the background of the diagram. It moves the widget itself. +func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { + dw.Move(dw.Position().Add(event.Dragged)) + dw.Refresh() +} + // GetBackgroundColor returns the background color for the widget from the diagram's theme, which // may be different from the application's theme. func (dw *DiagramWidget) GetBackgroundColor() color.Color { @@ -214,30 +232,6 @@ func (dw *DiagramWidget) GetForegroundColor() color.Color { return dw.DefaultDiagramElementProperties.ForegroundColor } -// DiagramNodeDragged moves the indicated node and refreshes any links that may be attached -// to it -func (dw *DiagramWidget) DiagramNodeDragged(node *BaseDiagramNode, event *fyne.DragEvent) { - delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} - dw.DisplaceNode(node, delta) -} - -// DisplaceNode moves the indicated node and refreshes any links that may be attached -// to it -func (dw *DiagramWidget) DisplaceNode(node DiagramNode, delta fyne.Position) { - node.Move(node.Position().Add(delta)) - dw.refreshDependentLinks(node) -} - -// Dragged responds to a drag movement in the background of the diagram. It moves all nodes -// in the diagram and refreshes all links. -func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { - delta := fyne.Position{X: event.Dragged.DX, Y: event.Dragged.DY} - for _, n := range dw.Nodes { - dw.DisplaceNode(n, delta) - } - dw.Refresh() -} - // GetDiagramElements returns a map of all of the diagram's DiagramElements func (dw *DiagramWidget) GetDiagramElements() map[string]DiagramElement { diagramElements := map[string]DiagramElement{} From f9c6702a194b4f2575ab5bf3657f63c619546dd4 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 28 Jul 2023 12:26:56 -0400 Subject: [PATCH 38/53] Added comments --- widget/diagramwidget/anchoredtext.go | 2 +- widget/diagramwidget/connectionpad.go | 4 ++- widget/diagramwidget/diagram.go | 2 +- widget/diagramwidget/diagramElement.go | 3 +- widget/diagramwidget/geometry/r2/box.go | 38 ++++++++++++----------- widget/diagramwidget/geometry/r2/line.go | 8 +++-- widget/diagramwidget/geometry/r2/vec2.go | 2 +- widget/diagramwidget/handle.go | 11 +++++++ widget/diagramwidget/link.go | 18 +++++++++-- widget/diagramwidget/linkpoint.go | 5 +++ widget/diagramwidget/linksegment.go | 4 +++ widget/diagramwidget/node.go | 12 +++++++ widget/diagramwidget/springforcelayout.go | 3 +- 13 files changed, 83 insertions(+), 29 deletions(-) diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index e1d9a55f..e24d0665 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -101,7 +101,7 @@ func (at *AnchoredText) MouseMoved(event *desktop.MouseEvent) { } -// MousOut is one of the required methods for a mouseable widget +// MouseOut is one of the required methods for a mouseable widget func (at *AnchoredText) MouseOut() { // at.textObject.TextStyle.Bold = false at.Refresh() diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index 5976de45..f18e15f3 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -164,6 +164,7 @@ func (pp *PointPad) MouseOut() { pp.Refresh() } +// SetPadColor sets the color to be used in rendering the pad func (pp *PointPad) SetPadColor(c color.Color) { pp.padColor = c pp.Refresh() @@ -242,7 +243,7 @@ func (rp *RectanglePad) CreateRenderer() fyne.WidgetRenderer { return rpr } -// GetCenterInDiagramCoordinates() returns the center of the pad in the diagram's coordinate system +// GetCenterInDiagramCoordinates returns the center of the pad in the diagram's coordinate system func (rp *RectanglePad) GetCenterInDiagramCoordinates() fyne.Position { box := rp.makeBox() r2Center := box.Center() @@ -306,6 +307,7 @@ func (rp *RectanglePad) MouseOut() { rp.Refresh() } +// SetPadColor sets the color to be used in rendering the pad func (rp *RectanglePad) SetPadColor(c color.Color) { rp.padColor = c rp.Refresh() diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index ff14f5ad..973e6b72 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -298,7 +298,7 @@ func (dw *DiagramWidget) MouseOut() { } } -// MouseDown responds to MouseDown events. It invokes the callback, if present +// MouseUp responds to MouseUp events. It invokes the callback, if present func (dw *DiagramWidget) MouseUp(event *desktop.MouseEvent) { if dw.MouseUpCallback != nil { dw.MouseUpCallback(event) diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index 374c63c2..080f9d0e 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -7,6 +7,7 @@ import ( "fyne.io/fyne/v2/widget" ) +// DiagramElementProperties are the rendering properties of a DiagramElement type DiagramElementProperties struct { ForegroundColor color.Color BackgroundColor color.Color @@ -20,7 +21,7 @@ type DiagramElementProperties struct { HandleStrokeWidth float32 } -// A DiagramElement is a widget that can be placed directly in a diagram. The most common +// DiagramElement is a widget that can be placed directly in a diagram. The most common // elements are Node and Link widgets. type DiagramElement interface { fyne.Widget diff --git a/widget/diagramwidget/geometry/r2/box.go b/widget/diagramwidget/geometry/r2/box.go index c650e9bb..d8cc224b 100644 --- a/widget/diagramwidget/geometry/r2/box.go +++ b/widget/diagramwidget/geometry/r2/box.go @@ -25,6 +25,7 @@ type Box struct { S Vec2 } +// MakeBox creates an r2 Box func MakeBox(a, s Vec2) Box { return Box{ A: a, @@ -32,11 +33,13 @@ func MakeBox(a, s Vec2) Box { } } +// Area returns the area of the Box func (b Box) Area() float64 { return b.S.X * b.S.Y } -// FindPerimeterPointNearestContainedPoint +// FindPerimeterPointNearestContainedPoint returns the perimiter point closest to the contained point. +// If the point is not actually within the Box, it returns a (0,0) vector func (b Box) FindPerimeterPointNearestContainedPoint(containedPoint Vec2) Vec2 { if !b.Contains(containedPoint) { return MakeVec2(0, 0) @@ -56,19 +59,17 @@ func (b Box) FindPerimeterPointNearestContainedPoint(containedPoint Vec2) Vec2 { if leftDistance > topDistance { // top is the closest return MakeVec2(containedPoint.X, top) - } else { - // left is the closest - return MakeVec2(left, containedPoint.Y) } + // left is the closest + return MakeVec2(left, containedPoint.Y) } else { // right is closer if rightDistance > topDistance { // top is the closest return MakeVec2(containedPoint.X, top) - } else { - // right is the closest - return MakeVec2(right, containedPoint.Y) } + // right is the closest + return MakeVec2(right, containedPoint.Y) } } else { // bottom is closer @@ -77,19 +78,17 @@ func (b Box) FindPerimeterPointNearestContainedPoint(containedPoint Vec2) Vec2 { if leftDistance > bottomDistance { // bottom is the closest return MakeVec2(containedPoint.X, bottom) - } else { - // left is the closest - return MakeVec2(left, containedPoint.Y) } + // left is the closest + return MakeVec2(left, containedPoint.Y) } else { // right is closer if rightDistance > bottomDistance { // bottom is the closest return MakeVec2(containedPoint.X, bottom) - } else { - // right is the closest - return MakeVec2(right, containedPoint.Y) } + // right is the closest + return MakeVec2(right, containedPoint.Y) } } } @@ -114,7 +113,7 @@ func (b Box) GetCorner4() Vec2 { return b.A.Add(V2(b.S.X, b.S.Y)) } -// Returns the intersection of the box and the line, and a Boolean indicating +// Intersect returns the intersection of the box and the line, and a Boolean indicating // if the box and vector intersect. If they do not collide, the zero vector is // returned. func (b Box) Intersect(l Line) (Vec2, bool) { @@ -133,7 +132,7 @@ func (b Box) Intersect(l Line) (Vec2, bool) { intersects := []bool{false, false, false, false} intersectPoints := make([]Vec2, 4) - shortest_dist := float64(-1.0) + shortestDist := float64(-1.0) best := -1 for i := range faces { @@ -145,13 +144,13 @@ func (b Box) Intersect(l Line) (Vec2, bool) { intersects[i] = ok intersectPoints[i] = in - if (dists[i] < shortest_dist) || (shortest_dist == float64(-1)) { - shortest_dist = dists[i] + if (dists[i] < shortestDist) || (shortestDist == float64(-1)) { + shortestDist = dists[i] best = i } } - if shortest_dist < 0 { + if shortestDist < 0 { return V2(0, 0), false } @@ -178,6 +177,7 @@ func (b Box) Bottom() Line { return MakeLineFromEndpoints(b.GetCorner3(), b.GetCorner4()) } +// Center returns the center of the Box as an r2 vector func (b Box) Center() Vec2 { return b.A.Add(b.S.Scale(0.5)) } @@ -227,10 +227,12 @@ func BoundingBox(points []Vec2) Box { return MakeBox(V2(xMin, yMax), V2(xMax-xMin, yMin-yMax)) } +// Width returns the width of the Box func (b Box) Width() float64 { return b.Top().Length() } +// Height returns the height of the box func (b Box) Height() float64 { return b.Left().Length() } diff --git a/widget/diagramwidget/geometry/r2/line.go b/widget/diagramwidget/geometry/r2/line.go index 6d0f3c72..c0b4d202 100644 --- a/widget/diagramwidget/geometry/r2/line.go +++ b/widget/diagramwidget/geometry/r2/line.go @@ -16,6 +16,7 @@ type Line struct { S Vec2 } +// MakeLine crates an r2 Line func MakeLine(a, s Vec2) Line { return Line{ A: a, @@ -23,17 +24,19 @@ func MakeLine(a, s Vec2) Line { } } -// Return a line which has endpoints a, b +// MakeLineFromEndpoints returns a line which has endpoints a, b func MakeLineFromEndpoints(a, b Vec2) Line { s := b.Add(a.Scale(-1)) return MakeLine(a, s) } +// Endpoint1 returns the first endpoint of the line func (l Line) Endpoint1() Vec2 { return l.A } +// Endpoint2 returns the second endpoint of the line func (l Line) Endpoint2() Vec2 { return l.A.Add(l.S) } @@ -54,11 +57,12 @@ func samesign(a, b float64) bool { return false } +// Length returns the length of the line func (l Line) Length() float64 { return l.S.Length() } -// This code is transliterated from here: +// IntersectLines This code is transliterated from here: // // https://github.com/JulNadeauCA/libagar/blob/master/gui/primitive.co // diff --git a/widget/diagramwidget/geometry/r2/vec2.go b/widget/diagramwidget/geometry/r2/vec2.go index 909c1971..b50dd74a 100644 --- a/widget/diagramwidget/geometry/r2/vec2.go +++ b/widget/diagramwidget/geometry/r2/vec2.go @@ -39,7 +39,7 @@ func (v Vec2) Add(u Vec2) Vec2 { return Vec2{X: v.X + u.X, Y: v.Y + u.Y} } -// Add angle adds two angles in radians. The inputs are assumed to be in the +// AddAngles adds two angles in radians. The inputs are assumed to be in the // range of +Pi to -Pi radians. The range of the result is +Pi to -Pi radians func AddAngles(a1 float64, a2 float64) float64 { angleSum := a1 + a2 diff --git a/widget/diagramwidget/handle.go b/widget/diagramwidget/handle.go index ded570c2..cf0cde7a 100644 --- a/widget/diagramwidget/handle.go +++ b/widget/diagramwidget/handle.go @@ -13,12 +13,14 @@ var _ fyne.Draggable = (*Handle)(nil) var defaultHandleSize float32 = 10.0 +// Handle is a widget used to manipulate the size or shape of its owning DiagramElement type Handle struct { widget.BaseWidget handleSize float32 de DiagramElement } +// NewHandle creates a handle for the specified DiagramElement func NewHandle(diagramElement DiagramElement) *Handle { handle := &Handle{ de: diagramElement, @@ -28,6 +30,7 @@ func NewHandle(diagramElement DiagramElement) *Handle { return handle } +// CreateRenderer is the required method for the Handle widget func (h *Handle) CreateRenderer() fyne.WidgetRenderer { hr := &handleRenderer{ handle: h, @@ -38,10 +41,13 @@ func (h *Handle) CreateRenderer() fyne.WidgetRenderer { return hr } +// Dragged respondss to drag events, passing them on to the owning DiagramElement. It is the +// DiagramElement that determines what to do as a result of the drag. func (h *Handle) Dragged(event *fyne.DragEvent) { h.de.handleDragged(h, event) } +// DragEnd passes the event on to the owning DiagramElement func (h *Handle) DragEnd() { h.de.handleDragEnd(h) } @@ -54,6 +60,7 @@ func (h *Handle) getStrokeWidth() float32 { return 1.0 } +// Move changes the position of the handle func (h *Handle) Move(position fyne.Position) { delta := fyne.Position{X: -h.handleSize / 2, Y: -h.handleSize / 2} h.BaseWidget.Move(position.Add(delta)) @@ -69,15 +76,18 @@ func (hr *handleRenderer) Destroy() { } +// Layout sets both the handle and its rectangle to the minimum size func (hr *handleRenderer) Layout(size fyne.Size) { hr.rect.Resize(hr.MinSize()) hr.handle.Resize(hr.MinSize()) } +// MinSize returns the minimum size of the Handle widget func (hr *handleRenderer) MinSize() fyne.Size { return fyne.Size{Height: hr.handle.handleSize, Width: hr.handle.handleSize} } +// Objects returns the objects that comprise the Handel func (hr *handleRenderer) Objects() []fyne.CanvasObject { obj := []fyne.CanvasObject{ hr.rect, @@ -85,6 +95,7 @@ func (hr *handleRenderer) Objects() []fyne.CanvasObject { return obj } +// Refresh re-renders the Handle after rendering properties have been changed func (hr *handleRenderer) Refresh() { hr.rect.StrokeColor = hr.handle.getStrokeColor() hr.rect.FillColor = color.Transparent diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index c15a707b..9f0e412f 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -13,15 +13,19 @@ var _ fyne.Tappable = (*BaseDiagramLink)(nil) var _ desktop.Hoverable = (*BaseDiagramLink)(nil) var _ DiagramElement = (*BaseDiagramLink)(nil) +// LinkEnd is an enumeration that identifies the ends of a link type LinkEnd int +// LinkEnds contains the enumerated LinkEnd values var LinkEnds [2]LinkEnd = [2]LinkEnd{SOURCE, TARGET} +// Specify the enumerated values for LinkEnd const ( SOURCE LinkEnd = iota TARGET ) +// ToString returns a string indicating which end is represented by the LinkEnd value func (le LinkEnd) ToString() string { switch le { case SOURCE: @@ -32,6 +36,7 @@ func (le LinkEnd) ToString() string { return "" } +// DiagramLink is a DiagramElement that connects two other DiagramElements type DiagramLink interface { DiagramElement getBaseDiagramLink() *BaseDiagramLink @@ -212,6 +217,7 @@ func (bdl *BaseDiagramLink) getHandleKey(handle *Handle) string { return "" } +// GetLinkPoints returns an array of points that define the vertices of the link including the endpoints func (bdl *BaseDiagramLink) GetLinkPoints() []*LinkPoint { return bdl.linkPoints } @@ -230,22 +236,27 @@ func (bdl *BaseDiagramLink) getMidPosition() fyne.Position { return midPoint } +// GetMidpointAnchoredText returns the midpoint anchored text indexed under the supplied key func (bdl *BaseDiagramLink) GetMidpointAnchoredText(key string) *AnchoredText { return bdl.midpointAnchoredText[key] } +// GetSourceAnchoredText returns the source end anchored text indexed under the skupplied key func (bdl *BaseDiagramLink) GetSourceAnchoredText(key string) *AnchoredText { return bdl.sourceAnchoredText[key] } +// GetTargetAnchoredText returns the target tend anchored text indexed under the supplied key func (bdl *BaseDiagramLink) GetTargetAnchoredText(key string) *AnchoredText { return bdl.targetAnchoredText[key] } +// GetSourceHandle returns the handle associated with the source end func (bdl *BaseDiagramLink) GetSourceHandle() *Handle { return bdl.handles[SOURCE.ToString()] } +// GetSourcePad returns the pad (on another DiagramElement) to which the source end is connected func (bdl *BaseDiagramLink) GetSourcePad() ConnectionPad { return bdl.sourcePad } @@ -254,10 +265,12 @@ func (bdl *BaseDiagramLink) getSourcePosition() fyne.Position { return bdl.linkPoints[0].Position() } +// GetTargetHandle returns the handle associated with the target end func (bdl *BaseDiagramLink) GetTargetHandle() *Handle { return bdl.handles[TARGET.ToString()] } +// GetTargetPad returns the pad (on another DiagramElement) to which the target end is connected func (bdl *BaseDiagramLink) GetTargetPad() ConnectionPad { return bdl.targetPad } @@ -397,7 +410,7 @@ func (bdl *BaseDiagramLink) MouseMoved(event *desktop.MouseEvent) { func (bdl *BaseDiagramLink) MouseOut() { } -// SetSourcePad sets the source pad and adds the link dependency to the diagram +// SetSourcePad sets the source pad (belonging to another DiagramElement) and adds the link dependency to the diagram func (bdl *BaseDiagramLink) SetSourcePad(pad ConnectionPad) { oldPad := bdl.sourcePad if oldPad != pad { @@ -413,7 +426,7 @@ func (bdl *BaseDiagramLink) SetSourcePad(pad ConnectionPad) { } } -// SetTargetPad sets the target pad and adds the link dependency to the diagram +// SetTargetPad sets the target pad (belonging to another DiagramElement) and adds the link dependency to the diagram func (bdl *BaseDiagramLink) SetTargetPad(pad ConnectionPad) { oldPad := bdl.targetPad if oldPad != pad { @@ -655,6 +668,7 @@ type ConnectionTransaction struct { PendingPad ConnectionPad } +// NewConnectionTransaction returns an instance of ConnectionTransaction func NewConnectionTransaction(linkPoint *LinkPoint, link DiagramLink, initialPad ConnectionPad, initialPosition fyne.Position) *ConnectionTransaction { ct := &ConnectionTransaction{ LinkPoint: linkPoint, diff --git a/widget/diagramwidget/linkpoint.go b/widget/diagramwidget/linkpoint.go index d78d1fef..f2a5e7e1 100644 --- a/widget/diagramwidget/linkpoint.go +++ b/widget/diagramwidget/linkpoint.go @@ -11,6 +11,7 @@ type LinkPoint struct { link DiagramLink } +// NewLinkPoint creates an instance of a LinkPoint for a specific DiagramLink func NewLinkPoint(link DiagramLink) *LinkPoint { lp := &LinkPoint{} lp.BaseWidget.ExtendBaseWidget(lp) @@ -18,15 +19,19 @@ func NewLinkPoint(link DiagramLink) *LinkPoint { return lp } +// CreateRenderer creates the renderere for a LinkPoint func (lp *LinkPoint) CreateRenderer() fyne.WidgetRenderer { lpr := &linkPointRenderer{} return lpr } +// GetLink returns the Link to which the LinkPoint belongs func (lp *LinkPoint) GetLink() DiagramLink { return lp.link } +// IsConnectionAllowed returns true if a connection is permitted with the indicated pad. The +// question is passed to the owning link func (lp *LinkPoint) IsConnectionAllowed(connectionPad ConnectionPad) bool { return lp.link.isConnectionAllowed(lp, connectionPad) } diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index 6d6e5d68..1ca39a97 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -11,6 +11,7 @@ import ( "github.com/twpayne/go-geom/xy" ) +// LinkSegment is a widget representing a single line segment belonging to a link type LinkSegment struct { widget.BaseWidget link *BaseDiagramLink @@ -20,6 +21,7 @@ type LinkSegment struct { mouseDownPosition fyne.Position } +// NewLinkSegment returns a LinkSegment belonging to the indicated Link func NewLinkSegment(link *BaseDiagramLink, p1 fyne.Position, p2 fyne.Position) *LinkSegment { ls := &LinkSegment{ link: link, @@ -31,6 +33,7 @@ func NewLinkSegment(link *BaseDiagramLink, p1 fyne.Position, p2 fyne.Position) * return ls } +// CreateRenderer creates the renderer for the LinkSegment func (ls *LinkSegment) CreateRenderer() fyne.WidgetRenderer { lsr := &linkSegmentRenderer{ ls: ls, @@ -65,6 +68,7 @@ func (ls *LinkSegment) MouseUp(event *desktop.MouseEvent) { } } +// SetPoints sets the endpoints of the LinkSegment func (ls *LinkSegment) SetPoints(p1 fyne.Position, p2 fyne.Position) { ls.p1 = p1 ls.p2 = p2 diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index c85dd804..7bd52fb7 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -10,6 +10,7 @@ import ( "fyne.io/fyne/v2/driver/desktop" ) +// DiagramNode is a rectangular DiagramElement typically containing one or more widgets type DiagramNode interface { DiagramElement getBaseDiagramNode() *BaseDiagramNode @@ -72,6 +73,7 @@ func InitializeBaseDiagramNode(diagramNode DiagramNode, diagram *DiagramWidget, diagramNode.Refresh() } +// CreateRenderer creates the renderer for the diagram node func (bdn *BaseDiagramNode) CreateRenderer() fyne.WidgetRenderer { dnr := diagramNodeRenderer{ node: bdn, @@ -86,17 +88,21 @@ func (bdn *BaseDiagramNode) CreateRenderer() fyne.WidgetRenderer { return &dnr } +// Center reurns the position of the center of the node func (bdn *BaseDiagramNode) Center() fyne.Position { return fyne.Position{X: float32(bdn.R2Center().X), Y: float32(bdn.R2Center().Y)} } +// Cursor() returns the desktop default cursor func (bdn *BaseDiagramNode) Cursor() desktop.Cursor { return desktop.DefaultCursor } +// DragEnd is presently a no-op func (bdn *BaseDiagramNode) DragEnd() { } +// Dragged passes the DragEvent to the diagram for processing func (bdn *BaseDiagramNode) Dragged(event *fyne.DragEvent) { bdn.diagram.DiagramNodeDragged(bdn, event) } @@ -205,6 +211,7 @@ func (bdn *BaseDiagramNode) IsNode() bool { return true } +// Move moves the node and invokes the callback if present. func (bdn *BaseDiagramNode) Move(position fyne.Position) { bdn.BaseWidget.Move(position) if bdn.MovedCallback != nil { @@ -213,6 +220,7 @@ func (bdn *BaseDiagramNode) Move(position fyne.Position) { bdn.Refresh() } +// R2Box returns the bounding box in r2 coordinates func (bdn *BaseDiagramNode) R2Box() r2.Box { inner := bdn.effectiveInnerSize() s := r2.V2( @@ -223,20 +231,24 @@ func (bdn *BaseDiagramNode) R2Box() r2.Box { return r2.MakeBox(bdn.R2Position(), s) } +// R2Center returns the r2 vector for the center of the bounding box func (bdn *BaseDiagramNode) R2Center() r2.Vec2 { return bdn.R2Box().Center() } +// R2Position returns the position of the node as an r2 vector func (bdn *BaseDiagramNode) R2Position() r2.Vec2 { return r2.V2(float64(bdn.Position().X), float64(bdn.Position().Y)) } +// SetInnerObject makes the skupplied canvas object the center of the node func (bdn *BaseDiagramNode) SetInnerObject(obj fyne.CanvasObject) { bdn.innerObject = obj bdn.Refresh() bdn.diagram.refreshDependentLinks(bdn) } +// Tapped passes the tapped event on to the Diagram func (bdn *BaseDiagramNode) Tapped(event *fyne.PointEvent) { bdn.diagram.DiagramElementTapped(bdn) } diff --git a/widget/diagramwidget/springforcelayout.go b/widget/diagramwidget/springforcelayout.go index 63b81921..bd6cf94e 100644 --- a/widget/diagramwidget/springforcelayout.go +++ b/widget/diagramwidget/springforcelayout.go @@ -46,9 +46,8 @@ func calculateForce(dw *DiagramWidget, n1, n2 DiagramNode, targetLength float64) if d < targetLength { return v.Scale(1*d*k + k*math.Pow(d, 1/(d+1))) - } else { - return v.Scale(-1*d*k - 0.01*k*math.Pow(d, 2)) } + return v.Scale(-1*d*k - 0.01*k*math.Pow(d, 2)) } else { if d > 1.2*targetLength { return r2.V2(0, 0) From 58634ebe41ca49d9437530e2b64d2d9173d7d0b2 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Fri, 28 Jul 2023 14:46:32 -0400 Subject: [PATCH 39/53] Fixed lint errors --- widget/diagramwidget/geometry/r2/box.go | 30 +++++++++++------------ widget/diagramwidget/linkpoint.go | 4 +-- widget/diagramwidget/node.go | 2 +- widget/diagramwidget/springforcelayout.go | 13 +++++----- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/widget/diagramwidget/geometry/r2/box.go b/widget/diagramwidget/geometry/r2/box.go index d8cc224b..fd7f23db 100644 --- a/widget/diagramwidget/geometry/r2/box.go +++ b/widget/diagramwidget/geometry/r2/box.go @@ -62,15 +62,14 @@ func (b Box) FindPerimeterPointNearestContainedPoint(containedPoint Vec2) Vec2 { } // left is the closest return MakeVec2(left, containedPoint.Y) - } else { - // right is closer - if rightDistance > topDistance { - // top is the closest - return MakeVec2(containedPoint.X, top) - } - // right is the closest - return MakeVec2(right, containedPoint.Y) } + // right is closer + if rightDistance > topDistance { + // top is the closest + return MakeVec2(containedPoint.X, top) + } + // right is the closest + return MakeVec2(right, containedPoint.Y) } else { // bottom is closer if rightDistance > leftDistance { @@ -81,15 +80,14 @@ func (b Box) FindPerimeterPointNearestContainedPoint(containedPoint Vec2) Vec2 { } // left is the closest return MakeVec2(left, containedPoint.Y) - } else { - // right is closer - if rightDistance > bottomDistance { - // bottom is the closest - return MakeVec2(containedPoint.X, bottom) - } - // right is the closest - return MakeVec2(right, containedPoint.Y) } + // right is closer + if rightDistance > bottomDistance { + // bottom is the closest + return MakeVec2(containedPoint.X, bottom) + } + // right is the closest + return MakeVec2(right, containedPoint.Y) } } diff --git a/widget/diagramwidget/linkpoint.go b/widget/diagramwidget/linkpoint.go index f2a5e7e1..904352c1 100644 --- a/widget/diagramwidget/linkpoint.go +++ b/widget/diagramwidget/linkpoint.go @@ -52,11 +52,11 @@ func (lpr *linkPointRenderer) MinSize() fyne.Size { return fyne.NewSize(1, 1) } -func (lp *linkPointRenderer) Objects() []fyne.CanvasObject { +func (lpr *linkPointRenderer) Objects() []fyne.CanvasObject { obj := []fyne.CanvasObject{} return obj } -func (lp *linkPointRenderer) Refresh() { +func (lpr *linkPointRenderer) Refresh() { } diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 7bd52fb7..da6c22cf 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -93,7 +93,7 @@ func (bdn *BaseDiagramNode) Center() fyne.Position { return fyne.Position{X: float32(bdn.R2Center().X), Y: float32(bdn.R2Center().Y)} } -// Cursor() returns the desktop default cursor +// Cursor returns the desktop default cursor func (bdn *BaseDiagramNode) Cursor() desktop.Cursor { return desktop.DefaultCursor } diff --git a/widget/diagramwidget/springforcelayout.go b/widget/diagramwidget/springforcelayout.go index bd6cf94e..5d036e96 100644 --- a/widget/diagramwidget/springforcelayout.go +++ b/widget/diagramwidget/springforcelayout.go @@ -48,14 +48,13 @@ func calculateForce(dw *DiagramWidget, n1, n2 DiagramNode, targetLength float64) return v.Scale(1*d*k + k*math.Pow(d, 1/(d+1))) } return v.Scale(-1*d*k - 0.01*k*math.Pow(d, 2)) - } else { - if d > 1.2*targetLength { - return r2.V2(0, 0) - } - // non-adjacent nodes repel, at a rate falling of with distance. - return v.Scale(50 * math.Sqrt(1/(d+0.1))) - // return r2.V2(0, 0*math.Sqrt(1)) } + if d > 1.2*targetLength { + return r2.V2(0, 0) + } + // non-adjacent nodes repel, at a rate falling of with distance. + return v.Scale(50 * math.Sqrt(1/(d+0.1))) + // return r2.V2(0, 0*math.Sqrt(1)) } // StepForceLayout calculates one step of force directed graph layout, with From 9130c238000ef7025a85d327e76e2a014c828de4 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Sat, 29 Jul 2023 09:26:36 -0400 Subject: [PATCH 40/53] Update box.go --- widget/diagramwidget/geometry/r2/box.go | 29 ++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/widget/diagramwidget/geometry/r2/box.go b/widget/diagramwidget/geometry/r2/box.go index fd7f23db..1acba780 100644 --- a/widget/diagramwidget/geometry/r2/box.go +++ b/widget/diagramwidget/geometry/r2/box.go @@ -70,25 +70,24 @@ func (b Box) FindPerimeterPointNearestContainedPoint(containedPoint Vec2) Vec2 { } // right is the closest return MakeVec2(right, containedPoint.Y) - } else { - // bottom is closer - if rightDistance > leftDistance { - // left is closer - if leftDistance > bottomDistance { - // bottom is the closest - return MakeVec2(containedPoint.X, bottom) - } - // left is the closest - return MakeVec2(left, containedPoint.Y) - } - // right is closer - if rightDistance > bottomDistance { + } + // bottom is closer + if rightDistance > leftDistance { + // left is closer + if leftDistance > bottomDistance { // bottom is the closest return MakeVec2(containedPoint.X, bottom) } - // right is the closest - return MakeVec2(right, containedPoint.Y) + // left is the closest + return MakeVec2(left, containedPoint.Y) + } + // right is closer + if rightDistance > bottomDistance { + // bottom is the closest + return MakeVec2(containedPoint.X, bottom) } + // right is the closest + return MakeVec2(right, containedPoint.Y) } // GetCorner1 returns the top left corner of the box From c2b4174b544f45970a7394f427419f24bd1d6a23 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 15 Aug 2023 13:19:12 -0400 Subject: [PATCH 41/53] Addressed Jacob's feedback. --- README.md | 12 ++++++------ cmd/diagramdemo/main.go | 3 --- widget/diagramwidget/anchoredtext.go | 1 - widget/diagramwidget/connectionpad.go | 3 +-- widget/diagramwidget/geometry/r2/vec2.go | 2 +- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ed562b75..d70fb738 100644 --- a/README.md +++ b/README.md @@ -104,9 +104,9 @@ This collection should be considered a work in progress. When changes are made, serious consideration will be given to backward compatibility, but compatibility is not guaranteed. -The DiagramWidget itself is intended to be incorporated into a Fyne application. It provides a -drawing area within which a diagram can be created. The diagram itself is a collection of -DiagramElement widgets (an interface). There are two types of DiagramElements: DiagramNode widgets and DiagramLink widgets. DiagramNode widgets are thin wrappers around a user-supplied CanvasObject. +The DiagramWidget provides a drawing area within which a diagram can be created. The diagram itself is a collection of +DiagramElement widgets (an interface). There are two types of DiagramElements: DiagramNode widgets and DiagramLink widgets. +DiagramNode widgets are thin wrappers around a user-supplied CanvasObject. Any valid CanvasObject can be used. DiagramLinks are line-based connections between DiagramElements. Note that links can connect to other links as well as nodes. @@ -125,7 +125,7 @@ or resized. Diagram Widget

-**DiagramElement Interface** +### DiagramElement Interface A DiagramElement is the base interface for any element of the diagram being managed by the DiagramWidget. It provides a common interface for DiagramNode and DiagramLink widgets. The DiagramElement @@ -134,12 +134,12 @@ for showing and hiding the handles that are used for graphically manipulating th The specifics of what handles do are different for nodes and links - these are described below in the sections for their respective widgets. -**DiagramNode Widget** +### DiagramNode Widget The DiagramNode widget is a wrapper around a user-supplied CanvasObject. In addition to the user-supplied CanvasObject, the node displays a border and, when selected, handles at the corners and edge mid-points that can be used to manipulate the size of the node. The node can be selected and dragged to a new position with a mouse by clicking in the border area around the canvas object. -**DiagramLink Widget** +### DiagramLink Widget The DiagramLink widget provides a directed line-based connection between two DiagramElements. The link is defined in terms of LinkPoints that are connected by LinkSegments (both of which diff --git a/cmd/diagramdemo/main.go b/cmd/diagramdemo/main.go index 835b03f8..a65c898e 100644 --- a/cmd/diagramdemo/main.go +++ b/cmd/diagramdemo/main.go @@ -17,9 +17,6 @@ var forceticks int = 0 func forceanim(diagramWidget *diagramwidget.DiagramWidget) { - // XXX: very naughty -- accesses shared memory in potentially unsafe - // ways, this almost certainly has race conditions... don't do this! - for { if forceticks > 0 { diagramwidget.StepForceLayout(diagramWidget, 300) diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index e24d0665..2b5992c3 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -40,7 +40,6 @@ func NewAnchoredText(text string) *AnchoredText { at.textEntry = widget.NewEntryWithData(at.displayedTextBinding) at.textEntry.Wrapping = fyne.TextWrapOff at.textEntry.Validator = nil - at.textEntry.Enable() at.ExtendBaseWidget(at) return at } diff --git a/widget/diagramwidget/connectionpad.go b/widget/diagramwidget/connectionpad.go index f18e15f3..dd063a19 100644 --- a/widget/diagramwidget/connectionpad.go +++ b/widget/diagramwidget/connectionpad.go @@ -68,8 +68,7 @@ func (rp *RectanglePad) MouseDown(event *desktop.MouseEvent) { if link.isConnectionAllowed(connectionTransaction.LinkPoint, rp) { padOwnerPosition := rp.padOwner.Position() pseudoEvent := &fyne.DragEvent{ - PointEvent: fyne.PointEvent{}, - Dragged: fyne.NewDelta(event.Position.X+padOwnerPosition.X, event.Position.Y+padOwnerPosition.Y), + Dragged: fyne.NewDelta(event.Position.X+padOwnerPosition.X, event.Position.Y+padOwnerPosition.Y), } // the link point has to be changed before the handle is dragged connectionTransaction.LinkPoint = connectionTransaction.Link.GetLinkPoints()[1] diff --git a/widget/diagramwidget/geometry/r2/vec2.go b/widget/diagramwidget/geometry/r2/vec2.go index b50dd74a..132edbd0 100644 --- a/widget/diagramwidget/geometry/r2/vec2.go +++ b/widget/diagramwidget/geometry/r2/vec2.go @@ -31,7 +31,7 @@ func (v Vec2) Length() float64 { // Dot returns the dot product of vector v and u func (v Vec2) Dot(u Vec2) float64 { - return v.X*u.X + v.Y + u.Y + return v.X*u.X + v.Y*u.Y } // Add returns the sum of vector v and u From 6f11e6db3b7d0b616cccce864967a446442e9b2f Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 6 Sep 2023 14:28:09 -0400 Subject: [PATCH 42/53] Fixed display flickering 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. --- widget/diagramwidget/anchoredtext.go | 10 +- widget/diagramwidget/diagram.go | 145 ++++++++++++++-------- widget/diagramwidget/diagram_test.go | 12 +- widget/diagramwidget/node.go | 11 +- widget/diagramwidget/springforcelayout.go | 10 +- 5 files changed, 116 insertions(+), 72 deletions(-) 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)}) } From 91eebaabab483c5912653e57227b483b5c471914 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 7 Sep 2023 10:21:11 -0400 Subject: [PATCH 43/53] Added scrolling --- widget/diagramwidget/diagram.go | 231 ++++++++++++++++++------- widget/diagramwidget/diagramElement.go | 8 +- 2 files changed, 178 insertions(+), 61 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 211adcee..b0f3a1ff 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -3,15 +3,17 @@ package diagramwidget import ( "container/list" "image/color" + "math" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) // Verify that interfaces are fully implemented -var _ fyne.Tappable = (*DiagramWidget)(nil) +var _ fyne.Tappable = (*drawingArea)(nil) type linkPadPair struct { link *BaseDiagramLink @@ -23,6 +25,8 @@ type linkPadPair struct { // manually (interactively) or programmatically. type DiagramWidget struct { widget.BaseWidget + scrollingContainer *container.Scroll + drawingArea *drawingArea // ID is expected to be unique across all DiagramWidgets in the application. ID string @@ -85,6 +89,9 @@ func NewDiagramWidget(id string) *DiagramWidget { selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, } + dw.drawingArea = newDrawingArea(dw) + dw.drawingArea.Resize(dw.DesiredSize) + dw.scrollingContainer = container.NewScroll(dw.drawingArea) appTheme := fyne.CurrentApp().Settings().Theme() appVariant := fyne.CurrentApp().Settings().ThemeVariant() dw.DefaultDiagramElementProperties.ForegroundColor = appTheme.Color(theme.ColorNameForeground, appVariant) @@ -148,6 +155,57 @@ func (dw *DiagramWidget) addNode(node DiagramNode) { // TODO add logic to rezise diagram if necessary } +// adjustBounds calculates the bounds of the diagram elements and adjusts the size of the drawing area accordingly +// If necessary, it also moves all the diagram elements so that their position coordinates are all positive +func (dw *DiagramWidget) adjustBounds() { + position := dw.drawingArea.Position() + size := dw.drawingArea.Size() + left := position.X + right := position.X + size.Width + top := position.Y + bottom := position.Y + size.Height + for _, diagramElement := range dw.GetDiagramElements() { + position = diagramElement.Position() + size = diagramElement.Size() + left = float32(math.Min(float64(left), float64(position.X))) + right = float32(math.Max(float64(right), float64(position.X+size.Width))) + top = float32(math.Min(float64(top), float64(position.Y))) + bottom = float32(math.Max(float64(bottom), float64(position.Y+size.Height))) + } + moveDelta := fyne.NewPos(0, 0) + moveDeltaChanged := false + if left < dw.drawingArea.Position().X { + moveDelta.X = -left + moveDeltaChanged = true + } + if top < dw.drawingArea.Position().Y { + moveDelta.Y = -top + moveDeltaChanged = true + } + if moveDeltaChanged { + dw.moveDiagramElements(moveDelta) + // moving the elements might have pushed an element beyond the newly computed bounds. + // we have to recompute + position = dw.drawingArea.Position() + size = dw.drawingArea.Size() + left = position.X + right = position.X + size.Width + top = position.Y + bottom = position.Y + size.Height + for _, diagramElement := range dw.GetDiagramElements() { + position = diagramElement.Position() + size = diagramElement.Size() + left = float32(math.Min(float64(left), float64(position.X))) + right = float32(math.Max(float64(right), float64(position.X+size.Width))) + top = float32(math.Min(float64(top), float64(position.Y))) + bottom = float32(math.Max(float64(bottom), float64(position.Y+size.Height))) + } + } + dw.DesiredSize = fyne.NewSize(right-left, bottom-top) + dw.drawingArea.Resize(dw.DesiredSize) + dw.scrollingContainer.Refresh() +} + // CreateRenderer creates the renderer for the diagram func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { r := diagramWidgetRenderer{ @@ -195,22 +253,12 @@ func (dw *DiagramWidget) DiagramNodeDragged(node *BaseDiagramNode, event *fyne.D dw.DisplaceNode(node, delta) } -// DisplaceNode moves the indicated node and refreshes any links that may be attached -// to it +// DisplaceNode moves the indicated node, refreshes any links that may be attached +// to it, and adjusts the bounds of the drawing area func (dw *DiagramWidget) DisplaceNode(node DiagramNode, delta fyne.Position) { node.Move(node.Position().Add(delta)) dw.refreshDependentLinks(node) -} - -// DragEnd is called when the drag comes to an end. It refreshes the widget -func (dw *DiagramWidget) DragEnd() { - dw.Refresh() -} - -// Dragged responds to a drag movement in the background of the diagram. It moves the widget itself. -func (dw *DiagramWidget) Dragged(event *fyne.DragEvent) { - dw.Move(dw.Position().Add(event.Dragged)) - dw.Refresh() + dw.adjustBounds() } // GetBackgroundColor returns the background color for the widget from the diagram's theme, which @@ -316,38 +364,10 @@ func (dw *DiagramWidget) IsSelected(de DiagramElement) bool { return dw.selection[de.GetDiagramElementID()] != nil } -// MouseDown responds to MouseDown events. It invokes the callback, if present -func (dw *DiagramWidget) MouseDown(event *desktop.MouseEvent) { - if dw.MouseDownCallback != nil { - dw.MouseDownCallback(event) - } -} - -// MouseIn responds to the mouse moving into the diagram. It presently is a noop -func (dw *DiagramWidget) MouseIn(event *desktop.MouseEvent) { - if dw.MouseInCallback != nil { - dw.MouseInCallback(event) - } -} - -// MouseMoved responds to mouse movements in the diagram. It presently is a noop -func (dw *DiagramWidget) MouseMoved(event *desktop.MouseEvent) { - if dw.MouseMovedCallback != nil { - dw.MouseMovedCallback(event) - } -} - -// MouseOut responds to the mouse leaving the diagram. It presently is a noop -func (dw *DiagramWidget) MouseOut() { - if dw.MouseOutCallback != nil { - dw.MouseOutCallback() - } -} - -// MouseUp responds to MouseUp events. It invokes the callback, if present -func (dw *DiagramWidget) MouseUp(event *desktop.MouseEvent) { - if dw.MouseUpCallback != nil { - dw.MouseUpCallback(event) +// moveDiagramElements moves all of the diagram elements +func (dw *DiagramWidget) moveDiagramElements(delta fyne.Position) { + for _, diagramElement := range dw.GetDiagramElements() { + diagramElement.Move(diagramElement.Position().Add(delta)) } } @@ -465,16 +485,6 @@ func (dw *DiagramWidget) StartNewLinkConnectionTransaction(link DiagramLink) { dw.showAllPads() } -// Tapped respondss to taps in the diagram background. It removes all diagram elements -// from the selection -func (dw *DiagramWidget) Tapped(event *fyne.PointEvent) { - if dw.OnTappedCallback != nil { - dw.OnTappedCallback(dw, event) - } else { - dw.ClearSelection() - } -} - // diagramWidgetRenderer type diagramWidgetRenderer struct { diagramWidget *DiagramWidget @@ -484,6 +494,7 @@ func (r *diagramWidgetRenderer) Destroy() { } func (r *diagramWidgetRenderer) Layout(size fyne.Size) { + r.diagramWidget.scrollingContainer.Resize(r.diagramWidget.Size()) } func (r *diagramWidgetRenderer) MinSize() fyne.Size { @@ -492,14 +503,116 @@ func (r *diagramWidgetRenderer) MinSize() fyne.Size { func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { obj := make([]fyne.CanvasObject, 0) - for _, n := range r.diagramWidget.GetDiagramElements() { + obj = append(obj, r.diagramWidget.scrollingContainer) + return obj +} + +func (r *diagramWidgetRenderer) Refresh() { + r.diagramWidget.scrollingContainer.Refresh() +} + +type drawingArea struct { + widget.BaseWidget + diagram *DiagramWidget +} + +func newDrawingArea(diagram *DiagramWidget) *drawingArea { + drawingArea := &drawingArea{ + diagram: diagram, + } + drawingArea.ExtendBaseWidget(drawingArea) + return drawingArea +} + +func (da *drawingArea) CreateRenderer() fyne.WidgetRenderer { + dar := &drawingAreaRenderer{} + dar.da = da + return dar +} + +// DragEnd is called when the drag comes to an end. It refreshes the widget +func (da *drawingArea) DragEnd() { + da.Refresh() +} + +// Dragged responds to a drag movement in the background of the diagram. It moves the widget itself. +func (da *drawingArea) Dragged(event *fyne.DragEvent) { + delta := fyne.NewPos(event.Dragged.DX, event.Dragged.DY) + da.diagram.moveDiagramElements(delta) + da.diagram.adjustBounds() +} + +// MouseDown responds to MouseDown events. It invokes the callback, if present +func (da *drawingArea) MouseDown(event *desktop.MouseEvent) { + if da.diagram.MouseDownCallback != nil { + da.diagram.MouseDownCallback(event) + } +} + +// MouseIn responds to the mouse moving into the diagram. It presently is a noop +func (da *drawingArea) MouseIn(event *desktop.MouseEvent) { + if da.diagram.MouseInCallback != nil { + da.diagram.MouseInCallback(event) + } +} + +// MouseMoved responds to mouse movements in the diagram. It presently is a noop +func (da *drawingArea) MouseMoved(event *desktop.MouseEvent) { + if da.diagram.MouseMovedCallback != nil { + da.diagram.MouseMovedCallback(event) + } +} + +// MouseOut responds to the mouse leaving the diagram. It presently is a noop +func (da *drawingArea) MouseOut() { + if da.diagram.MouseOutCallback != nil { + da.diagram.MouseOutCallback() + } +} + +// MouseUp responds to MouseUp events. It invokes the callback, if present +func (da *drawingArea) MouseUp(event *desktop.MouseEvent) { + if da.diagram.MouseUpCallback != nil { + da.diagram.MouseUpCallback(event) + } +} + +// Tapped respondss to taps in the diagram background. It removes all diagram elements +// from the selection +func (da *drawingArea) Tapped(event *fyne.PointEvent) { + if da.diagram.OnTappedCallback != nil { + da.diagram.OnTappedCallback(da.diagram, event) + } else { + da.diagram.ClearSelection() + } +} + +type drawingAreaRenderer struct { + da *drawingArea +} + +func (dar *drawingAreaRenderer) Destroy() { + +} + +func (dar *drawingAreaRenderer) Layout(fyne.Size) { + +} + +func (dar *drawingAreaRenderer) MinSize() fyne.Size { + return dar.da.diagram.DesiredSize +} + +func (dar *drawingAreaRenderer) Objects() []fyne.CanvasObject { + obj := []fyne.CanvasObject{} + for _, n := range dar.da.diagram.GetDiagramElements() { obj = append(obj, n) } return obj } -func (r *diagramWidgetRenderer) Refresh() { - for _, obj := range r.diagramWidget.GetDiagramElements() { +func (dar *drawingAreaRenderer) Refresh() { + for _, obj := range dar.da.diagram.GetDiagramElements() { obj.Refresh() } } diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index 080f9d0e..7bcc64c5 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -55,14 +55,18 @@ type DiagramElement interface { IsLink() bool // IsNode returns true of the diagram element is a node IsNode() bool - // ShowHandles shows the handles on the DiagramElement - ShowHandles() + // Position returns the position of the diagram element + Position() fyne.Position // SetForegroundColor sets the foreground color for the widget SetForegroundColor(color.Color) // SetBackgroundColor sets the background color for the widget SetBackgroundColor(color.Color) // SetProperties sets the foreground, background, and handle colors SetProperties(DiagramElementProperties) + // ShowHandles shows the handles on the DiagramElement + ShowHandles() + // Size returns the size of the diagram element + Size() fyne.Size } type diagramElement struct { From 58a0e64b17c6d93db35268304702034d7373b25e Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 7 Sep 2023 11:11:52 -0400 Subject: [PATCH 44/53] Improved link selection; Added bring to front/send to back --- widget/diagramwidget/diagram.go | 58 +++++++++++++++++++++++--- widget/diagramwidget/diagramElement.go | 1 + widget/diagramwidget/link.go | 14 +++---- widget/diagramwidget/linksegment.go | 2 +- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index b0f3a1ff..1e3038f6 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -114,6 +114,7 @@ func (dw *DiagramWidget) addElementToSelection(de DiagramElement) { if !dw.IsSelected(de) { if dw.primarySelection == nil { dw.primarySelection = de + dw.BringToFront(de.GetDiagramElementID()) if dw.PrimaryDiagramElementSelectionChangedCallback != nil { dw.PrimaryDiagramElementSelectionChangedCallback(de.GetDiagramElementID()) } @@ -126,9 +127,7 @@ func (dw *DiagramWidget) addElementToSelection(de DiagramElement) { // addLink adds a link to the diagram func (dw *DiagramWidget) addLink(link DiagramLink) { dw.DiagramElements.PushBack(link) - // dw.Links[link.GetDiagramElementID()] = link link.Refresh() - // TODO add logic to rezise diagram if necessary } func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link *BaseDiagramLink, pad ConnectionPad) { @@ -150,9 +149,8 @@ func (dw *DiagramWidget) addLinkDependency(diagramElement DiagramElement, link * // addNode adds a node to the diagram func (dw *DiagramWidget) addNode(node DiagramNode) { dw.DiagramElements.PushBack(node) - // dw.Nodes[node.GetDiagramElementID()] = node + dw.adjustBounds() node.Refresh() - // TODO add logic to rezise diagram if necessary } // adjustBounds calculates the bounds of the diagram elements and adjusts the size of the drawing area accordingly @@ -206,6 +204,30 @@ func (dw *DiagramWidget) adjustBounds() { dw.scrollingContainer.Refresh() } +// BringToFront moves the diagram element to the top of the display list (which is the back of the DiagramElements list) +func (dw *DiagramWidget) BringToFront(elementID string) { + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + value := listElement.Value + diagramElement := value.(DiagramElement) + if diagramElement.GetDiagramElementID() == elementID { + dw.DiagramElements.MoveToBack(listElement) + dw.drawingArea.Refresh() + } + } +} + +// BringForward moves the diagram element on top of the next element of the display list +func (dw *DiagramWidget) BringForward(elementID string) { + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + value := listElement.Value + diagramElement := value.(DiagramElement) + if diagramElement.GetDiagramElementID() == elementID { + dw.DiagramElements.MoveAfter(listElement, listElement.Next()) + dw.drawingArea.Refresh() + } + } +} + // CreateRenderer creates the renderer for the diagram func (dw *DiagramWidget) CreateRenderer() fyne.WidgetRenderer { r := diagramWidgetRenderer{ @@ -445,7 +467,7 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { if element.IsLink() { dw.removeDependenciesInvolvingLink(elementID) } - dw.Refresh() + dw.drawingArea.Refresh() } // SelectDiagramElement clears the selection, makes the indicated element the primary selection, and invokes @@ -467,6 +489,30 @@ func (dw *DiagramWidget) SelectDiagramElementNoCallback(id string) { } } +// SendToBack moves the diagram element to the top of the display list (which is the front of the DiagramElements list) +func (dw *DiagramWidget) SendToBack(elementID string) { + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + value := listElement.Value + diagramElement := value.(DiagramElement) + if diagramElement.GetDiagramElementID() == elementID { + dw.DiagramElements.MoveToFront(listElement) + dw.drawingArea.Refresh() + } + } +} + +// SendBackward moves the diagram element on top of the next element of the display list +func (dw *DiagramWidget) SendBackward(elementID string) { + for listElement := dw.DiagramElements.Front(); listElement != nil; listElement = listElement.Next() { + value := listElement.Value + diagramElement := value.(DiagramElement) + if diagramElement.GetDiagramElementID() == elementID { + dw.DiagramElements.MoveBefore(listElement, listElement.Prev()) + dw.drawingArea.Refresh() + } + } +} + // showAllPads is a work-around for fyne Issue #3906 in which a child's Hoverable interface // (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 @@ -508,7 +554,7 @@ func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { } func (r *diagramWidgetRenderer) Refresh() { - r.diagramWidget.scrollingContainer.Refresh() + r.diagramWidget.drawingArea.Refresh() } type drawingArea struct { diff --git a/widget/diagramwidget/diagramElement.go b/widget/diagramwidget/diagramElement.go index 7bcc64c5..7145645c 100644 --- a/widget/diagramwidget/diagramElement.go +++ b/widget/diagramwidget/diagramElement.go @@ -153,5 +153,6 @@ func (de *diagramElement) SetProperties(properties DiagramElementProperties) { func (de *diagramElement) ShowHandles() { for _, handle := range de.handles { handle.Show() + de.Refresh() } } diff --git a/widget/diagramwidget/link.go b/widget/diagramwidget/link.go index 9f0e412f..8439f9ff 100644 --- a/widget/diagramwidget/link.go +++ b/widget/diagramwidget/link.go @@ -9,7 +9,7 @@ import ( "fyne.io/fyne/v2/driver/desktop" ) -var _ fyne.Tappable = (*BaseDiagramLink)(nil) +// var _ fyne.Tappable = (*BaseDiagramLink)(nil) var _ desktop.Hoverable = (*BaseDiagramLink)(nil) var _ DiagramElement = (*BaseDiagramLink)(nil) @@ -68,10 +68,8 @@ type DiagramLink interface { // Link can connect to another Link using this ConnectionPad. type BaseDiagramLink struct { diagramElement - linkPoints []*LinkPoint - linkSegments []*LinkSegment - // LinkColor color.Color - // strokeWidth float32 + linkPoints []*LinkPoint + linkSegments []*LinkSegment sourcePad ConnectionPad targetPad ConnectionPad SourceDecorations []Decoration @@ -442,9 +440,9 @@ func (bdl *BaseDiagramLink) SetTargetPad(pad ConnectionPad) { } } -// Tapped handles tap events -func (bdl *BaseDiagramLink) Tapped(event *fyne.PointEvent) { -} +// // Tapped handles tap events +// func (bdl *BaseDiagramLink) Tapped(event *fyne.PointEvent) { +// } // diagramLinkRenderer type diagramLinkRenderer struct { diff --git a/widget/diagramwidget/linksegment.go b/widget/diagramwidget/linksegment.go index 1ca39a97..32bf75bc 100644 --- a/widget/diagramwidget/linksegment.go +++ b/widget/diagramwidget/linksegment.go @@ -60,7 +60,7 @@ func (ls *LinkSegment) MouseUp(event *desktop.MouseEvent) { clickPoint := geom.Coord{float64(event.Position.X), float64(event.Position.Y)} p1 := geom.Coord{float64(ls.p1.X), float64(ls.p1.Y)} p2 := geom.Coord{float64(ls.p2.X), float64(ls.p2.Y)} - if xy.DistanceFromPointToLine(clickPoint, p1, p2) <= float64(ls.link.properties.StrokeWidth/2)+1 { + if xy.DistanceFromPointToLine(clickPoint, p1, p2) <= float64(ls.link.properties.StrokeWidth/2)+3 { ls.link.diagram.DiagramElementTapped(ls.link) } } else if ls.link.diagram.LinkSegmentMouseUpCallback != nil { From 3be02763a67a5cc4c9920a1da65a072751012649 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Mon, 11 Sep 2023 15:01:04 -0400 Subject: [PATCH 45/53] Fixed review feedback Static check error fixed; Fixed rotating arrow heads --- go.mod | 8 +- go.sum | 111 +++++++++++++++++++-------- go.work.sum | 7 ++ widget/diagramwidget/anchoredtext.go | 2 + widget/diagramwidget/arrowhead.go | 4 + widget/diagramwidget/diagram.go | 64 +++++++-------- widget/diagramwidget/node.go | 3 +- 7 files changed, 129 insertions(+), 70 deletions(-) create mode 100644 go.work.sum diff --git a/go.mod b/go.mod index 711804af..8efa6fe4 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module fyne.io/x/fyne go 1.14 require ( - fyne.io/fyne/v2 v2.3.5 + fyne.io/fyne/v2 v2.4.0 github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 github.com/eclipse/paho.mqtt.golang v1.3.5 github.com/gorilla/websocket v1.4.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 - github.com/stretchr/testify v1.8.1 + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef + github.com/stretchr/testify v1.8.4 github.com/twpayne/go-geom v1.0.0 github.com/wagslane/go-password-validator v0.3.0 - golang.org/x/image v0.3.0 + golang.org/x/image v0.11.0 ) diff --git a/go.sum b/go.sum index 81941f60..8bb24d2f 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -36,15 +38,16 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fyne.io/fyne/v2 v2.3.5 h1:Q8WOtsms+esLrBKJGdj6P+klu+UXzRq63uPxFSQm4nc= -fyne.io/fyne/v2 v2.3.5/go.mod h1:fbrL+kwOQ6sdVhnURktTHIRIEXwysQSLeejyFyABmNI= -fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b h1:MP1cUnIdF1cxrMhK9iw9H0JP3zopyD1zi84BqU6WTsE= -fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= +fyne.io/fyne/v2 v2.4.0 h1:LlyOyHmvkSo9IBm3aY+NVWSBIw+GMnssmyyIMK8F7zM= +fyne.io/fyne/v2 v2.4.0/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro= +fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a h1:6Xf9fP3/mt72NrqlQhJWhQGcNf6GoG9X96NTaXr+K6A= +fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -78,11 +81,12 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM= -github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg= +github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU= @@ -100,16 +104,17 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 h1:DvHeDNqK8cxdZ7C6y88pt3uE7euZH7/LluzyfnUfH/Q= -github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16/go.mod h1:zvWM81wAVW6QfVDI6yxfbCuoLnobSYTuMsrXU/u11y8= -github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6 h1:zAAA1U4ykFwqPbcj6YDxvq3F2g0wc/ngPfLJjkR/8zs= -github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE= +github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 h1:VkKnvzbvHqgEfm351rfr8Uclu5fnwq8HP2ximUzJsBM= +github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8/go.mod h1:h29xCucjNsDcYb7+0rJokxVwYAq+9kQ19WiFuBKkYtc= +github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a h1:VjN8ttdfklC0dnAdKbZqGNESdERUxtE3l8a/4Grgarc= +github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= +github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= +github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8= -github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -166,12 +171,14 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= @@ -204,7 +211,7 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= +github.com/jackmordaunt/icns/v2 v2.2.6/go.mod h1:DqlVnR5iafSphrId7aSD06r3jg0KRC9V6lEBBp504ZQ= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -216,11 +223,12 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= +github.com/lucor/goinfo v0.9.0/go.mod h1:L6m6tN5Rlova5Z83h1ZaKsMP1iiaoZ9vGTNzu5QKOD4= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -241,12 +249,12 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -267,15 +275,17 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA= -github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= -github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -287,8 +297,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= @@ -305,8 +315,9 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU= +github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -327,8 +338,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -343,8 +357,9 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= +golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= +golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -359,8 +374,9 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee h1:/tShaw8UTf0XzI8DOZwQHzC7d6Vi3EtrBnftiZ4vAvU= golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34= +golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -371,6 +387,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -404,14 +422,20 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -436,6 +460,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -473,22 +500,33 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -498,8 +536,11 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -552,11 +593,14 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -625,7 +669,9 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -664,9 +710,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..b5329f50 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,7 @@ +fyne.io/fyne/v2 v2.4.0 h1:LlyOyHmvkSo9IBm3aY+NVWSBIw+GMnssmyyIMK8F7zM= +fyne.io/fyne/v2 v2.4.0/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro= +github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= +github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index 2c8d3f4d..53f53f92 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -4,6 +4,7 @@ import ( "image/color" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" @@ -40,6 +41,7 @@ func NewAnchoredText(text string) *AnchoredText { at.textEntry = widget.NewEntryWithData(at.displayedTextBinding) at.displayedTextBinding.AddListener(at) at.textEntry.Wrapping = fyne.TextWrapOff + at.textEntry.Scroll = container.ScrollNone at.textEntry.Validator = nil at.ExtendBaseWidget(at) return at diff --git a/widget/diagramwidget/arrowhead.go b/widget/diagramwidget/arrowhead.go index 9789169f..46dc9766 100644 --- a/widget/diagramwidget/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -188,6 +188,10 @@ func (ar *arrowheadRenderer) Refresh() { ar.right.StrokeWidth = ar.arrowhead.StrokeWidth ar.left.StrokeColor = ar.arrowhead.StrokeColor ar.right.StrokeColor = ar.arrowhead.StrokeColor + ar.left.Position1 = fyne.Position{X: 0, Y: 0} + ar.left.Position2 = ar.arrowhead.LeftPoint() + ar.right.Position1 = fyne.Position{X: 0, Y: 0} + ar.right.Position2 = ar.arrowhead.RightPoint() ar.left.Refresh() ar.right.Refresh() } diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 1e3038f6..ab6b71b5 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -13,7 +13,7 @@ import ( ) // Verify that interfaces are fully implemented -var _ fyne.Tappable = (*drawingArea)(nil) +var _ fyne.Tappable = (*DrawingArea)(nil) type linkPadPair struct { link *BaseDiagramLink @@ -26,7 +26,7 @@ type linkPadPair struct { type DiagramWidget struct { widget.BaseWidget scrollingContainer *container.Scroll - drawingArea *drawingArea + DrawingArea *DrawingArea // ID is expected to be unique across all DiagramWidgets in the application. ID string @@ -89,9 +89,9 @@ func NewDiagramWidget(id string) *DiagramWidget { selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, } - dw.drawingArea = newDrawingArea(dw) - dw.drawingArea.Resize(dw.DesiredSize) - dw.scrollingContainer = container.NewScroll(dw.drawingArea) + dw.DrawingArea = newDrawingArea(dw) + dw.DrawingArea.Resize(dw.DesiredSize) + dw.scrollingContainer = container.NewScroll(dw.DrawingArea) appTheme := fyne.CurrentApp().Settings().Theme() appVariant := fyne.CurrentApp().Settings().ThemeVariant() dw.DefaultDiagramElementProperties.ForegroundColor = appTheme.Color(theme.ColorNameForeground, appVariant) @@ -156,8 +156,8 @@ func (dw *DiagramWidget) addNode(node DiagramNode) { // adjustBounds calculates the bounds of the diagram elements and adjusts the size of the drawing area accordingly // If necessary, it also moves all the diagram elements so that their position coordinates are all positive func (dw *DiagramWidget) adjustBounds() { - position := dw.drawingArea.Position() - size := dw.drawingArea.Size() + position := dw.DrawingArea.Position() + size := dw.DrawingArea.Size() left := position.X right := position.X + size.Width top := position.Y @@ -172,11 +172,11 @@ func (dw *DiagramWidget) adjustBounds() { } moveDelta := fyne.NewPos(0, 0) moveDeltaChanged := false - if left < dw.drawingArea.Position().X { + if left < dw.DrawingArea.Position().X { moveDelta.X = -left moveDeltaChanged = true } - if top < dw.drawingArea.Position().Y { + if top < dw.DrawingArea.Position().Y { moveDelta.Y = -top moveDeltaChanged = true } @@ -184,8 +184,8 @@ func (dw *DiagramWidget) adjustBounds() { dw.moveDiagramElements(moveDelta) // moving the elements might have pushed an element beyond the newly computed bounds. // we have to recompute - position = dw.drawingArea.Position() - size = dw.drawingArea.Size() + position = dw.DrawingArea.Position() + size = dw.DrawingArea.Size() left = position.X right = position.X + size.Width top = position.Y @@ -200,7 +200,7 @@ func (dw *DiagramWidget) adjustBounds() { } } dw.DesiredSize = fyne.NewSize(right-left, bottom-top) - dw.drawingArea.Resize(dw.DesiredSize) + dw.DrawingArea.Resize(dw.DesiredSize) dw.scrollingContainer.Refresh() } @@ -211,7 +211,7 @@ func (dw *DiagramWidget) BringToFront(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveToBack(listElement) - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } } } @@ -223,7 +223,7 @@ func (dw *DiagramWidget) BringForward(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveAfter(listElement, listElement.Next()) - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } } } @@ -467,7 +467,7 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { if element.IsLink() { dw.removeDependenciesInvolvingLink(elementID) } - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } // SelectDiagramElement clears the selection, makes the indicated element the primary selection, and invokes @@ -496,7 +496,7 @@ func (dw *DiagramWidget) SendToBack(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveToFront(listElement) - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } } } @@ -508,7 +508,7 @@ func (dw *DiagramWidget) SendBackward(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveBefore(listElement, listElement.Prev()) - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } } } @@ -554,70 +554,72 @@ func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { } func (r *diagramWidgetRenderer) Refresh() { - r.diagramWidget.drawingArea.Refresh() + r.diagramWidget.DrawingArea.Refresh() } -type drawingArea struct { +// DrawingArea is the widget representing the page upon which the diagram appears. +type DrawingArea struct { widget.BaseWidget diagram *DiagramWidget } -func newDrawingArea(diagram *DiagramWidget) *drawingArea { - drawingArea := &drawingArea{ +func newDrawingArea(diagram *DiagramWidget) *DrawingArea { + drawingArea := &DrawingArea{ diagram: diagram, } drawingArea.ExtendBaseWidget(drawingArea) return drawingArea } -func (da *drawingArea) CreateRenderer() fyne.WidgetRenderer { +// CreateRenderer is the required function for widgets +func (da *DrawingArea) CreateRenderer() fyne.WidgetRenderer { dar := &drawingAreaRenderer{} dar.da = da return dar } // DragEnd is called when the drag comes to an end. It refreshes the widget -func (da *drawingArea) DragEnd() { +func (da *DrawingArea) DragEnd() { da.Refresh() } // Dragged responds to a drag movement in the background of the diagram. It moves the widget itself. -func (da *drawingArea) Dragged(event *fyne.DragEvent) { +func (da *DrawingArea) Dragged(event *fyne.DragEvent) { delta := fyne.NewPos(event.Dragged.DX, event.Dragged.DY) da.diagram.moveDiagramElements(delta) da.diagram.adjustBounds() } // MouseDown responds to MouseDown events. It invokes the callback, if present -func (da *drawingArea) MouseDown(event *desktop.MouseEvent) { +func (da *DrawingArea) MouseDown(event *desktop.MouseEvent) { if da.diagram.MouseDownCallback != nil { da.diagram.MouseDownCallback(event) } } // MouseIn responds to the mouse moving into the diagram. It presently is a noop -func (da *drawingArea) MouseIn(event *desktop.MouseEvent) { +func (da *DrawingArea) MouseIn(event *desktop.MouseEvent) { if da.diagram.MouseInCallback != nil { da.diagram.MouseInCallback(event) } } // MouseMoved responds to mouse movements in the diagram. It presently is a noop -func (da *drawingArea) MouseMoved(event *desktop.MouseEvent) { +func (da *DrawingArea) MouseMoved(event *desktop.MouseEvent) { if da.diagram.MouseMovedCallback != nil { da.diagram.MouseMovedCallback(event) } } // MouseOut responds to the mouse leaving the diagram. It presently is a noop -func (da *drawingArea) MouseOut() { +func (da *DrawingArea) MouseOut() { if da.diagram.MouseOutCallback != nil { da.diagram.MouseOutCallback() } } // MouseUp responds to MouseUp events. It invokes the callback, if present -func (da *drawingArea) MouseUp(event *desktop.MouseEvent) { +func (da *DrawingArea) MouseUp(event *desktop.MouseEvent) { if da.diagram.MouseUpCallback != nil { da.diagram.MouseUpCallback(event) } @@ -625,7 +627,7 @@ func (da *drawingArea) MouseUp(event *desktop.MouseEvent) { // Tapped respondss to taps in the diagram background. It removes all diagram elements // from the selection -func (da *drawingArea) Tapped(event *fyne.PointEvent) { +func (da *DrawingArea) Tapped(event *fyne.PointEvent) { if da.diagram.OnTappedCallback != nil { da.diagram.OnTappedCallback(da.diagram, event) } else { @@ -634,7 +636,7 @@ func (da *drawingArea) Tapped(event *fyne.PointEvent) { } type drawingAreaRenderer struct { - da *drawingArea + da *DrawingArea } func (dar *drawingAreaRenderer) Destroy() { diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 7a9a6108..27fffa94 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -52,8 +52,7 @@ type BaseDiagramNode struct { // 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) DiagramNode { - var diagramNode DiagramNode - diagramNode = &BaseDiagramNode{} + var diagramNode DiagramNode = &BaseDiagramNode{} InitializeBaseDiagramNode(diagramNode, diagram, obj, nodeID) return diagramNode } From 298d96f89dc386ebf975fac772588e1b545a67a4 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 12 Sep 2023 10:17:20 -0400 Subject: [PATCH 46/53] Revert "Fixed review feedback" This reverts commit 3be02763a67a5cc4c9920a1da65a072751012649. --- widget/diagramwidget/anchoredtext.go | 2 - widget/diagramwidget/arrowhead.go | 4 -- widget/diagramwidget/diagram.go | 64 ++++++++++++++-------------- widget/diagramwidget/node.go | 3 +- 4 files changed, 33 insertions(+), 40 deletions(-) diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index 53f53f92..2c8d3f4d 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -4,7 +4,6 @@ import ( "image/color" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" @@ -41,7 +40,6 @@ func NewAnchoredText(text string) *AnchoredText { at.textEntry = widget.NewEntryWithData(at.displayedTextBinding) at.displayedTextBinding.AddListener(at) at.textEntry.Wrapping = fyne.TextWrapOff - at.textEntry.Scroll = container.ScrollNone at.textEntry.Validator = nil at.ExtendBaseWidget(at) return at diff --git a/widget/diagramwidget/arrowhead.go b/widget/diagramwidget/arrowhead.go index 46dc9766..9789169f 100644 --- a/widget/diagramwidget/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -188,10 +188,6 @@ func (ar *arrowheadRenderer) Refresh() { ar.right.StrokeWidth = ar.arrowhead.StrokeWidth ar.left.StrokeColor = ar.arrowhead.StrokeColor ar.right.StrokeColor = ar.arrowhead.StrokeColor - ar.left.Position1 = fyne.Position{X: 0, Y: 0} - ar.left.Position2 = ar.arrowhead.LeftPoint() - ar.right.Position1 = fyne.Position{X: 0, Y: 0} - ar.right.Position2 = ar.arrowhead.RightPoint() ar.left.Refresh() ar.right.Refresh() } diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index ab6b71b5..1e3038f6 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -13,7 +13,7 @@ import ( ) // Verify that interfaces are fully implemented -var _ fyne.Tappable = (*DrawingArea)(nil) +var _ fyne.Tappable = (*drawingArea)(nil) type linkPadPair struct { link *BaseDiagramLink @@ -26,7 +26,7 @@ type linkPadPair struct { type DiagramWidget struct { widget.BaseWidget scrollingContainer *container.Scroll - DrawingArea *DrawingArea + drawingArea *drawingArea // ID is expected to be unique across all DiagramWidgets in the application. ID string @@ -89,9 +89,9 @@ func NewDiagramWidget(id string) *DiagramWidget { selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, } - dw.DrawingArea = newDrawingArea(dw) - dw.DrawingArea.Resize(dw.DesiredSize) - dw.scrollingContainer = container.NewScroll(dw.DrawingArea) + dw.drawingArea = newDrawingArea(dw) + dw.drawingArea.Resize(dw.DesiredSize) + dw.scrollingContainer = container.NewScroll(dw.drawingArea) appTheme := fyne.CurrentApp().Settings().Theme() appVariant := fyne.CurrentApp().Settings().ThemeVariant() dw.DefaultDiagramElementProperties.ForegroundColor = appTheme.Color(theme.ColorNameForeground, appVariant) @@ -156,8 +156,8 @@ func (dw *DiagramWidget) addNode(node DiagramNode) { // adjustBounds calculates the bounds of the diagram elements and adjusts the size of the drawing area accordingly // If necessary, it also moves all the diagram elements so that their position coordinates are all positive func (dw *DiagramWidget) adjustBounds() { - position := dw.DrawingArea.Position() - size := dw.DrawingArea.Size() + position := dw.drawingArea.Position() + size := dw.drawingArea.Size() left := position.X right := position.X + size.Width top := position.Y @@ -172,11 +172,11 @@ func (dw *DiagramWidget) adjustBounds() { } moveDelta := fyne.NewPos(0, 0) moveDeltaChanged := false - if left < dw.DrawingArea.Position().X { + if left < dw.drawingArea.Position().X { moveDelta.X = -left moveDeltaChanged = true } - if top < dw.DrawingArea.Position().Y { + if top < dw.drawingArea.Position().Y { moveDelta.Y = -top moveDeltaChanged = true } @@ -184,8 +184,8 @@ func (dw *DiagramWidget) adjustBounds() { dw.moveDiagramElements(moveDelta) // moving the elements might have pushed an element beyond the newly computed bounds. // we have to recompute - position = dw.DrawingArea.Position() - size = dw.DrawingArea.Size() + position = dw.drawingArea.Position() + size = dw.drawingArea.Size() left = position.X right = position.X + size.Width top = position.Y @@ -200,7 +200,7 @@ func (dw *DiagramWidget) adjustBounds() { } } dw.DesiredSize = fyne.NewSize(right-left, bottom-top) - dw.DrawingArea.Resize(dw.DesiredSize) + dw.drawingArea.Resize(dw.DesiredSize) dw.scrollingContainer.Refresh() } @@ -211,7 +211,7 @@ func (dw *DiagramWidget) BringToFront(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveToBack(listElement) - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } } } @@ -223,7 +223,7 @@ func (dw *DiagramWidget) BringForward(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveAfter(listElement, listElement.Next()) - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } } } @@ -467,7 +467,7 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { if element.IsLink() { dw.removeDependenciesInvolvingLink(elementID) } - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } // SelectDiagramElement clears the selection, makes the indicated element the primary selection, and invokes @@ -496,7 +496,7 @@ func (dw *DiagramWidget) SendToBack(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveToFront(listElement) - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } } } @@ -508,7 +508,7 @@ func (dw *DiagramWidget) SendBackward(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveBefore(listElement, listElement.Prev()) - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } } } @@ -554,72 +554,70 @@ func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { } func (r *diagramWidgetRenderer) Refresh() { - r.diagramWidget.DrawingArea.Refresh() + r.diagramWidget.drawingArea.Refresh() } -// DrawingArea is the widget representing the page upon which the diagram appears. -type DrawingArea struct { +type drawingArea struct { widget.BaseWidget diagram *DiagramWidget } -func newDrawingArea(diagram *DiagramWidget) *DrawingArea { - drawingArea := &DrawingArea{ +func newDrawingArea(diagram *DiagramWidget) *drawingArea { + drawingArea := &drawingArea{ diagram: diagram, } drawingArea.ExtendBaseWidget(drawingArea) return drawingArea } -// CreateRenderer is the required function for widgets -func (da *DrawingArea) CreateRenderer() fyne.WidgetRenderer { +func (da *drawingArea) CreateRenderer() fyne.WidgetRenderer { dar := &drawingAreaRenderer{} dar.da = da return dar } // DragEnd is called when the drag comes to an end. It refreshes the widget -func (da *DrawingArea) DragEnd() { +func (da *drawingArea) DragEnd() { da.Refresh() } // Dragged responds to a drag movement in the background of the diagram. It moves the widget itself. -func (da *DrawingArea) Dragged(event *fyne.DragEvent) { +func (da *drawingArea) Dragged(event *fyne.DragEvent) { delta := fyne.NewPos(event.Dragged.DX, event.Dragged.DY) da.diagram.moveDiagramElements(delta) da.diagram.adjustBounds() } // MouseDown responds to MouseDown events. It invokes the callback, if present -func (da *DrawingArea) MouseDown(event *desktop.MouseEvent) { +func (da *drawingArea) MouseDown(event *desktop.MouseEvent) { if da.diagram.MouseDownCallback != nil { da.diagram.MouseDownCallback(event) } } // MouseIn responds to the mouse moving into the diagram. It presently is a noop -func (da *DrawingArea) MouseIn(event *desktop.MouseEvent) { +func (da *drawingArea) MouseIn(event *desktop.MouseEvent) { if da.diagram.MouseInCallback != nil { da.diagram.MouseInCallback(event) } } // MouseMoved responds to mouse movements in the diagram. It presently is a noop -func (da *DrawingArea) MouseMoved(event *desktop.MouseEvent) { +func (da *drawingArea) MouseMoved(event *desktop.MouseEvent) { if da.diagram.MouseMovedCallback != nil { da.diagram.MouseMovedCallback(event) } } // MouseOut responds to the mouse leaving the diagram. It presently is a noop -func (da *DrawingArea) MouseOut() { +func (da *drawingArea) MouseOut() { if da.diagram.MouseOutCallback != nil { da.diagram.MouseOutCallback() } } // MouseUp responds to MouseUp events. It invokes the callback, if present -func (da *DrawingArea) MouseUp(event *desktop.MouseEvent) { +func (da *drawingArea) MouseUp(event *desktop.MouseEvent) { if da.diagram.MouseUpCallback != nil { da.diagram.MouseUpCallback(event) } @@ -627,7 +625,7 @@ func (da *DrawingArea) MouseUp(event *desktop.MouseEvent) { // Tapped respondss to taps in the diagram background. It removes all diagram elements // from the selection -func (da *DrawingArea) Tapped(event *fyne.PointEvent) { +func (da *drawingArea) Tapped(event *fyne.PointEvent) { if da.diagram.OnTappedCallback != nil { da.diagram.OnTappedCallback(da.diagram, event) } else { @@ -636,7 +634,7 @@ func (da *DrawingArea) Tapped(event *fyne.PointEvent) { } type drawingAreaRenderer struct { - da *DrawingArea + da *drawingArea } func (dar *drawingAreaRenderer) Destroy() { diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 27fffa94..7a9a6108 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -52,7 +52,8 @@ type BaseDiagramNode struct { // 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) DiagramNode { - var diagramNode DiagramNode = &BaseDiagramNode{} + var diagramNode DiagramNode + diagramNode = &BaseDiagramNode{} InitializeBaseDiagramNode(diagramNode, diagram, obj, nodeID) return diagramNode } From efe64547db8f757c7a8822078e78ba0ef09fb6ce Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 12 Sep 2023 10:43:53 -0400 Subject: [PATCH 47/53] Addressed review feedback Fixed arrowhead rotation. It wasn't a problem with fyne 2.3.5, but would have been a problem with fyne 4.0. Now it won't be a problem. Made DrawingArea public to support application-level testing scenarios that require simulated Drag, Mouse, and Tap events to be sent to the diagram. Made (but commented out) a change to AnchoredText for fyne 2.4.0 so that anchored text does not scroll. This needs to be uncommented for fyne 2.4.0 --- go.mod | 8 +- go.sum | 111 ++++++++------------------- widget/diagramwidget/anchoredtext.go | 3 + widget/diagramwidget/arrowhead.go | 4 + widget/diagramwidget/diagram.go | 67 ++++++++-------- 5 files changed, 80 insertions(+), 113 deletions(-) diff --git a/go.mod b/go.mod index 8efa6fe4..711804af 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module fyne.io/x/fyne go 1.14 require ( - fyne.io/fyne/v2 v2.4.0 + fyne.io/fyne/v2 v2.3.5 github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 github.com/eclipse/paho.mqtt.golang v1.3.5 github.com/gorilla/websocket v1.4.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef - github.com/stretchr/testify v1.8.4 + github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 + github.com/stretchr/testify v1.8.1 github.com/twpayne/go-geom v1.0.0 github.com/wagslane/go-password-validator v0.3.0 - golang.org/x/image v0.11.0 + golang.org/x/image v0.3.0 ) diff --git a/go.sum b/go.sum index 8bb24d2f..81941f60 100644 --- a/go.sum +++ b/go.sum @@ -3,7 +3,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -16,7 +15,6 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -38,16 +36,15 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fyne.io/fyne/v2 v2.4.0 h1:LlyOyHmvkSo9IBm3aY+NVWSBIw+GMnssmyyIMK8F7zM= -fyne.io/fyne/v2 v2.4.0/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro= -fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a h1:6Xf9fP3/mt72NrqlQhJWhQGcNf6GoG9X96NTaXr+K6A= -fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= +fyne.io/fyne/v2 v2.3.5 h1:Q8WOtsms+esLrBKJGdj6P+klu+UXzRq63uPxFSQm4nc= +fyne.io/fyne/v2 v2.3.5/go.mod h1:fbrL+kwOQ6sdVhnURktTHIRIEXwysQSLeejyFyABmNI= +fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b h1:MP1cUnIdF1cxrMhK9iw9H0JP3zopyD1zi84BqU6WTsE= +fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -81,12 +78,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg= -github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= +github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM= +github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU= @@ -104,17 +100,16 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 h1:VkKnvzbvHqgEfm351rfr8Uclu5fnwq8HP2ximUzJsBM= -github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8/go.mod h1:h29xCucjNsDcYb7+0rJokxVwYAq+9kQ19WiFuBKkYtc= -github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a h1:VjN8ttdfklC0dnAdKbZqGNESdERUxtE3l8a/4Grgarc= -github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= -github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= -github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 h1:DvHeDNqK8cxdZ7C6y88pt3uE7euZH7/LluzyfnUfH/Q= +github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16/go.mod h1:zvWM81wAVW6QfVDI6yxfbCuoLnobSYTuMsrXU/u11y8= +github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6 h1:zAAA1U4ykFwqPbcj6YDxvq3F2g0wc/ngPfLJjkR/8zs= +github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8= +github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -171,14 +166,12 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= @@ -211,7 +204,7 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackmordaunt/icns/v2 v2.2.6/go.mod h1:DqlVnR5iafSphrId7aSD06r3jg0KRC9V6lEBBp504ZQ= +github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -223,12 +216,11 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lucor/goinfo v0.9.0/go.mod h1:L6m6tN5Rlova5Z83h1ZaKsMP1iiaoZ9vGTNzu5QKOD4= +github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -249,12 +241,12 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -275,17 +267,15 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= -github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA= +github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY= github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= -github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= -github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -297,8 +287,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= @@ -315,9 +305,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU= -github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -338,11 +327,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -357,9 +343,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= -golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= -golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -374,9 +359,8 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee h1:/tShaw8UTf0XzI8DOZwQHzC7d6Vi3EtrBnftiZ4vAvU= golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= -golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34= -golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -387,8 +371,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -422,20 +404,14 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -460,9 +436,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -500,33 +473,22 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -536,11 +498,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -593,14 +552,11 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -669,9 +625,7 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -710,8 +664,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index 2c8d3f4d..0fb6a913 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -4,6 +4,7 @@ import ( "image/color" "fyne.io/fyne/v2" + // "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" @@ -40,6 +41,8 @@ func NewAnchoredText(text string) *AnchoredText { at.textEntry = widget.NewEntryWithData(at.displayedTextBinding) at.displayedTextBinding.AddListener(at) at.textEntry.Wrapping = fyne.TextWrapOff + // TODO After upgrade to fyne 2.4.0, uncomment the following line and add container as an imported package + // at.textEntry.Scroll = container.ScrollNone at.textEntry.Validator = nil at.ExtendBaseWidget(at) return at diff --git a/widget/diagramwidget/arrowhead.go b/widget/diagramwidget/arrowhead.go index 9789169f..46dc9766 100644 --- a/widget/diagramwidget/arrowhead.go +++ b/widget/diagramwidget/arrowhead.go @@ -188,6 +188,10 @@ func (ar *arrowheadRenderer) Refresh() { ar.right.StrokeWidth = ar.arrowhead.StrokeWidth ar.left.StrokeColor = ar.arrowhead.StrokeColor ar.right.StrokeColor = ar.arrowhead.StrokeColor + ar.left.Position1 = fyne.Position{X: 0, Y: 0} + ar.left.Position2 = ar.arrowhead.LeftPoint() + ar.right.Position1 = fyne.Position{X: 0, Y: 0} + ar.right.Position2 = ar.arrowhead.RightPoint() ar.left.Refresh() ar.right.Refresh() } diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 1e3038f6..3c2d18e2 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -13,7 +13,7 @@ import ( ) // Verify that interfaces are fully implemented -var _ fyne.Tappable = (*drawingArea)(nil) +var _ fyne.Tappable = (*DrawingArea)(nil) type linkPadPair struct { link *BaseDiagramLink @@ -26,7 +26,9 @@ type linkPadPair struct { type DiagramWidget struct { widget.BaseWidget scrollingContainer *container.Scroll - drawingArea *drawingArea + // DrawingArea is public only to support application-level testing scenarios in which simylated + // Drag, Mouse, and Tap events need to be sent to the diagram. It should not be otherwise accessed + DrawingArea *DrawingArea // ID is expected to be unique across all DiagramWidgets in the application. ID string @@ -89,9 +91,9 @@ func NewDiagramWidget(id string) *DiagramWidget { selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, } - dw.drawingArea = newDrawingArea(dw) - dw.drawingArea.Resize(dw.DesiredSize) - dw.scrollingContainer = container.NewScroll(dw.drawingArea) + dw.DrawingArea = newDrawingArea(dw) + dw.DrawingArea.Resize(dw.DesiredSize) + dw.scrollingContainer = container.NewScroll(dw.DrawingArea) appTheme := fyne.CurrentApp().Settings().Theme() appVariant := fyne.CurrentApp().Settings().ThemeVariant() dw.DefaultDiagramElementProperties.ForegroundColor = appTheme.Color(theme.ColorNameForeground, appVariant) @@ -156,8 +158,8 @@ func (dw *DiagramWidget) addNode(node DiagramNode) { // adjustBounds calculates the bounds of the diagram elements and adjusts the size of the drawing area accordingly // If necessary, it also moves all the diagram elements so that their position coordinates are all positive func (dw *DiagramWidget) adjustBounds() { - position := dw.drawingArea.Position() - size := dw.drawingArea.Size() + position := dw.DrawingArea.Position() + size := dw.DrawingArea.Size() left := position.X right := position.X + size.Width top := position.Y @@ -172,11 +174,11 @@ func (dw *DiagramWidget) adjustBounds() { } moveDelta := fyne.NewPos(0, 0) moveDeltaChanged := false - if left < dw.drawingArea.Position().X { + if left < dw.DrawingArea.Position().X { moveDelta.X = -left moveDeltaChanged = true } - if top < dw.drawingArea.Position().Y { + if top < dw.DrawingArea.Position().Y { moveDelta.Y = -top moveDeltaChanged = true } @@ -184,8 +186,8 @@ func (dw *DiagramWidget) adjustBounds() { dw.moveDiagramElements(moveDelta) // moving the elements might have pushed an element beyond the newly computed bounds. // we have to recompute - position = dw.drawingArea.Position() - size = dw.drawingArea.Size() + position = dw.DrawingArea.Position() + size = dw.DrawingArea.Size() left = position.X right = position.X + size.Width top = position.Y @@ -200,7 +202,7 @@ func (dw *DiagramWidget) adjustBounds() { } } dw.DesiredSize = fyne.NewSize(right-left, bottom-top) - dw.drawingArea.Resize(dw.DesiredSize) + dw.DrawingArea.Resize(dw.DesiredSize) dw.scrollingContainer.Refresh() } @@ -211,7 +213,7 @@ func (dw *DiagramWidget) BringToFront(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveToBack(listElement) - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } } } @@ -223,7 +225,7 @@ func (dw *DiagramWidget) BringForward(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveAfter(listElement, listElement.Next()) - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } } } @@ -467,7 +469,7 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { if element.IsLink() { dw.removeDependenciesInvolvingLink(elementID) } - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } // SelectDiagramElement clears the selection, makes the indicated element the primary selection, and invokes @@ -496,7 +498,7 @@ func (dw *DiagramWidget) SendToBack(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveToFront(listElement) - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } } } @@ -508,7 +510,7 @@ func (dw *DiagramWidget) SendBackward(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveBefore(listElement, listElement.Prev()) - dw.drawingArea.Refresh() + dw.DrawingArea.Refresh() } } } @@ -554,70 +556,73 @@ func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { } func (r *diagramWidgetRenderer) Refresh() { - r.diagramWidget.drawingArea.Refresh() + r.diagramWidget.DrawingArea.Refresh() } -type drawingArea struct { +// DrawingArea is the widget in which the diagram is actually rendered. It is public only to support +// application testing scenarios in which simulated Drag, Mouse, and Tap events need to be sent to the diagram. +type DrawingArea struct { widget.BaseWidget diagram *DiagramWidget } -func newDrawingArea(diagram *DiagramWidget) *drawingArea { - drawingArea := &drawingArea{ +func newDrawingArea(diagram *DiagramWidget) *DrawingArea { + drawingArea := &DrawingArea{ diagram: diagram, } drawingArea.ExtendBaseWidget(drawingArea) return drawingArea } -func (da *drawingArea) CreateRenderer() fyne.WidgetRenderer { +// CreateRenderer is the required method for widget extensions +func (da *DrawingArea) CreateRenderer() fyne.WidgetRenderer { dar := &drawingAreaRenderer{} dar.da = da return dar } // DragEnd is called when the drag comes to an end. It refreshes the widget -func (da *drawingArea) DragEnd() { +func (da *DrawingArea) DragEnd() { da.Refresh() } // Dragged responds to a drag movement in the background of the diagram. It moves the widget itself. -func (da *drawingArea) Dragged(event *fyne.DragEvent) { +func (da *DrawingArea) Dragged(event *fyne.DragEvent) { delta := fyne.NewPos(event.Dragged.DX, event.Dragged.DY) da.diagram.moveDiagramElements(delta) da.diagram.adjustBounds() } // MouseDown responds to MouseDown events. It invokes the callback, if present -func (da *drawingArea) MouseDown(event *desktop.MouseEvent) { +func (da *DrawingArea) MouseDown(event *desktop.MouseEvent) { if da.diagram.MouseDownCallback != nil { da.diagram.MouseDownCallback(event) } } // MouseIn responds to the mouse moving into the diagram. It presently is a noop -func (da *drawingArea) MouseIn(event *desktop.MouseEvent) { +func (da *DrawingArea) MouseIn(event *desktop.MouseEvent) { if da.diagram.MouseInCallback != nil { da.diagram.MouseInCallback(event) } } // MouseMoved responds to mouse movements in the diagram. It presently is a noop -func (da *drawingArea) MouseMoved(event *desktop.MouseEvent) { +func (da *DrawingArea) MouseMoved(event *desktop.MouseEvent) { if da.diagram.MouseMovedCallback != nil { da.diagram.MouseMovedCallback(event) } } // MouseOut responds to the mouse leaving the diagram. It presently is a noop -func (da *drawingArea) MouseOut() { +func (da *DrawingArea) MouseOut() { if da.diagram.MouseOutCallback != nil { da.diagram.MouseOutCallback() } } // MouseUp responds to MouseUp events. It invokes the callback, if present -func (da *drawingArea) MouseUp(event *desktop.MouseEvent) { +func (da *DrawingArea) MouseUp(event *desktop.MouseEvent) { if da.diagram.MouseUpCallback != nil { da.diagram.MouseUpCallback(event) } @@ -625,7 +630,7 @@ func (da *drawingArea) MouseUp(event *desktop.MouseEvent) { // Tapped respondss to taps in the diagram background. It removes all diagram elements // from the selection -func (da *drawingArea) Tapped(event *fyne.PointEvent) { +func (da *DrawingArea) Tapped(event *fyne.PointEvent) { if da.diagram.OnTappedCallback != nil { da.diagram.OnTappedCallback(da.diagram, event) } else { @@ -634,7 +639,7 @@ func (da *drawingArea) Tapped(event *fyne.PointEvent) { } type drawingAreaRenderer struct { - da *drawingArea + da *DrawingArea } func (dar *drawingAreaRenderer) Destroy() { From 6e5f359ab6f8d4daaccc9e7cbde289dae9c2cd52 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 12 Sep 2023 11:20:43 -0400 Subject: [PATCH 48/53] Fixed static error in node.go line 55 --- widget/diagramwidget/node.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/widget/diagramwidget/node.go b/widget/diagramwidget/node.go index 7a9a6108..27fffa94 100644 --- a/widget/diagramwidget/node.go +++ b/widget/diagramwidget/node.go @@ -52,8 +52,7 @@ type BaseDiagramNode struct { // 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) DiagramNode { - var diagramNode DiagramNode - diagramNode = &BaseDiagramNode{} + var diagramNode DiagramNode = &BaseDiagramNode{} InitializeBaseDiagramNode(diagramNode, diagram, obj, nodeID) return diagramNode } From ba2699062960a853b9a67b551ba7ebad0592a23b Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 13 Sep 2023 10:23:45 -0400 Subject: [PATCH 49/53] Removed go.work.sum --- go.work.sum | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 go.work.sum diff --git a/go.work.sum b/go.work.sum deleted file mode 100644 index b5329f50..00000000 --- a/go.work.sum +++ /dev/null @@ -1,7 +0,0 @@ -fyne.io/fyne/v2 v2.4.0 h1:LlyOyHmvkSo9IBm3aY+NVWSBIw+GMnssmyyIMK8F7zM= -fyne.io/fyne/v2 v2.4.0/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro= -github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= -github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= -github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= From 2a8813fb5e75b2a9dd36f7ec07903375876e2c41 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 14 Sep 2023 11:13:15 -0400 Subject: [PATCH 50/53] Made drawingArea private, added simulation events --- widget/diagramwidget/diagram.go | 107 ++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index 3c2d18e2..e6140079 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -13,7 +13,7 @@ import ( ) // Verify that interfaces are fully implemented -var _ fyne.Tappable = (*DrawingArea)(nil) +var _ fyne.Tappable = (*drawingArea)(nil) type linkPadPair struct { link *BaseDiagramLink @@ -26,9 +26,9 @@ type linkPadPair struct { type DiagramWidget struct { widget.BaseWidget scrollingContainer *container.Scroll - // DrawingArea is public only to support application-level testing scenarios in which simylated + // drawingArea is public only to support application-level testing scenarios in which simylated // Drag, Mouse, and Tap events need to be sent to the diagram. It should not be otherwise accessed - DrawingArea *DrawingArea + drawingArea *drawingArea // ID is expected to be unique across all DiagramWidgets in the application. ID string @@ -91,9 +91,9 @@ func NewDiagramWidget(id string) *DiagramWidget { selection: map[string]DiagramElement{}, diagramElementLinkDependencies: map[string][]linkPadPair{}, } - dw.DrawingArea = newDrawingArea(dw) - dw.DrawingArea.Resize(dw.DesiredSize) - dw.scrollingContainer = container.NewScroll(dw.DrawingArea) + dw.drawingArea = newDrawingArea(dw) + dw.drawingArea.Resize(dw.DesiredSize) + dw.scrollingContainer = container.NewScroll(dw.drawingArea) appTheme := fyne.CurrentApp().Settings().Theme() appVariant := fyne.CurrentApp().Settings().ThemeVariant() dw.DefaultDiagramElementProperties.ForegroundColor = appTheme.Color(theme.ColorNameForeground, appVariant) @@ -158,8 +158,8 @@ func (dw *DiagramWidget) addNode(node DiagramNode) { // adjustBounds calculates the bounds of the diagram elements and adjusts the size of the drawing area accordingly // If necessary, it also moves all the diagram elements so that their position coordinates are all positive func (dw *DiagramWidget) adjustBounds() { - position := dw.DrawingArea.Position() - size := dw.DrawingArea.Size() + position := dw.drawingArea.Position() + size := dw.drawingArea.Size() left := position.X right := position.X + size.Width top := position.Y @@ -174,11 +174,11 @@ func (dw *DiagramWidget) adjustBounds() { } moveDelta := fyne.NewPos(0, 0) moveDeltaChanged := false - if left < dw.DrawingArea.Position().X { + if left < dw.drawingArea.Position().X { moveDelta.X = -left moveDeltaChanged = true } - if top < dw.DrawingArea.Position().Y { + if top < dw.drawingArea.Position().Y { moveDelta.Y = -top moveDeltaChanged = true } @@ -186,8 +186,8 @@ func (dw *DiagramWidget) adjustBounds() { dw.moveDiagramElements(moveDelta) // moving the elements might have pushed an element beyond the newly computed bounds. // we have to recompute - position = dw.DrawingArea.Position() - size = dw.DrawingArea.Size() + position = dw.drawingArea.Position() + size = dw.drawingArea.Size() left = position.X right = position.X + size.Width top = position.Y @@ -202,7 +202,7 @@ func (dw *DiagramWidget) adjustBounds() { } } dw.DesiredSize = fyne.NewSize(right-left, bottom-top) - dw.DrawingArea.Resize(dw.DesiredSize) + dw.drawingArea.Resize(dw.DesiredSize) dw.scrollingContainer.Refresh() } @@ -213,7 +213,7 @@ func (dw *DiagramWidget) BringToFront(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveToBack(listElement) - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } } } @@ -225,7 +225,7 @@ func (dw *DiagramWidget) BringForward(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveAfter(listElement, listElement.Next()) - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } } } @@ -285,6 +285,47 @@ func (dw *DiagramWidget) DisplaceNode(node DiagramNode, delta fyne.Position) { dw.adjustBounds() } +// SimulateDragEnd is provided to support application-level testing. It calls DragEnd on the drawingArea +func (dw *DiagramWidget) SimulateDragEnd() { + dw.drawingArea.DragEnd() +} + +// SimulateDragged is provided to support application-level testing. It calls Dragged on the drawingArea +func (dw *DiagramWidget) SimulateDragged(event *fyne.DragEvent) { + dw.drawingArea.Dragged(event) +} + +// SimulateMouseDown is provided to support application-level testing. It calls MouseDown on the drawingArea +func (dw *DiagramWidget) SimulateMouseDown(event *desktop.MouseEvent) { + dw.drawingArea.MouseDown((event)) +} + +// SimulateMouseIn is provided to support application-level testing. It calls MouseIn on the drawingArea +func (dw *DiagramWidget) SimulateMouseIn(event *desktop.MouseEvent) { + dw.drawingArea.MouseIn(event) +} + +// SimulateMouseMoved is provided to support application-level testing. It calls MouseMoved on the drawingArea +func (dw *DiagramWidget) SimulateMouseMoved(event *desktop.MouseEvent) { + dw.drawingArea.MouseMoved(event) +} + +// SimulateMouseOut is provided to support application-level testing. It calls MouseOut on the drawingArea +func (dw *DiagramWidget) SimulateMouseOut() { + dw.drawingArea.MouseOut() +} + +// SimulateMouseUp is provided to support application-level testing. It calls MouseUp on the drawingArea +func (dw *DiagramWidget) SimulateMouseUp(event *desktop.MouseEvent) { + dw.drawingArea.MouseUp(event) +} + +// SimulateTapped is provided to support application-level testing. It calls Tapped on the drawingArea +// from the selection +func (dw *DiagramWidget) SimulateTapped(event *fyne.PointEvent) { + dw.drawingArea.Tapped(event) +} + // GetBackgroundColor returns the background color for the widget from the diagram's theme, which // may be different from the application's theme. func (dw *DiagramWidget) GetBackgroundColor() color.Color { @@ -469,7 +510,7 @@ func (dw *DiagramWidget) RemoveElement(elementID string) { if element.IsLink() { dw.removeDependenciesInvolvingLink(elementID) } - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } // SelectDiagramElement clears the selection, makes the indicated element the primary selection, and invokes @@ -498,7 +539,7 @@ func (dw *DiagramWidget) SendToBack(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveToFront(listElement) - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } } } @@ -510,7 +551,7 @@ func (dw *DiagramWidget) SendBackward(elementID string) { diagramElement := value.(DiagramElement) if diagramElement.GetDiagramElementID() == elementID { dw.DiagramElements.MoveBefore(listElement, listElement.Prev()) - dw.DrawingArea.Refresh() + dw.drawingArea.Refresh() } } } @@ -556,18 +597,18 @@ func (r *diagramWidgetRenderer) Objects() []fyne.CanvasObject { } func (r *diagramWidgetRenderer) Refresh() { - r.diagramWidget.DrawingArea.Refresh() + r.diagramWidget.drawingArea.Refresh() } -// DrawingArea is the widget in which the diagram is actually rendered. It is public only to support +// drawingArea is the widget in which the diagram is actually rendered. It is public only to support // application testing scenarios in which simulated Drag, Mouse, and Tap events need to be sent to the diagram. -type DrawingArea struct { +type drawingArea struct { widget.BaseWidget diagram *DiagramWidget } -func newDrawingArea(diagram *DiagramWidget) *DrawingArea { - drawingArea := &DrawingArea{ +func newDrawingArea(diagram *DiagramWidget) *drawingArea { + drawingArea := &drawingArea{ diagram: diagram, } drawingArea.ExtendBaseWidget(drawingArea) @@ -575,54 +616,54 @@ func newDrawingArea(diagram *DiagramWidget) *DrawingArea { } // CreateRenderer is the required method for widget extensions -func (da *DrawingArea) CreateRenderer() fyne.WidgetRenderer { +func (da *drawingArea) CreateRenderer() fyne.WidgetRenderer { dar := &drawingAreaRenderer{} dar.da = da return dar } // DragEnd is called when the drag comes to an end. It refreshes the widget -func (da *DrawingArea) DragEnd() { +func (da *drawingArea) DragEnd() { da.Refresh() } // Dragged responds to a drag movement in the background of the diagram. It moves the widget itself. -func (da *DrawingArea) Dragged(event *fyne.DragEvent) { +func (da *drawingArea) Dragged(event *fyne.DragEvent) { delta := fyne.NewPos(event.Dragged.DX, event.Dragged.DY) da.diagram.moveDiagramElements(delta) da.diagram.adjustBounds() } // MouseDown responds to MouseDown events. It invokes the callback, if present -func (da *DrawingArea) MouseDown(event *desktop.MouseEvent) { +func (da *drawingArea) MouseDown(event *desktop.MouseEvent) { if da.diagram.MouseDownCallback != nil { da.diagram.MouseDownCallback(event) } } // MouseIn responds to the mouse moving into the diagram. It presently is a noop -func (da *DrawingArea) MouseIn(event *desktop.MouseEvent) { +func (da *drawingArea) MouseIn(event *desktop.MouseEvent) { if da.diagram.MouseInCallback != nil { da.diagram.MouseInCallback(event) } } // MouseMoved responds to mouse movements in the diagram. It presently is a noop -func (da *DrawingArea) MouseMoved(event *desktop.MouseEvent) { +func (da *drawingArea) MouseMoved(event *desktop.MouseEvent) { if da.diagram.MouseMovedCallback != nil { da.diagram.MouseMovedCallback(event) } } // MouseOut responds to the mouse leaving the diagram. It presently is a noop -func (da *DrawingArea) MouseOut() { +func (da *drawingArea) MouseOut() { if da.diagram.MouseOutCallback != nil { da.diagram.MouseOutCallback() } } // MouseUp responds to MouseUp events. It invokes the callback, if present -func (da *DrawingArea) MouseUp(event *desktop.MouseEvent) { +func (da *drawingArea) MouseUp(event *desktop.MouseEvent) { if da.diagram.MouseUpCallback != nil { da.diagram.MouseUpCallback(event) } @@ -630,7 +671,7 @@ func (da *DrawingArea) MouseUp(event *desktop.MouseEvent) { // Tapped respondss to taps in the diagram background. It removes all diagram elements // from the selection -func (da *DrawingArea) Tapped(event *fyne.PointEvent) { +func (da *drawingArea) Tapped(event *fyne.PointEvent) { if da.diagram.OnTappedCallback != nil { da.diagram.OnTappedCallback(da.diagram, event) } else { @@ -639,7 +680,7 @@ func (da *DrawingArea) Tapped(event *fyne.PointEvent) { } type drawingAreaRenderer struct { - da *DrawingArea + da *drawingArea } func (dar *drawingAreaRenderer) Destroy() { From 846dd70d84919eac191ee3da269b06b0b7bfcb4e Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Mon, 18 Sep 2023 11:48:48 -0400 Subject: [PATCH 51/53] Removed DiagramWidget.SimulateMouse... functions --- widget/diagramwidget/diagram.go | 41 --------------------------------- 1 file changed, 41 deletions(-) diff --git a/widget/diagramwidget/diagram.go b/widget/diagramwidget/diagram.go index e6140079..07287b85 100644 --- a/widget/diagramwidget/diagram.go +++ b/widget/diagramwidget/diagram.go @@ -285,47 +285,6 @@ func (dw *DiagramWidget) DisplaceNode(node DiagramNode, delta fyne.Position) { dw.adjustBounds() } -// SimulateDragEnd is provided to support application-level testing. It calls DragEnd on the drawingArea -func (dw *DiagramWidget) SimulateDragEnd() { - dw.drawingArea.DragEnd() -} - -// SimulateDragged is provided to support application-level testing. It calls Dragged on the drawingArea -func (dw *DiagramWidget) SimulateDragged(event *fyne.DragEvent) { - dw.drawingArea.Dragged(event) -} - -// SimulateMouseDown is provided to support application-level testing. It calls MouseDown on the drawingArea -func (dw *DiagramWidget) SimulateMouseDown(event *desktop.MouseEvent) { - dw.drawingArea.MouseDown((event)) -} - -// SimulateMouseIn is provided to support application-level testing. It calls MouseIn on the drawingArea -func (dw *DiagramWidget) SimulateMouseIn(event *desktop.MouseEvent) { - dw.drawingArea.MouseIn(event) -} - -// SimulateMouseMoved is provided to support application-level testing. It calls MouseMoved on the drawingArea -func (dw *DiagramWidget) SimulateMouseMoved(event *desktop.MouseEvent) { - dw.drawingArea.MouseMoved(event) -} - -// SimulateMouseOut is provided to support application-level testing. It calls MouseOut on the drawingArea -func (dw *DiagramWidget) SimulateMouseOut() { - dw.drawingArea.MouseOut() -} - -// SimulateMouseUp is provided to support application-level testing. It calls MouseUp on the drawingArea -func (dw *DiagramWidget) SimulateMouseUp(event *desktop.MouseEvent) { - dw.drawingArea.MouseUp(event) -} - -// SimulateTapped is provided to support application-level testing. It calls Tapped on the drawingArea -// from the selection -func (dw *DiagramWidget) SimulateTapped(event *fyne.PointEvent) { - dw.drawingArea.Tapped(event) -} - // GetBackgroundColor returns the background color for the widget from the diagram's theme, which // may be different from the application's theme. func (dw *DiagramWidget) GetBackgroundColor() color.Color { From a8defda0a9b3841cd6cb7205a79acddfaa19d203 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Tue, 17 Oct 2023 10:37:43 -0400 Subject: [PATCH 52/53] Upgrade to fyne 2.4.1 --- README.md | 108 ++++++------------- go.mod | 8 +- go.sum | 111 ++++++++++++++------ img/Screenshot 2023-10-07 102222.png | Bin 0 -> 39424 bytes widget/diagramwidget/README.md | 148 +++++++++++++++++++++++++++ widget/diagramwidget/anchoredtext.go | 3 +- 6 files changed, 261 insertions(+), 117 deletions(-) create mode 100644 img/Screenshot 2023-10-07 102222.png create mode 100644 widget/diagramwidget/README.md diff --git a/README.md b/README.md index d70fb738..be838d87 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ This repository holds community extensions for the [Fyne](https://fyne.io) toolk This is in early development and more information will appear soon. -# Layouts +## Layouts Community contributed layouts. `import "fyne.io/x/fyne/layout"` -## Responsive Layout +### Responsive Layout The responsive layout provides a "bootstrap like" configuration to automatically make containers and canvas reponsive to the window width. It reacts to the window size to resize and move the elements. The sizes are configured with a ratio of the **container** width (`0.5` is 50% of the container size). @@ -51,13 +51,22 @@ layout := NewResponsiveLayout( ``` -# Widgets +## Widgets -Community contributed widgets. +This package contains a collection of community-contributed widgets for the [Fyne](https://fyne.io/) +toolkit. The code here is intended to be production ready, but may be lacking +some desirable functional features. If you have suggestions for changes to +existing functionality or addition of new functionality, please look at the existing +issues in the repository to see if your idea is already on the table. If it is not, +feel free to open an issue. + +This collection should be considered a work in progress. When changes are made, +serious consideration will be given to backward compatibility, but compatibility +is not guaranteed. `import "fyne.io/x/fyne/widget"` -## Animated Gif +### Animated Gif A widget that will run animated gifs. @@ -70,7 +79,7 @@ gif, err := NewAnimatedGif(storage.NewFileURI("./testdata/gif/earth.gif")) gif.Start() ``` -## Calendar +### Calendar A date picker which returns a [time](https://pkg.go.dev/time) object with the selected date. @@ -91,18 +100,7 @@ calendar := widget.NewCalendar(time.Now(), onSelected, cellSize, padding) ``` [Demo](./cmd/hexwidget_demo/main.go) available for example usage -## DiagramWidget - -This package contains a collection of widgets for the [Fyne](https://fyne.io/) -toolkit. The code here is intended to be production ready, but may be lacking -some desirable functional features. If you have suggestions for changes to -existing functionality or addition of new functionality, please look at the existing -issues in the repository to see if your idea is already on the table. If it is not, -feel free to open an issue. - -This collection should be considered a work in progress. When changes are made, -serious consideration will be given to backward compatibility, but compatibility -is not guaranteed. +### DiagramWidget The DiagramWidget provides a drawing area within which a diagram can be created. The diagram itself is a collection of DiagramElement widgets (an interface). There are two types of DiagramElements: DiagramNode widgets and DiagramLink widgets. @@ -113,69 +111,21 @@ Note that links can connect to other links as well as nodes. While some provisions have been made for automatic layout, layouts are for the convenience of the author and are on-demand only. The design intent is that users will place the diagram elements for human readability. -DiagramElements are essentially self-managed from a layout perspective. DiagramNodes have no size +DiagramElements are managed by the DiagramWidget from a layout perspective. DiagramNodes have no size constraints imposed by the DiagramWidget and can be placed anywhere. DiagramLinks connect DiagramElements. The DiagramWidget keeps track of the DiagramElements to which each DiagramLink is connected and calls the Refresh() method on the link when the connected diagram element is moved or resized. -* [demo](../../cmd/diagramdemo/main.go) +* [demo](./cmd/diagramdemo/main.go) +* [More Detail](./widget/diagramwidget/README.md)

Diagram Widget

-### DiagramElement Interface - -A DiagramElement is the base interface for any element of the diagram being managed by the -DiagramWidget. It provides a common interface for DiagramNode and DiagramLink widgets. The DiagramElement -interface provides operations for retrieving the DiagramWidget, the ID of the DiagramElement, and -for showing and hiding the handles that are used for graphically manipulating the diagram element. -The specifics of what handles do are different for nodes and links - these are described below in the -sections for their respective widgets. - -### DiagramNode Widget - -The DiagramNode widget is a wrapper around a user-supplied CanvasObject. In addition to the user-supplied -CanvasObject, the node displays a border and, when selected, handles at the corners and edge mid-points that can be used to manipulate the size of the node. The node can be selected and dragged to a new position with a mouse by clicking in the border area around the canvas object. - -### DiagramLink Widget - -The DiagramLink widget provides a directed line-based connection between two DiagramElements. -The link is defined in terms of LinkPoints that are connected by LinkSegments (both of which -are widgets in their own right). The link maintains an array of points, with the point at index -[0] being the point at which the link connects to the source DiagramElement and the point at the -last index being the point at which the link connects to the target DiagramElement. The link also -maintains an array of line segments, with the segment at index [0] connecting points [0] and [1], -the segment at index [1] connecting the points [1] and [2], etc. The current implementation only -has a single segment, but interfaces will be added shortly to enable the addition and removal of -points and segments. - -Many visual languages (formalized diagrams) utilize graphical decorations on lines. The link -provides the ability to add an arbitrary number of graphic decorations at three points along -the link: the source end, the target end, and the midpoint. Decorations are stacked in the order -they are added at the indicated point. The location of the source and target points is obvious, -but the midpoint bears some discussion. If there is only one line segment, the midpoint is the -midpoint of this segment. If there is more than one line segment, the "midpoint" is defined to -be the next to last point in the array of points. For a two-segment link, this will be the point -at which the two segments join. For a multi-segment link, this will be the point at which the -next-to-last and last segments join. - -Also common in visual languages are textual annotations associated with either the link as a whole -or to the ends of the link. For this purpose, the link allows the association of one or more -AnchoredText widgets with each of the reference points on the link: source, target, and midpoint. -These widgets keep track of their position relative to the link's reference points. They can -be moved interactively with the mouse to a new position. When the reference point on the link -moves, the anchored text will also move, maintaining its relative position. - -Users do not create AnchoredText widgets directly: the link itself creates and manages them. -the user calls Add\AnchoredText(key, text) to add an anchored text. The key is expected -to be unique at the position and can be used to update the text later. The AnchoredText can also -be directly edited in the diagram. - -When a link connects to another link, it connects at the midpoint of the source or target link. - -## FileTree + +### FileTree An extension of widget.Tree for displaying a file system hierarchy. @@ -191,7 +141,7 @@ tree.Sorter = func(u1, u2 fyne.URI) bool { FileTree Widget

-## CompletionEntry +### CompletionEntry An extension of widget.Entry for displaying a popup menu for completion. The "up" and "down" keys on the keyboard are used to navigate through the menu, the "Enter" key is used to confirm the selection. The options can also be selected with the mouse. The "Escape" key closes the selection list. @@ -235,7 +185,7 @@ entry.OnChanged = func(s string) { CompletionEntry Widget

-## 7-Segment ("Hex") Display +### 7-Segment ("Hex") Display A skeuomorphic widget simulating a 7-segment "hex" display. Supports setting digits by value, as well as directly controlling which segments are on or @@ -254,7 +204,7 @@ h := widget.NewHexWidget() h.Set(0xf) ``` -## Map +### Map An OpenStreetMap widget that can the user can pan and zoom. To use this in your app and be compliant with their requirements you may need to request @@ -266,13 +216,13 @@ m := NewMap() ![](img/map.png) -# Data Binding +## Data Binding Community contributed data sources for binding. `import fyne.io/x/fyne/data/binding` -## WebString +### WebString A `WebSocketString` binding creates a `String` data binding to the specified web socket URL. Each time a message is read the value will be converted to a `string` and set on the binding. @@ -287,7 +237,7 @@ The code above uses a test web sockets server from "PieSocket", you can run the and go to [their test page](https://www.piesocket.com/websocket-tester) to send messages. The widget will automatically update to the latest data sent through the socket. -## MqttString +### MqttString A `MqttString` binding creates a `String` data binding to the specified _topic_ associated with the specified **MQTT** client connection. Each time a message is received the value will be converted @@ -310,13 +260,13 @@ if err := token.Error(); err != nil { s, err := binding.NewMqttString(client, "fyne.io/x/string") ``` -# Data Validation +## Data Validation Community contributed validators. `import fyne.io/x/fyne/data/validation` -## Password +### Password A validator for validating passwords. Uses https://github.com/wagslane/go-password-validator for validation using an entropy system. diff --git a/go.mod b/go.mod index 711804af..a42681e8 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module fyne.io/x/fyne go 1.14 require ( - fyne.io/fyne/v2 v2.3.5 + fyne.io/fyne/v2 v2.4.1 github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 github.com/eclipse/paho.mqtt.golang v1.3.5 github.com/gorilla/websocket v1.4.2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 - github.com/stretchr/testify v1.8.1 + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef + github.com/stretchr/testify v1.8.4 github.com/twpayne/go-geom v1.0.0 github.com/wagslane/go-password-validator v0.3.0 - golang.org/x/image v0.3.0 + golang.org/x/image v0.11.0 ) diff --git a/go.sum b/go.sum index 81941f60..7ddeca3c 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -36,15 +38,16 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -fyne.io/fyne/v2 v2.3.5 h1:Q8WOtsms+esLrBKJGdj6P+klu+UXzRq63uPxFSQm4nc= -fyne.io/fyne/v2 v2.3.5/go.mod h1:fbrL+kwOQ6sdVhnURktTHIRIEXwysQSLeejyFyABmNI= -fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b h1:MP1cUnIdF1cxrMhK9iw9H0JP3zopyD1zi84BqU6WTsE= -fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= +fyne.io/fyne/v2 v2.4.1 h1:Es100N6HIhJGg8H2ZAS2j5H/YibfxecXHs2V4A4hbq8= +fyne.io/fyne/v2 v2.4.1/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro= +fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a h1:6Xf9fP3/mt72NrqlQhJWhQGcNf6GoG9X96NTaXr+K6A= +fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84 h1:L2C3QKBQwuHuiKwaBj8HDs2r0bAUGqtBYe3ymG0S6Z4= github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -78,11 +81,12 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM= -github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg= +github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU= @@ -100,16 +104,17 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 h1:DvHeDNqK8cxdZ7C6y88pt3uE7euZH7/LluzyfnUfH/Q= -github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16/go.mod h1:zvWM81wAVW6QfVDI6yxfbCuoLnobSYTuMsrXU/u11y8= -github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6 h1:zAAA1U4ykFwqPbcj6YDxvq3F2g0wc/ngPfLJjkR/8zs= -github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE= +github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 h1:VkKnvzbvHqgEfm351rfr8Uclu5fnwq8HP2ximUzJsBM= +github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8/go.mod h1:h29xCucjNsDcYb7+0rJokxVwYAq+9kQ19WiFuBKkYtc= +github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a h1:VjN8ttdfklC0dnAdKbZqGNESdERUxtE3l8a/4Grgarc= +github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= +github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= +github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8= -github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -166,12 +171,14 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= @@ -204,7 +211,7 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= +github.com/jackmordaunt/icns/v2 v2.2.6/go.mod h1:DqlVnR5iafSphrId7aSD06r3jg0KRC9V6lEBBp504ZQ= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -216,11 +223,12 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= +github.com/lucor/goinfo v0.9.0/go.mod h1:L6m6tN5Rlova5Z83h1ZaKsMP1iiaoZ9vGTNzu5QKOD4= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -241,12 +249,12 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -267,15 +275,17 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA= -github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= -github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -287,8 +297,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= @@ -305,8 +315,9 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU= +github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -327,8 +338,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -343,8 +357,9 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= +golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= +golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -359,8 +374,9 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee h1:/tShaw8UTf0XzI8DOZwQHzC7d6Vi3EtrBnftiZ4vAvU= golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34= +golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -371,6 +387,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -404,14 +422,20 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -436,6 +460,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -473,22 +500,33 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -498,8 +536,11 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -552,11 +593,14 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -625,7 +669,9 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -664,9 +710,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/img/Screenshot 2023-10-07 102222.png b/img/Screenshot 2023-10-07 102222.png new file mode 100644 index 0000000000000000000000000000000000000000..a71664b4af9f6a390260439a78d71425da2ecbf9 GIT binary patch literal 39424 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 +
Go API Reference + Join us on Slack +
+ Code Status + Build Status + Coverage Status +

+ +# DiagramWidget + +The DiagramWidget provides a drawing area within which a diagram can be created. The diagram itself is a collection of +DiagramElement widgets (an interface). There are two types of DiagramElements: DiagramNode widgets and DiagramLink widgets. +DiagramNode widgets are thin wrappers around a user-supplied CanvasObject. +Any valid CanvasObject can be used. DiagramLinks are line-based connections between DiagramElements. +Note that links can connect to other links as well as nodes. + +While some provisions have been made for automatic layout, layouts are for the convenience +of the author and are on-demand only. The design intent is that users will place the diagram elements for human readability. + +DiagramElements are managed by the DiagramWidget from a layout perspective. DiagramNodes have no size +constraints imposed by the DiagramWidget and can be placed anywhere. DiagramLinks connect +DiagramElements. The DiagramWidget keeps track of the DiagramElements to which each DiagramLink +is connected and calls the Refresh() method on the link when the connected diagram element is moved +or resized. + +* [demo](../../cmd/diagramdemo/main.go) + +

+ Diagram Widget +

+ +## DiagramElement Interface + +A DiagramElement is the base interface for any element of the diagram being managed by the +DiagramWidget. It provides a common interface for DiagramNode and DiagramLink widgets. The DiagramElement +interface provides operations for retrieving the DiagramWidget, the ID of the DiagramElement, and +for showing and hiding the handles that are used for graphically manipulating the diagram element. +The specifics of what handles do are different for nodes and links - these are described below in the +sections for their respective widgets. + +## DiagramNode Widget + +The DiagramNode widget is a wrapper around a user-supplied CanvasObject. In addition to the user-supplied +CanvasObject, the node displays a border and, when selected, handles at the corners and edge mid-points +that can be used to manipulate the size of the node. The node can be selected and dragged to a new position +with a mouse by clicking in the border area around the canvas object. + +## DiagramLink Widget + +The DiagramLink widget provides a directed line-based connection between two DiagramElements. +The link is defined in terms of LinkPoints that are connected by LinkSegments (both of which +are widgets in their own right). The link maintains an array of points, with the point at index +[0] being the point at which the link connects to the source DiagramElement and the point at the +last index being the point at which the link connects to the target DiagramElement. The link also +maintains an array of line segments, with the segment at index [0] connecting points [0] and [1], +the segment at index [1] connecting the points [1] and [2], etc. The current implementation only +has a single segment, but interfaces will be added shortly to enable the addition and removal of +points and segments. + +Many visual languages (formalized diagrams) utilize graphical decorations on lines. The link +provides the ability to add an arbitrary number of graphic decorations at three points along +the link: the source end, the target end, and the midpoint. Decorations are stacked in the order +they are added at the indicated point. The location of the source and target points is obvious, +but the midpoint bears some discussion. If there is only one line segment, the midpoint is the +midpoint of this segment. If there is more than one line segment, the "midpoint" is defined to +be the next to last point in the array of points. For a two-segment link, this will be the point +at which the two segments join. For a multi-segment link, this will be the point at which the +next-to-last and last segments join. Decorations can be added by calling +`BaseDiagramLink.AddDecoration(decoration Decoration)`. Two implementations of the Decoration +interface are provided: An Arrowhead and a Polygon. + +Also common in visual languages are textual annotations associated with either the link as a whole +or to the ends of the link. For this purpose, the link allows the association of one or more +AnchoredText widgets with each of the reference points on the link: source, target, and midpoint. +These widgets keep track of their position relative to the link's reference points. They can +be moved interactively with the mouse to a new position. When the reference point on the link +moves, the anchored text will also move, maintaining its relative position. + +Users do not create AnchoredText widgets directly: the link itself creates and manages them. +the user calls `BaseDiagramLink.AddAnchoredText(key, text)` to add an anchored text. +The key is expected to be unique at the position and can be used to update the text later. +The AnchoredText can also be directly edited in the diagram. + +When a link connects to another link, it connects at the midpoint of the source or target link. + +## Target Applications + +Applications employing diagram-based user interfaces commonly have a core model (data structure), +a tree view of the underlying model, one or more diagrams displaying subsets of the model, and +a properties view of a single selected model elements. Desired application behavior is that +model data may be edited in any of the views and changes are immediately reflected in the other views. + +Mappings from model to diagram are more complex than simple bindings and typically require some +type of mapping. A simple value in the model (e.g. a pointer from Element1 to Element2 in the model) +may map to a graphical element (e.g. a link between graphical representations of Element1 and Element2). +Simple bindings are not sufficient for this type of mapping. The DiagramElements employ +application-provided unique identifiers (similar to those used in the Tree Widget) that provide +a mechanism for correlating graphical and model elements. Callbacks on the DiagramWidget +(discussed below) provide the hooks for adding mapping behavior. + +## DiagramWidget Architecture + +The DiagramWidget manages a private drawing area inside a scrolling container and the DiagramElements +that appear in the drawing. These DiagramElements may be either Nodes or Links. The content of each +node is an application-supplied canvas object that can be arbitrarily complex. Links are lines that +can connect to both Nodes and other Links. + +All DiagramElements have default ConnectionPads to which links may connect, and these defaults may be +augmented with application-supplied ConnectionPads at specific positions on the DiagramElement. The +user interface mechanisms for interactively connecting Links to ConnectionPads are built into the +DiagramWidget, with an application-provided callback `DiagramWidget.IsConnectionAllowedCallback()` +determining which connections between links and pads are allowable. + +Links can be customized with both graphical decorations and floating text annotations. Graphical +decorations may be "stacked" at three locations on each link, either at the ends or the mid-point. +An arbitrary number of floating text annotations can be added at each of these points as well. +The position of each annotation with respect to the point may be adjusted by the user and the +relative position will be maintained as the links are moved. The text in the annotations may be +edited directly (an Entry widget is used for each annotation). + +A number of callbacks on the DiagramWidget enable applications to add functionality. + +* `DiagramWidget.LinkConnectionChangedCallback()` can be used to update application data structures when link connections are changed interactively. +* `DiagramWidget.LinkSegmentMouseDownSecondaryCallback()` can be used to provide context menus for links. +* `DiagramWidget.LinkSegmentMouseUpCallback()` + +* `DiagramWidget.PrimaryDiagramElementSelectionChangedCallback()` can be used to notify the application that the graphical DiagramElement selection has changed. + +There are a numer of callbacks for events directly in the drawing area: +* `DiagramWidget.MouseDownCallback()` +* `DiagramWidget.MouseInCallback()` +* `DiagramWidget.MouseMovedCallback()` +* `DiagramWidget.MouseOutCallback()` +* `DiagramWidget.MouseUpCallback()` can be used to complete a drag-and-drop operation adding a view of a data object to the diagram. +* `DiagramWidget.OnTappedCallback()' can be used to add new elements to a diagram based on a toolbar selection of element type. + +## Extending a DiagramElement + +DiagramElements can be extended by the application designer, but the initialization of the extension +is slightly different than the normal fyne extension mechanism, The initialization function uses the +normal fyne extension mechanism behind the scenes, but in addition initializes its relationship to the +DiagramWidget to which it belongs, allowing the DiagramWidget to manage the DiagramElement. When the +application designer extends diagram elements, they are expected to extend either `BaseDiagramNode` or +`BaseDiagramLink` and call the appropriate initialization functions: +* `InitializeBaseDiagramNode(diagramNode DiagramNode, diagram *DiagramWidget, obj fyne.CanvasObject, nodeID string)` +* `InitializeBaseDiagramLink(diagramLink DiagramLink, diagram *DiagramWidget, linkID string)` +where diagramNode or diagramLink are the application-defined extensions. diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index 0fb6a913..5109f22b 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -5,6 +5,7 @@ import ( "fyne.io/fyne/v2" // "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" @@ -42,7 +43,7 @@ func NewAnchoredText(text string) *AnchoredText { at.displayedTextBinding.AddListener(at) at.textEntry.Wrapping = fyne.TextWrapOff // TODO After upgrade to fyne 2.4.0, uncomment the following line and add container as an imported package - // at.textEntry.Scroll = container.ScrollNone + at.textEntry.Scroll = container.ScrollNone at.textEntry.Validator = nil at.ExtendBaseWidget(at) return at From bc06b79b94a357dd52b1ff160c55784f1e809d58 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Wed, 18 Oct 2023 11:09:43 -0400 Subject: [PATCH 53/53] Renamed screenshot, removed TODO comment, edited README --- README.md | 2 +- img/DiagramWidget.png | Bin 38325 -> 0 bytes ...ot 2023-10-07 102222.png => diagramdemo.png} | Bin widget/diagramwidget/README.md | 11 +---------- widget/diagramwidget/anchoredtext.go | 2 -- 5 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 img/DiagramWidget.png rename img/{Screenshot 2023-10-07 102222.png => diagramdemo.png} (100%) diff --git a/README.md b/README.md index be838d87..f6d022bd 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ or resized. * [More Detail](./widget/diagramwidget/README.md)

- Diagram Widget + Diagram Widget

diff --git a/img/DiagramWidget.png b/img/DiagramWidget.png deleted file mode 100644 index 9754d30065b690ef05dd6a767cb101df11537a3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38325 zcmcG$byQSs7%w`sfPe~!qJ%-Jpum8Dlpx(HElPJIHH3nq5+Wf(_b{|{3#dplbeGZ$ zJ@mkNx8FJEu65S^>#lQI>sx-n%$~j9c%EOqgsH2_QBg2bz+f;c1$h}w7>o!GgAsnX zKni|BOy+S2{vmMFlzR#*>bbcB{me>AMG6Khjifv^BLUx&JIm|4!C=&_(0>GNPAGF2 zELKrLMoRm&33iGsX>joP*S6ORQywArvyveBs`QN8u0MTrPVemp-;T@A7^T0rpP%t^ z$)JQ_pIWnkHG}m-Yz8Om{e}_zrrV-L3LudMq_$q@6+c6X0ib`%B^m79PWE1o}^3=`Z zwugLA*bL(1YQ7}Z^d^b@upckyvNM@q(x~4{34ohr6zZ1^cf{RW8d#a~+~td5s<)bMkv-DGe&*NeV3C7GXB=*g00s;Edx^!UW!Iu3rXATUSZGke?Uw58 z7KO1uJXLWH%g8E>d`3-_YA1W$}`-c`1{|JM%=D-~Q)^SV6Z%WxM)UcNlEJ zkonxJ36w7xy?=Ey?>%Tz`6}V>aI9AxX=HL=o_D-|y>=%$!y1xVsLdrdK z^DR~Kbqj45XYSf|REqQW-LE<-Tb$_VVl8hj)^W~Lw|m#shBDXTHs4rq>bNLo7e6`^ zP+Y!Kv8In>)G4%`e()z+d-0^~b?Jkosvo+?aLr0hk9^JiMyz^S%i_TeaPMpzQPR{d zLF`}q<9JOso_2>)-LT(V#tfHPsRd|(-^1j2T0^PIh-jYlg(U1Obhe@4Qf~Yz&~KvF zhl@=&YkN@-GduZKeWg%freK%oDd19R8WAq(>|dc5v5s-|-Mt9cwuQct2_g*NQHrvd zd{NO%2>sx$sE&0ocQ_?@%dJ7^2k!|(61HM0o#$FEdX(!|IVOj*NQn2|N|s<2?pW+e zUW?yxI>A;|wbn5$szwk&?=X~>7OAqvv~cSct>=88zh_|J<5NosNAA$t%GMth!5xOX zc-eCJ{W@lL`yX(^3XLlJYQ3=YJs)WPz5C7ucr3ThINu;jli#f6?Gx$l<Ccw+gL(Mg&a*x#ar4~@V(U1S4k|8OT0S#;sjaw@~5KL1X?G5 z{5`K_?UCh>-oh1I%W)31i1HPLg00u84M$}@z2WoIWGWWHDdfQuOt4NnTjUaS2|+L5Ho#v;MOu2$FDqUO1tK)_yD&}~%PV)?+- zF`&S}vTTH>qG_176t@OHmR$UMP8m~Jg6Unzsq|R&Zaj(Vk!VUBLO$+FIlX)JeSxm1 z==P~Ol|YbDiUj!Fs0=z?Is-Zrx|f8{E?c{5&Z?I=;z_;qi&dW~<(D+aIX<_f9KK&3 zpv&vLHZV`Bk*{qRu9H{S!MmWxzcya>Whh0wHRC#E#@Q*ftxrGF2>!foon?Q1h9|SW zCJ?@RdN31U2$uY*PMAunaA)O2QBlKjcU9#J+)G0}%UB2Qx?g;Jxjdqk<&1D{3({0v zT-=dq2nD@;&#}I4=?>$)ri|#JV#7AHUh&2zmwi}wlE4Uem8}icy%U|-dt>7XsS`;Z zO!i9#w$r$<2UEhH_%5}Sa9H5zNHkluq4R#Or@Do$Z@@;tGDS?(>%y> zyxhidtJHO&L;q-JVag)t9=z10yGu8>uw*B8#PCRQtzvH_nj-|tvq;Jh=3w5hTJ&YZED#dv$Kia!sixNgW;NeqohvMd+ zRg%nybw)hw90%mYZBosr6YCrncQV3e9G~Hy^{V&>pT6?!Jbh{@_5#7ed$K0lWW{0l z#P#*EH`sHmX^k952~mud$61efy3ADItC4A=(-6xB#-O*ib~uN+@(E# zcG089wEixCx%;<$MEQyVR^<7^{Ye?Ne8*JyYG83Vy`5(`eT8|raobV1O4TF(dFeNu zeBEwtU9`E4MZXbBK}o4hcz1E>k^1+@{Z@e)mw^5&c9>oF35m5B!SskjDm(oIn>!AF zmlCQH^~M-=OkTxKy?|BNFO4hN5B}EJ5MRWW$z6>*Q4{U zDrp+=?~DT0vmsq2X}5;qs2y zkGbV(W~p*K8|Gqad&yhS>t#&R-y;83j5gj7?_#&aMQXJ9p#{LtCAk&0)u9f;I>bf+ zHx$d_2mcSdOY{^P{6P6T|Ft85{|nzLDXFRTK5s%a6dG3buq%IkA?CZkr$->U*Oe+U z12D`|{H5jo?2kjmrZc<*uuH~Jzg_H3G|X0vEl`Z*1}NwmHyozH?-cwaMgYsY0$v2ZG^x`ed?%T8`M;LS`2WkdtmgP*T%{-0peZJK z*lE1HotM4~JPLNBZnIL@ZSfFZ)A{>%iTCc}*kp~{)Dy5%uY>z|${cX&B6_l3<}h5K zkLb1>F>(v{$Kys2c`xWf8}a*whBdC)reDfJC>abuOTdQSa4fmuRFCQAHK`vprG&xe z!9%gCLiK)Ywz;TQ^Gb@(D%XTfZt{9NN2>ckgy@EK?~~n6O;=&CA7BP0lUivcCgbkw zzuas%NHP5~I+&-04dX~DdB^PYd)jC10R)npe%XT&-v@U-o+kBXy=Ez;Y;Vs!K;;uM zCY)Jp+_)Hq+wGO8!-1~!t^Etfgi9Nf6#%#{15M|6r$hl8%|FEQ_aL6?Q{0H@h9`QESirQ>UjO7zq{JyB09djpg22bT`XdQEjpO$~0E2!!AiH}1`7EJqqM*Ymqg!muaG_zHr8yb}#+bvf;u(Enprclv zCL2Sbi9b$92oE05vHukZqnXOu-ZdEPg%TLA-%{A|3hbbkT1C3_yIM?J@Zu|}nFIjh zYC8jiKaes!FS86!q|w5$l>jzyPp>HHK9j$2Uy%3u_xO1earR9K&=2cDTU z=g37e7p>?&Wb)s`ke~FT46fS{CAQ#-csHWtVevp4(DS zkphxi94YWZDpxgmat3_H186zZpc_${P3U_BBuqL+Jk_&=LYm`Ix1`rIKJvI$6&lri zGj{n&@Axo~zxT<2&%p*3EmqsWS#2E!)A8vMUb^X(*yw*!o1V1riND7=_PTMdLbL1v zD12A`m7P8f8T@m#uMeAj_SaX62?LikbfiDn8Rpzd8k)%lt8Gm7KIMJm+Cf~d#sd3g z9oyh0J&D8;`g^+O;eM45KAeEUlE%5>RS0%+(K#CNYDg$nkqTx{OhL*!=6no3V)TmU$yOlcJE!{X&~PTv;l`5ueqUZGivsZX&vw*U7)ZuP+KWWSTQse0|~bXU!8 zxF8|_>w5ie&qnQpQ`1yP;68Su&I02=A5ATB>g)^Z>y$=1D0dTYXvD26>_$9?qGu2% z-BYSTX{yO0o@Yl(hVQ7E?AxQ+_J7}rgc?F2D8u`y=cgR^oUzq^A~v>rMAq(C-G5+M zS+1ofHq=0R9c~+QW9tRLui0_rCo&3MiDPlay0rB!uqW?SYwA5fF1DJgZH zd%6ZNX*U27Bwt^=!viW>WiyadbM)P=xWUpCag?2?w=fU-jMqUvEL-w2F=cp*=W_Zx zgKi|;0W_JSUgf2pWNe~SW9gm9C;cHL@Q+%3isqnYRoaj7G04coets1p{EGwyaep1< z{h5FOucqz2(}OW97N{0eT4c&adP=DtB*eunrmP`OuvU7;9t5! zu%k@*oruRR*n+mrxA#6-w@DZ|JYI;=GE#rNR#;^;G+$J=(Q!3r@cw!r5p@Xx5$R~G zp2-R~FNL_2haCRJAOqVq5#h9nJpBZuYu<;u6m;)yy1u8n`M6JPc+4slOBney4w-Hv zT5u_ARTGQ^HqOwol#;i;4__D@&AhwQ$?MfU+!Bify%M7nuKnPO&ig?*xnhOpyUIF= zTv|D!pr~x1AEH?+YzBUXh)y-39}~cJw4UGKGk>2i%f*@Oy_j%j0E2yCfgVximdqV@ zu0CQQNYGs?=P%+8cP#!8;@LM8qeQ?aCN?$GZzS>gFxihJtykiFl6OIcfa8JyAL8^i zgYx`}_)yzYPNL(t8;;e@Dyy@_wi>fgTkGzn%MdWfWhs0y`neJ*apsISZ0Xf8Qzp2F zaT}@y#o9uw2Vjk*7KOu7LW)P|79)4evyBRrtAJ(KwUP3T{NhH}gUp9_Y!`Eu_D(jN z>TJWASzv+Y(kOx`I-Z)pS4PP0f|80T;)?U^(eY4^8g=UxGnR+~n`#tZrn&G8uRO>r zJ4A)KoaplB*O)Yo*k81`3R1vDhgQGVJx>%(o5LN+JuIxjCaPRTqoq}JctTe=;x>YP zv-0!uVTOEr+z?2lQ`C41*`arEls(t^1n19OT&=g;f|A|J)lY!ELx!rINuT* zR5@a~mC;deZPZ~n4-I+wyNI;FFRCK#A&ew^Vk17d)tUoY0-m(92aFFTd)X#WmAl&p ziLt+KIPFz+N#N=*tVFB4Sqee9&puw=a5+0WaXOj*$Xfuo8P97{ygv-a$t55`6eZAj zc5KH$+!7?qG&VG1VMr8y9QaoB#4a<}@&#Iu^P$bdz!$Ty5bE%2o&eR=ElhdMOi^7- z>v)HJk*D@)gI9>LP&t-WM??nyYS2#^Zy}D~)ABl(s*nqk*Sc-Xm-8aK5snsFVf_*u zy`eUz*vg%skYFMWil%69_^%xGj0tWONsbpW_M z*XYrEuc3Y!kdVy%6au4l5>)A@KWNpebKGowa+lDGVX`s9EK=fCn57!~;1Fc%}UWJM1@lp2ooRdLs+ZG+F5JD=Z!qqTB#fMi65H@f5a84SM zop!Ut4Ll8wvGcg`%Gl$RNx$801XoT;A{ik}{Zbe*6#zK8n#*@&tJ#gs(DtY=5*boU z7yCJ_&63{I!ql%UYZ0YA&)a;DM1~M`gY~!Q8A>jI>2C@M5JE|R6*t7y0el59H9B&f z7KZbs*(xjmRJda{nebyAXB0C4rHJq`(arilEh-4oyD(T9>JoS{clh`0WTWJCiL~8r zYpdrAW-_Uys_&hAhU>hXMr5d9fzgsE0(DN=NM>xyCs{nj^7`)h(a?KSd9h>r)>ftb z-ta~uMk4?~=Ved?B`WUJ^w{6FhjW3lHwLJSBc|_E*w6|14>SG}NyGGCJG%~3PXb`L za-3ez$jtv_AE)%l79nWe$J+F^|7q&UgR05-_%2ACZ7Je@4b*V>Q>sA10dm%g)?)yV zPT-bHNp3w?<<>Q7*8#lQ5baBsCTsd-M4~oB4{GEUu*4cwQl5-q(;CmuP7vWWOwX~u zJvXPPiluDYwtB%$0vH*^k$l-yG@FQwde5#4^wOo4Po6J=USB-F2;)=**sG=%zdwNm zwPj!F(dq?p_ht1a;=iBfQVC!`gVc#w(`Qk~pw;6U%fPDW!KQ%@*I!JqBR6v+G=LOF zM-T4U9>9y<2gecucEerRm=xCzE(Ddj^S=+^1|OjBeix6=u6BjD>GWHEPgj!gK=GI#^=Cl)>nuX(aoOaunf)3& zv+_sv-n#{Q#U>E;u3e~CP895JJX*Zb6eEf)7qlJx9U;7U&Fcujq7lKu&W_rs_#148xPe)9iR3LK>rXBt&aV2S#Y9B7{@QS18S>=E zJ=4bex{E?5nEKE;h+bAL+m#hUm9=gqCvp8FZ)3@Ie#>fv_=ssh14O*fu5voi>F(hR zG_yZAG~xFF9m@$KA{z&d!r<=QF@TdEGuULW>9JamHA~#;kQ==+Oj6*IZ6KZJYF@78 z4G}#P?*;n0xzHP@nrD!fLrTMJ@3z?G(z)4qQWVYz?@+Ny^{-4u9ItF#@O5?nyw3L#^;(1nC|)_P)Bku`AZ zzVF512_hb72+#2TbYCCEFbR`ofga{K>Y{Uv9+|6_y3ym2x%t{GWDVLtA-NDow@BvH z*17F3K>%xy1UD1~IKpr2GC%?C#gDwS4b5@@3r48)%!``b>jD2dwOr<#_0V2%B?Zz3O zvj(efX;`h#R@!psAZUVeR1z&I@7xe_bWd0Gryu;pd6`KgQUYDn1fg2fr#4>maPIr19ku)k7D+rR+VbBXUp10?zRcss8)hcK^+SjM3}zZ-L0wCtKf%5H@RXTb2Dv z$m0NUS>$OI5Yd^%R?T=;4GGK5C!P=w{*V*RHWPVTWJ{%(dK$X6F;!=koK^-%xGs=) z)~4#J?0jnAb0W$r69Of_K#ABK0Uekro%B6ta)|LzLR#cUBj;w4B9St95NrLw^Yo`A z5>@)nSx+>g+S@P7-86&X_#2pYU$6}RrijOCOkquua?)d$j|f4|>Zo?Jvf^SS<&7s}(hteEQT z-0oCeWLQ;na%MNW9oV9z-rS{DeU$_WX?lQfz5*O;&yE150lL-gr%ajjmI8$Ol6d`c zeukM-d&BT+k6Ms$yEupOxLfWz!u{&~BE9bkm7W8dZalP-^+naScnKhdayb@%0aKzI ztZ)a1YTdHZtxJomb~zY~drEeF3-0{vKtkv^Ay{RhBesm0_gc$MSkz>H|G)shK*uM~ z=l_o!D}R-7OyQ1r$P{4FW5_9);en*W{O3Z7D<>nU=&JQk@ zl^9SkYvqXd|NB{Py+?Gj9O6ki$_X#QGICEo^MFZmcGUv20hpMT`&z6K#Q4*^GnF0J1d_i8*-fj2^%~>D`Q**;5 zqcQpzy+VV9_?H6Z_RcwOFhe;rc4dyW^_iw7Q$m;wNh~94Sl*X1&)Sxt902(@Y>f)$VtJaYgo z2vqt+N`GQ0nB?~DK^U9)TuTT~*OToZw4iPS$#{xd&u_^R7IF`CxZ?xVn~UI)fb+{H zxzxH0h_&eljOs8@VNb6FW>lVR)YZMYuxd!#Cq)_pnol_>#V_|J-Fj=7azJsLD1Iag zkVU06HBs83bqOpPCa8R_YU;QBtce&V?+I@9nlc0{?lwTMLdE_sTSx-fb0=_^B1NkN zk!1O!EPnO3e0+TC|G@^ZK$KqRxWn4-G?FtyFW7k@ZgG&3A5%9tP`vvW(0cR^M%GGWzWtQEOsu-@^r1=q` z=>W(9_E{=p+9gy%M>Lz#sb&{o^bSCsI#7aXHUm2H2bmGF@|^^hc6fJHOBxlHcC@=R zQSI{Q^h$Iyz&v!zUtN zZx59;Jmr2RN*!~N6v{}zqFe!n1Yftq|8rgX|G#~!MNtSzeMQD=Ij@^Nu3x{7P-(6| zUd`X1eM2+8w=#f7o{m+!Wcp45aq0*@Ml!OrS5J_YV?J)O;sO-37ocC4f{{L{@TP1E z-O}#A%4@Q2at>68)e)Ga`(<0jTln|5m4P-D3H;s{(;Ku#xh&YiZAe9o;nK2S>CZ-} zOoJ66v^G+l>N^SifRlT7LIH(-%=MWsNRtYZp2H2`J?#9Ki|hp~x$5n$n} z1b(YyHdy;@{YpJh1JaEVM+?J%4Ah++uVEp**NjzS?(QjLi;s5%k{t|o#ir2~vHI~PTg94q6CbX&Re6_dVm{?l0OK4{OI6qoeFF_tAFMe5 zX^-~7N~z&$f~xHsQmSvjSviK6W}^P7j;gR|Jq5?&@?_vvykTWRy}1dfVR>$fPX{mkCu&u()2-GCx(_fJy|FX=2~A(*TjaqhxWQqx z&bYx@%leap=J0#y16uub#<{F}P`9Dfa3(_#_GjB>}RaX9?CsAmDVl`h$c zn=8ysj#ITf&)WgY_=8sVN!=5n`v9hs!LC0m%vaT$3xLzqiThQ(SKnu7kC|Y zwl*{XI{rabano6?p%Bnv?Ix>Bfh*KLC0OiG&a4($?0?0f7>B3?U{S0ouyV$!@hB%z zHk@%moCveO2cj`vy?WiN+W!3xxLP+~iM?sti9&3pxR=WK@S0EMAIY8mrZT`m>acIl zm{y}(WwJ-fIg)01a70;wm((OKR!#fl`vaYJgG~4WiLGKf6XLerEQiocM+I+CI<&#hov$h4(E!v4CkapZ-51oYnaQTqQOsrEq|LB zmnUpkqAKxQ>X|%&Nv}=-zstN#mFhvhN3E)fFBX1qCvPP8U>9LO#qS=}o{KIx*tJ z%y0`_WPUP=4g84wX)AXMDX>9vkVv;i&ra6|byEh_J)+c%8upu@%jSr5^~5?yvgmRv1q_WlHK_>+{Wdn2Aj%?0{HzOZMN& z@Zp)|(!DcV(c)g&FN>{vwYY0O#e3e%c+FW&){80e;xza|o?Rb_y|dNr&H7zchK#-X zio%f-VbdOLeo^BiY>FQktkK zRw`DVbQClfq&B62tua}tJRzo|uH6lV&zh=!&`r7rP;mH!#~ZnZ85SoaNC+FMGO#7Y zWN-JFIgM5t(56j|l+ni*%f(0I7N61cI>bMluxVgd*&d2!9R%)0H=f#h!gtc8Z7jhm zWrbO-TxDctm9J@24piew|CNgT)hx+)W&d#4-*9!U4|&7^JMA2GnQq6W!h;kV1b z6Hp0n5l?W^mtqDy%$4OtEza==UL*MzQS6LCFJx{pX;315ziA@pp;VOc16qQonXH+- z+{218&C&AKjPDujthPP3K+hI1i{5Eg??(riUL|{A+6L<*dBFslkR%WU_*MY;} zrN^YAJ5Jkz}YEL~Bh)7`j)h!;}QCx(}Wm%oPvt7poF z8x1_ftPSnUO>khSYfRp>gi!yoPRwS)O)iUjlvw|cy2Z1X7w~bpqNACNWu52E!%XpK z83runBLxndT@WTuHFmY?GhvTMMRDM%0G0 zR~T-LmS&z{rWuc$M=oQ4CNQchfp@Rs$;}M(QSoL<=u+r=avNhea+1RoA$chO@-+vg zF@rDYD-^aq#?b@2)Tmx{*5@n;JOMnB zwxM#?gL87l<8H10SL9mWO5)S_0{D)GcT* z#DFJ7eanbKNs$beKjt{{m!NYel1fDdiwnWnK8ii8?r%VR9n`z)C-Y#rgyrT+f230% zsj=Jzk1Va=^e7L1=W^9x%WLQ&RQvPS6kY;T22$|5_58P`26)AaU%b0@>C}0pO(4qJ zcx&8)4nC?+VUla&_1fx!4H2_DJ#pmO+#KDxY&*A~riF5+s($RV}xOl1zTlu4DtazA`VNjIQcvPS67 z&y==I$cpb2JGf^!xICGX)p>*qBB!>8_`(JsW@hOCe*yi{N4&YdkrI~i2Ge^RRU%7E zA$zf(CLBqLqP221R~*UvlPjWUYyL2HWhh8ng?&%RD#;}@UlCPUQ8W@+!p+_s8H5a6 zUQQN?UA0Cw8m=1UPuT4-hnrlzNktnjo7IzbTsPbH=>9dlG85i|#g>NYG|FS8|0n(s z?Hwf2L8|f(zZEcDW||s`h!WXZ6W{X-W@03v^SCr**XO3*rX$vrw%at#&`^{{RC>H+ zoyA-+G_3j-X^sl7BVOBUE!IG~(w=^Q_sEu7Qw42bJlH0qQdT}t)TU9Uhj(Ol?~^K` z#Y28nrP4U_PnNw4CU214q}}?@MCW{H23ej>@H#jRn#xby(oqm_FAok)iHy_Xy6oVz zcatt~kU@ULZXy$#g8aU+y=UYhVURnqT4AI9rsNRQTkfyDE9%f%o_vwi;zALFLn?0k z&5Ml2`{ToT`bo^vk+}!P-8%p+@gFea&hEWms0bg%qh>x&)9So&XYp}r?e5&DUE?{p zzRW);Xq1{ht<|u3Y>ylDVZlA7@V70Bn>e{#-(*nSYvLbE7fSz_9(lzfw{i z$2w_adh4mmeLC2nwiMGV+?(v^fX97wZxKImZ%|Cc*dM6y2KJSq{CH9BTo!364sq+XwW3CHEiXcBc+Ht2?QhJFeKc^p zHsrJ?E_lR@M51J!PMgk8Bn}RwVp$tSk{BGc1Z+++m$I?(NZFOYNy3<2%$nv5Gs$D@ z`~21P?;uFvTkQ~!Sg#1z;vAs%bDm`rVstgQr5>aaBrA1i*^i~q_xjsAAz`y_>OU2j zws9%>M@VWq8)A4v1nVbggsn0To)T!271Dj1pHcH?7P*jfChpTTD6>^i*5f+DMa*rq zav+tiv#A0~noa;+++G95PD4woK^KPk9G8-klAd{cJk9n_Ry2RK;X^tIG8Zq>7)ncU zQ_vpY8j4hXLPCV)B<|+Cg83}LL_g^8p>`!M;@8R}SD%|9v`itN0(gAF(FUt>Vxv-* z@e_R*MW$!>yTtj!M5-3|#|SlI6esREu(;2Ri*pi(RMARV9Zzlu3Dr6|#P22TuAW@U z!6sCCkBk3gx!>>Bzkd(oAxxg~&LlO$k39qPM|-Ys`7?#*_Fm1ZiyMWew?pQe1Ji)i zW$!iq!B=;W#V||7+|kpmhrJS*lXP(ziae3WIhTj_#wouQT0z7QFFMf7j{7puB<(l zs&V;l$}2zWplgA^OgaLA6d?8;{#y(s69cnGvJ`0TW*QqjfF%2mJb*s^-@7xUIYx)t zUEwR{^~nEsCjH=nn3&irD5eL(Q;84Ff^q9(1EwM7aQo4RUhP(~K8N<7Z4Xs5%jZOC z=)~}%cGLAW5X?tw=e-1}l`PGF;Ybl6kWA90aQqjC`~=ufwXUVzNKyCy`cyVlAINfm zyN$YXjgvKaP}ufxYYt*B$8dT2=B17%>8ua^jSWbCs|f$2g?Rg7rOSW=V0>4y;|m~u znHC5|BZ3Goz%L5~ocRG+Hpx`cKY1o#hOryD5YOioeY}rz%ga-W=h1CK_%{*D7}`vj zgs3F&{e*b9km=2I*`@`j23%YshfM5!=-RgjS&`JjKYCwVC7F5dk0F54)(i4J4rNct zUWHz~J|D{TKr!HS`yFm`W~RFdwD^rHFinjNb?H>byH#>5IR0Bee6@lSL^J1j(@M$A zK#jp5S)du>jm~U{)1gR@LM9pgU|FxYo!6wxJ5$2IXlay`>kaYA-XCGb(=bVLt&e0R z$nPMJ__grmH*3u86Ocv1YtD75p^iYv#R0_A5bf&$=O?L9lte{jzGljELLb;gMy6?} zUj-eIDoKwqyabZ%w+30g8;){P$@BDjd(9d7e)Pg5+mTPazdvF?$+4XN3YgF^sOAk2FDA}0nLMA6D~ua z#r=;*mob@BKHi*eH&6&Zz%n9|fqj;cSU)Pb`7dcu-1IM<2yWEtfPoqwZ|s?n&zNU3 z3TECoR=!uMc>(w%9=&rQ0O4Mx0{E-#5W0q21;pfx0@6xfwbfp_OCbzavYWr-kd+nC zG93MHE5+R}g%UtwmCU@87is<(iS1mINpmT_U8{p(-Nhyj3BDwy-s#sF1FyIP!)^?` z8XMpBFEI+X5s+em0`7vh1)pW{z@WSYFxVO#Po_>8vWZ8 z;kANULTw$jtgfu?c$6|q9w^cQ54>XtI8aJzx#YCrd5KHQsG%Bu) zU=kf)A1ecfPulXsan-d7rXml zbay<;X~-BuLhA`tRO#*x)~^SVF_!>hHw}f|g5@P6X^9y%vU7Y7HV|BkAnW5CFO~NB zUls=Vd8sGOdOz7X&|66!!}>)EhBJ-+Q!|`>@)>QS^Y8CKChLJxqD^wjnu^d=4DJxE zzOX+EQ-6K|E-ya!-F4EXw9~TtWxSbyG?982aGO@S>4QL!Wf12-#<L?&$rGuSBmK!7>2>jJ6t z+MmvYwQ3bx?{g`l2(=GObv!g@SYVvCZyE2V&bIOS$*#+!%MR|{{|=_`LlpYaDltp- z-)N=vU`n1XC2y^FrzIRIuUkkc;D2gCaNbyleK{%h3%d$(6p}bKmHXx-)GKUt>K2i9 z-&82z+mWfiLDJd&pHKt{q@zCGiSBjFV7)!C79rDu3k-@luuM*&#dOIw%BV-Av)8fDQ>ZKQJnKZTDkM886HNNuyIl?bFjx?ptW*_o05eY@9+A z;uO;8o%b8Or}Z-aMgl(iva+F+3V+%nt3VL3ri}rIX;%Oi?G&dygN0+jkbr8sO0mwJ zf-uqxeVmRIX4^eIlg$4R;_-`7YcU2DnED`?6P~koc8pk3KY0V(%%SwsjH4&w>B?>QaGR6=`IegE1P8k5w2)#1SAvYoRYtiUQ!==);&D9 zUf{JkZFq4j^97jZwiGWGo!4#3CT-AI1N>lzQzB?4R8TY+` zoV)ReE~AC+`CejiQ#Y^JCM0K0>Aju}mf3r<=cmoq|BCodI)S8_*&e@)7HYsY&TD3f zx6Bk4*phv@4g`m8nSs=h^=tFwW{C>|8Q`oLQFza-%)oR45)d*gzzn$aBm}m^Gh4MW zc}?E#d(ZuZgd9L8t@G4+!f=#+^3%l71ol1{Cy%{D)TygTZYw#*;dL12lRL7XoR@6L zjXdzWsd15i(g;aS50}kb=uy(sty9^+JdisT@;d6R8Kzx+Z9ZoE9KS0 zgs830asfOfFwTo2zUM&aT#WBZR@d}bj}GbK$lPt-1N~N26e|E?2nUdc`&)3G52QcP ziK1RbH>%Bn2D@?hr>(9MXr*;qPbFwG(m-6J^vJ34QKH}jK^oYE`hEwH}{%T(BLn^45jc^!Il>%9IBEj1vH|89=8Nz#n}kR;dlpwP*9go-yw&~n|MvH zSn(NqO~rA{Ui2Cl!@Elm!H#mvmfw*PFHhE#iP9(!f>2HjJLBDFWEld{nNP!IUD-k3 z4!?Q5u0V^!^LhYbPuzoU5zkNE=eYQ>WlX6z>^GT5Z4;Cvh^}#~wtoGW6PRV3DubuW zORfF#Q4~UqPg6jDrepr)y)`>(;g>DD(J%rwELISFc1;}!a!q+t6M|H58WhS!aQOfT zs7OnK1Qnv1_VSucE7*x_gRBZ6?_8)wR@o`wd@tXowr4d8fk=C@SpbN$Be(=v14Exh z@PX({zYI~%@~IhPvgXncZqvpm>7)vrPd@F!M-CH|6T+T!)_Ne=wXoxsa!mAzGbK{F z3E|yDk~eXw8cUj(&y6TB9kkF@%t1mH%Izi!yIB@U0DuODK7H$95AfU)9w(U2+k@@G zp8y;LfMGr%4#BPg%8K}Y7k}?z_A)qMz^OuK4b@>%UyXA{0DO+#zH{km2M0yi&AYyK z)52auF$-f_^l^&+{+Q=adcDK!!4eQv_0k7cxEDDr&%P%~SkP(u$w;cK&c6!~H_Nm> zUb@=|pe#A?4aN4KF4yZqoddw5^BtwUTu?m_)G*C>)hB*H>#alA1&3dylZ=rPTMwaQ8^G`($icJ1o>FCeQlyIN z{}sVx$6)GLYRV@Hn&3JJ)b1!<0M7_r7#LQbE4?ZNthgrt<_f;vdeLMq_~JWEbnY$f z=@sZii6lyj4(_mO1JWWjBYNg?fIYJK1YFXe?fo*#P8IRmu)YJf#9NM$5Lv5 zqyO>Vwvr_j{+a^03IzGlGw>3FmNi4K?niF{{O~zIabew z+SA|o%L2`x29));7XVY*V|)c%S||TFK^Yv$x(?IW1u^$FC>0pr1?jDJQ?(VQPo=;J z$5?_%x&bEXw!+X?09}u=M+0FZJE4Uj4x*0SrVO?Q`}6ZRfMe~uJCQ<8J%O;0x9UV` z0(S;k$1S^e_+imghueP&LE8oR;p?l2QsCtc@S52HV+zxoFqEg&)=MC{@&!^nN)dP; zLm;lJ{O?mK(WAHC{onz?tMvq{m%Fg&Vg7`3RS9s=0xT44#fZT~r33y-nb186Wrna^`f7?|~Ejs!xpm9h!Up7z7~tfmI7g+-?gc z?#<5uQEK{*=A`IOuXA}eHA7%v%95n#Il~_cO-B_swq^QBN&Y}^P~h^2vu40 zpuc`#m}Y}Ka*9jdXaN?wq)pbMC5Qq8hZgjp%5JVs04|m36P)!{ZR(Q2(zFl#25f8Yw=x>k0J^R7XAvw4QLI3 z4?+)H_rD+jEd4z@I7Fhbw3=UB2`uOt-UO=ad_S0;Nn^lrl)MNk4_(oq{phR}EQSu3 znn2GOu8y}F45NMa5%}~_@zOFtrndhm>5&Y>{7wM1%X892j+B^F*5^DMP$oc~74Lz4 z=Cr6RL6r7*cvQS>QQ zwBaRXS4xgBZulL!Up#ArVa9}Sc6!3#XQ$p&I+Y2{#v*5Ed$Da-ew4HX}&CSB`??G@#G{67)R~rT{JL|K77&AzWX7QZ$zFtkk=Gg*eG; zYQc(cD1XTcW?u+w`Ee-E5#L2Ewx{k3>}-(DAb^G*Ji-noGP)fAss!aQ)$cVgf>PVv zyW!xIhBe`f`x8zFN|unDWDLSw2=eye|M_{0j`bMO;aAb478#1&Aq-&Cg2s6rX7~rn z|AQqt#hV}oY-7*w1Z0P7G3DTF%dv17$VVLu!#I<{G>1yl4C&)v?#}qV5_B+_S1)rm zTmNQQHOdxPm~qD6bh`7=^n;E)SbGccQ5XN!X2pEm+K3fpm6<_tqqE^F(APXcbDT&L zb~{BTIgFMx14Q=4pP{~nk_^73Y4Q`xG<#ptb>N%CD?Gf(|Kl)b_h!U13((B z_(;0@%*XgbT}Uf2AYmKxpbzY9w%t^t<(Zq&6zQazpvT{M^139yDCX@#XS^Y>>efMn z>_MuEtlSNFpza4oAnt#oEWLG26W{J$^Ox7G(!R@nx@Ja1dT~o>HN`U>hn*PVy>2(hregFl{t?Dk~u3 zH|5+) z?7klCX=X`=P~ww+Z;IFy zTZRRaw4uGc;>2W?A38e<@pyZ1%uNuSh8eNfK97?)UcNH{<^npi1&V}Mt(yvy?|Ny1 z#ODX=--&3-!OctnQOOEA5eFJRM#pxd!fq*81w>8OAth=l3;ZpdSUb%e*36~6HJybc zuwW-~G>Mu+I_29 z(@XgM4xL9#*)Qe&PhRdc~n3HS>nhr(_;W=td_Gh|7`aHY!z~6aUbP7jdRJmA1BwfJvnE-(@BkRlmqrESWhq8V9 z9(xoiqe7xv)=F81kc{kPCkatm#x`UP*;A<$NiwppGq%c3QK~yF_OX+c>`Ov2mfqvk z{rf%d=Y5~|{XC!N@27t%%Qe?rb6w|o9N+EW|K5Y_eRh9MQcFVTCWEs6Z780Jq`ewf zjN)rzp=O&SH}MAUA2;!bp(_jer(-S42yx0_MwL86KC&HoG_*q|l}h`GPXbbg*9QGB zF5IggE}Xc_?2JvcJCK-@#OJY_AT3Yr({`oqvdQDGenyDA0szrU&RQF@a!)?P8&&=Q z!(+pY{cr~iHorm8Cn*kl-_PrS%5OQe!f!EM`UZ>S$DhgB=~*{FAg7h4Zbx`5DvJkC zv&a=!ePLS#5BgxVjQMkOv9=iv?lgb_y8dx0^Whv)0J>fc?tCZTa_s9ic^j{H!o}jr zq*@6(x;Ns8uTUdf+itLOi5!h!yJu0g0-*Dg50#&!gTAZEuZAv7)#fTh%BZzIRgD~| z;Z5S3txigW$NxGX`5OWUr@XzvSX9&ez@;l*fLB$MFJS57mb5VjF%HG^qZjWgkKTEN zd?hiSX0a)({6z@09Mo2mAM}a0y(PBA;C|poskc>i*c_2grvOsW?b%~ zRFZ0^wK;L8v4hC3RnGe1bfib&1zHU>VKz};7vIvBR984upPL7Sbp9${7a5*j31GE{Ouk7scT-6 zTcY+G$IGg7B|vHKw?>_o&0`Rt^o(K&>PmMhpI3QQx1K}c2tzkX|tiq zS-xZyV`Y=!#o)E3ET9>z8&ce@9|9RdO}(3qFf>>$Usk)X(45^)bOg>k(oc26MF?rT z_f_%Za3$2N|?mHVy`9vf{v$a)5zD6RD4-vpsmwNcXKzYhbQG@eN#z8 zA-8NCDZK?24Jl=IS_We)Mtq;cGy~f#6T#z`CAHGIT}xDo5j^mF$UHeDYXe|xn3lgU zD{`(WwF)=2Wa^}pGw>333+p97FpQk20#iyl6J3}))Q3J%tKBy|=ua<<#1H1YW=XbF zXj4#V&~KE@Od_s=2br;pxan?b_J89V~Rep}Fn zculv-nUgL(Tl})?o*!Y%bw&bDk+aj>4cnThpFG-dij&YVMr3Pth_lKUAFobE^^3fj zPZcSw0j^fayy_V6Lx#sD&x9BXU)d(DhCnALd>YC^0{4&TbXEoNpTgw!U}EviDb@)t!!rIjPx7J=OUZmh|d5KTaR7 ztvEIfMc2#c%D(0put+J}9(C73#yiMV2^lEOaY&*Gc^!z~9wR zgB8q6brcA&cU@~T=YXi%GfpRV08S!pC@$#IUF8Ae$G>!5swYA*;9H4wfzwU$Lp}?1 zF+@4AyULeLy=tPPikxlok==RLfXWD+2VDrO*x5cyHqC_&o$pw}&NibKMX70M7d+td zmAk&G2kxoRTfXU`)GPLYVt)ISGH% zOKeq8jkb=HLLx~e{4T!+R#DF{j_I7J6`O~C--HVJET!yQT66XJ_!NEeZTs%g-)hdI zLj0RQIhVk;r@*4bj~k0?xesn2?)TNnU!E^@SrNxpTh+ENxgs(}*pTG|a38A69zVc9 zH*}%VafuuN9K@jN^$(J@A8~413HD9MLVJWxY#aKN^53z?Qp_~k?ik~PFL?10kB=V| zLIO3U;XDmheq|*0)g-%PmPOqLUgsaxPQV-Rzgv!k8e@66PQ23mQMZgty<|PFMSO&! z#mzQ&DZ!R6>v0JKGtECYlJVt@WcdM4wXXViD3X9*7GqQet-*?V(BktK(c>+^{@|z zIF`e_TV2#jqs=tlo{2|aAb2s(mai1J+g|v1rz1W>H|`4Z(X4tAP41S@Lt!WHY^>9Z zU2$XI5j>prf`>_9Vw<+3%hSxdqjWRJCbT_N30k_r9#}a)FfkBQdj`Pmw{aFLr9DsIhTcV@=5p{f+Qtte z*qxF{htpy(l7%;3*BapBhI(`LG{4-4xOnQCo|4hZ4%vVmOb^0no%s`IkSoLMaVdA5 zzCp#$0aPk`4YO)Q8ktu8eOJIE?~&Kd)yicA(v~MXQ&rcM%*ss@S_bkbzJW?}&15=^ zF)S0vZ)ne^*Lakv~-Tb5jZCS(mt2}C`w2-CA0?S=B@ zu$c?zB|t)&x>#+64v=5XcbT+qNrRL%P5G@K40A+c`>T_wb9nxo&bRniI<5r5Qa$Tk z+ErMzdKCjw^bn6|sj4aZ^l<)n&fko@>6bUNL&gay%~t7L)e|=uITZkQsGT0Le4c-C z0snfuk@!nJp--w^?1FKow<7s$R(ya2hH0!&b(l>efLHg7nwyXZFaB%B^?iL8#=nI% z3unYBe?eYqj#l@+Sglq~Ph(kj~+bC%19tm=}n z!t>W8;EL~e5qx~)UBWdNUuAN!Q6li^;(=x^UKXw1@324pVG#YS{+e*iA?1UxCt%{DdyTi< z*nOhfB}B7W&mroPBoBrR2qYB`09hvgp%jPX(9z1<7xZANbqo!cy>m>@r|L1|C!uV> z!TVFsKU{Bcfv5G31++8kJ7X)`_qg2rVl9m^f!ZG(XiF=HrKgu}8^=RZBZkTN>`8s& zH28g&;chHB!&pcq-uauZ3d4cxP~bbbp3B`~RVKvj7GuVN1D}1J18vz05QRc z@wUMRB~(T9k!J*iMP231&P5gs0F!h~i7pu;ZPoTZl;|)H`qycCB6|+) zKTe!3Jo~Q(5I>sq*J)iwt?)oAI_R?O|c|TalZ0q=Az?B&c1!F_y z!!2_79{`IjwXSUwwg>G2@$KPO0KqCtnXyq|ex+59n4THChwz5lZ4I{xb?0AQ`w93# zKOM{7&)vK3q_fF6HSe{0Y^T}O|vxr%b*vm-8eSivj00Iy_|vQuTu6Wl59B2 zM={xnb#@VCFpZ?PA%^5;H#E#nfP|LVO8XOIkjL`~orVyPU&Sq&we(9d0?vi%nV9*X z*kAoR^yN1d7=Us4>vtpMym)&u_zQh!`Z^(l(T$x~_PgsvtP21-U(a=W)UQA2Ok>%+ zP`;k@2KO;BAG0|=(DCylNwF>cO#_`MUf-_m=$V&7(KMm@r+!&WBY6$P?#^eC#F6Rs z>S~QxY`%#%KqLJi0rCKN{|TIYi>)hBj9~5v7kiXGmblxnGD$h80x{1OEhkp1C^5~6 z@uqL!#o;mXg~v#$!yx4jjk%1=0rQw&9|ig}K0PQ*gLhxb*N&_Kw1EhMh4! zy9O$tV+KlmG^rn`g>D8ih7? zZN@#2BX!wtM)xhaj!C9_yAYf>#0=i-aPd+~yiM{Tvp%d_1z|v1MndK3Uv*yvM9&yd zlWfl)`VJdvZ9nYa*R2+FSRp$pGfhVRMmY2FJu;5BZ5aeDceunVZo!E$$BP>JrNlB! zelO%#O0VKJ&wn>pZBM;>D!fkYYNTFfMnR+_fr%&Kl=;K9EG?^AznK9Y&xQVMH@IuJ zz2i{Fz?vbn@K#PQ{~evtnQI*UE+`N{Z?w52Bf~lTp3%b$N7oi%)B9LA+3Zqqt0p=9 zajSCg!QJYJ_Vf+mTiSYa8HI>sWNI{Z0x&pCW0`n#408|AXL2-`XJ#y!y^d7Eh|~hs zEhTi=C}fyYD1GYifmU6#`B1p9?b<9b$*kjD?cVc9 z79pQ~EY}FGn0t{D?_n#cMBY)llV;I5 zCXrMuZya0CSr=~s4q3%Krf_Cq@nc_uRk2Ie_9ihBcA!*Y2xq>r2Pz@1rk2Msh-xK` zG9vui|0cz2eL0c14RZh( zUlYA`+LI6Et``&WbmaO5D2rzkKca*`|EDJec@D7Q-g&{{#B|_r4|4TkA@=N(GSiw4 z^J8>HAavM0kb=?bTqMY__28v}@GT$lrEk_d5)%`<>-Ih7%Y6#l#@7V;_A}noH)v}4 z(juI7XBJ~bLIkbq(Fw?h_zr3})4abWtt#)>WKvfF4t~l+0_{x?k|8CAjXZ_?fDY$9 z^oruy5$M_EJs^066_W!necR$(=vzr&`id5nyG?q;rWUgt*5hlB(^Q>v<7w=Bj=ngO zWCIe2u1XKvdd34KNYLR+Q~C8dvNS4jbA>lLi%w%W%7}ho9+~>Rj9Cr48Qw_pp`BN@Gce6zp(K2ZNZZO zw-cNv=tRwVfxpfY>diFH+{1}6&VG5w>`9#9#~Y)3a&81rcLl|9XcVimqNMCZO!^3P z4(^~^gi{=e3;BENNp^Eydm3Dzr_e-zgiT%-%c;SJQ$Y3^c5p$BeoXrM0FX%=K1k6@ zZa%T@Nst6PvC&GnAaJBgP_&#Hdi~nHbicy+g4p%etaHinnKGb zdKB55PgWk!9iX<4gpFme*59#X?bng`Ni`IAY zNg{N; z51f_)Km~oWIiDaE!uiPa#e4ACf*cZ+R||T0S@SK+0w1%Fa&gE%y#ON0-AeT_5;(*& z+k~z!b>$ffAfd}zxudhQbFp+sk(K=JFrQj=$Q!b*9nZXFeT+WL{v=e+Th@;`B`GIM z=!^S9&IIzAu9>*uF-!88OagE+vnB}-kzH;jOjAKmw%zjRUuO{^2oFB-97)dIW$Ug( zz?ZMi!d^Rd-5cb8E9Je;Tb!4At3e$GiILj--~nV_KeNi!BCMW}OEvzTgjBpMzywJN zV*WDhe4yTNUgSg%;|H2N=aD@})u@J=mFrNSaQ)0l0%ZFvhDGABpJwp)?{OIjbY96YALI)OsnYQZ6_B?LU7>MiGWYQz<{p;8qX>TgUjs- zUVfhfoaBcp+^s_OFk(D#A$FxCW>_oK$BG*#XhyQ^_0o1b`Mzo_HE4jt@-uL&rARp& z!g`}ly{QH9Mw?5$v-Vh#d7c4(5TJ{0ppTV9>pg6jwr58*rPWe6%6B|M^6G7O4=^1l z6$DAh8!dr1qZ$m1k1lDtg?xP2<8bD?KihG$OC}9q0&_;e3rOG)W|p-eL5_a-ou9>s zgve_k)@Kdq3ssHG+;dGL$7##;7ZVjFR#wKI1oS$KLMgoDqViShwfB4Y2nAc=-e}%u zVs5|yqX!5f*TlB6+=mz{nM;l!3 zK!@4{6{WGZu^wYEb2hoAC}IntOwbym1W|DGt_lm{Ko=nB1ka(>!lGkN_yYg1$ z5^@4tuER4SK8&QA5JU4zxD?E^AMPkliq$h_7u#XN>>3?0FOZ-B@R>0uY5suhwE2ip zCwQ3#1ivuLvgZH3tr{tx(qURMD0ByQ1g#Q9G_uy%mZ8kSE| zBQGpJO0EIvkV^y2cK3>Kx(`UMUOa%VlHZ*2aHiJ7Eo7#z$6=2-NgK333s3;$t=;Q| zHHiq&L!m9b?vZ~TSo+WHE@FbhI#IqCdR)U0+Hiy}2MP&H1rYm_)u7elI+-9MO$~K~ zv_ayD!-r$wGd~8Jl^zqws1N)b1qJr?~0sK$)e*7Vl%+< zIsQ`~Sb{6M@WPv$W-gi9;f^5AhgY}_XU^AES(3a(R>N@UtvhQU>Z*4U5SdEP&iO|R zhlw^KM+@ed4ws&M+Vfy;1Q3%4hdI&i2u=C+dPY17yMVNbyyQF+;LNi?P((&KbG`I2 zeqCE+UpxR^4t^G~^;el0x6I{VTIAVG0ror`NXwQ6VIa1qlB0qc@xWGZ75l z0FDw1oPXE_NRlw@K?xD}dfDX%Vv8;?pnDY42&@-AZnY(J<07D?KlL0@NKrYMwvW&? zLZ^w@e9dgLm?U|RRNI^ETa0qICGSfY1g>pF65I?kl608fCr)(f+J~$v4p}Wo$B4Fs zVO~^_H`P0st02=iF&+lW zOJ?UznXp`i6aVeS0PB;2(~>VP?$V z?Cp&9pf|lIgZYJ=C6Ja|Wl?aAv>?Vv{oh{)z1ht)ng8gr@aN%J_zPD_CIsaBfVu<4 zGqa4TiRD9BL=Au6pJ--CJ&izmgZz$;K@R`RihBCh+4tW;*+sP+_Ypl*axe(QqqSW1VD>Xi~ zmSo~yFckWGu!WmFrO^vSg(2b!k1l_S?#ee7s%K0;3VE-quxh|Pk#k?sb33wHf7~q} zC6MxP9yt=|rhu({I@wu(h~hC1<6l0zW|8s?k>r)f{ie_GK~s`^sXn9eK-}nCN?QoQk7WNEsA;|xgC~1+dEB}%VRO5I4epQh2 zH#9P}Q{3su_FY+bW#xw-=#Be;p1fcF4C#4LM#zH&y{3V(NCBdxJm~0_A2lFxR4|Kr>F2*uah2(-E{`qRyXP07)cwe|(PMmWYQWM>RGWsVj0 zPvQ`XKRf>l^>T$72}{;0Dp^ zR|tx(dsS99M_YdCGhtHCECyu(UKaQ5%ryT9e@PPFV>`JXjEaW%M1(J)<0e7}cBsoqp%=;*^vAWoe(%y0zJxQikEFma*!3|1hHm9 z#A4nVyj)f#@%EDn*^0qOxW6y1{G1)^2cY>;;WC@H=D)8PrU&2$H|Qq35+44_Jw^#Z z@%es$k|3Vm4T@j?aLrBzCZ3sXu1;sy&%-|I2!!>cDT^Iu`i?EBGZH)dj881KstqpH z+?D@y`IU`8Zptij0D(barhCK?Bxr6(x?s-Q<^Y1xLJ0Ac3}c!y+rh@N+dN7(B9{JI zY7n&FYdA-iy`Evn&NHhpRqpI`GFnbgY}D_ZS# z3W3V1PnCs##BY|np+U6ykeKq{C~Xju@_p8Gun!rKd?`r9*9M>=n{S6aVS%ZgGkHj{ zed%r7YKoyc1$+t^tFBK(QNKtFxBR>oq@+*jOC@4!41+}yAq{s|@LR0r(jW>g8d$2G zsj#XDBhOWS+8#WIyR1%JPsHv!QadfZdO-N z9Ca=Sl4T&w2=4bH5VjTbB>63J3r2|8@BC!h?T)%6KThA~o%($fo!DH;b%`xndoNur zxbxaHM?=3rR+#q%zQwTViF)_JZ08h-y_r)DCHs#Icc)L@@eBEy%qRYAGk@?!0o(mF zgH<*a%2VKgdzM1-Cr$S(_5^a!MQsaCrMNJq-PH`+ns&TYaB&}MHi}Jrf!V2A9)tGM zzZ)7X!!8I}v|yS9uRPy*gU$@W+Y~Qp_dZ#9fz|~YkavU6Xewm3{8e*KXTT-57S&J} z%zI38j8tthY9RwmQRNpztmPOJwPMxFGp3Q2kbxw{NskO33eaXUDrps?hA+cJ2^~^+6C2(oTscbuYS9Txs=(ZE%~KCp~;$vxOc}#G*gI%Ok~MrkvxX`HR*JfM*8ArsO0#9 z3;9F0{k+Oc3sZ*HZiedbw0&dJYE&wV8!{~|K^g=ko(^@t5IrlOn(Bd{vt0|an*E0H-^le(4*#MuHSpuEa#VVM1rf^#k z-G-t-6kC&f{X82EcB^XJ_sI}bC|>FOFswwZBegfy&z$JuuOYf zEPn{bN-5SKTqI~oKlD-*Cm3J}WkN++vo4O=-I+Q~$w)ui4@=TcxnUD;VPEXsl7^A% z4c1jj5BxIA7Yg(s{64+0z$1!e?5rhBh{##pW|2v~DzT^c(B;s`+@QeO;S=J40-YZX z66B0Kq?w`z_JSslIA*neIs%`~zxaqbrt#LNi{^&1CjH<{w?{e1LKV>x{KFEd`IffF zINzEIa&*kRGSj(TZ`ejl@3lKh_R79_3g5)Pq%TjcBa@!|pOVa4+ja!|Hgv_gr-W+gd-+(+9HSUUGcy=<@c9;=F4wBu zJ@dJM^&W+t*G9L}SZ4o>x(kc7R%55r7wA!%<(T-_ww|QZ4cAVH4?8xH)05(#0T0#4ylZ@h0VMXOQP`)b5(8SFiN1lS$dSk)$E=LY=0f z>C#{6Z1*8QV2rm=Ri7MjXThZ*4?}LzN}PO~5u5#%I+i__6|%p&&1ne<&e=PPR!?`H zY}sGJW=O<}bk1o+auTo#z6}-{1|{bE|2h?}Ut&yMJiPYY^u~uN>!ZBePNpn=o$wVu zHFe}P_TExTcWwe1$uZGg0)}Bn`N-%9T9plo+PE55NACQ_k4V)5*mUUv31f25USU1 ze>{4B%DW}{(=Bv&cBhU9xA_JR2DAlb_Kz-pV!m4nSRHV?Y0(=q*M1YB9G+dlUfnns z8sG2Tco%35r_CTpaiNPKYvBPi^`*Ia(6OXG0T|Wk6gZY`5_w!?KxiBr$I~<51sYaq zG^r!?H+TwUfRM=kVX5ip>C?SD)qO-r=1SEH-jUOHS;4veKxN_bUov7&_Se4L249oI zaoTtm5A-d*-Ed?N;=PaG6PKzoXHrwgP|f=Q#&e9^Zcb5hdR_?up&uHzjl%_y5R&;= zGnVr<7K-6xSr8G%leE8i@N~%%2qWL0fzJ+AFgYpN^3A zDsvIcH4qOME6T6a9Y#MNtU&L=k&qv4fo}>hdv3&4WS^P!L-9uv6y4FEwz6{-{keG2 zB&|0c|GIiv@fz1AWZK5PhwDw3PPOC-E-urO2U68S@&j{14$&TU?lmx%XWx>(e1==t z{1*g_H!a9!S5QnToY4{vE(W`wrSZ<3O2`@@h`E^CQcoE+)tM<%s~ydfOyhOf-xMwd z8ZKVu-7C1m{EC?|b=`qkNnS9F)I6~lA6Se8*L*wlBduR|xKfuUZ)H!$(!`uwW9(wt z(;pj?0T;=cxRKp=t6=3Vq;U-pPG}QfuBiMfj6JlN^0Bee^QDHWrrKX`uOC<%G+vNn z62KYW&J($M);88I_r#eRL#AhTXB^wS+g@ztZ97Gmlg?k*DXKeG3jxvxrzd>LNA5*9 zwyy0IJN0rrRuP{bZi7iL>j=3{s+|b-O0pj zEGS0E(czOaw*tf8;;+DcfhVkFF5I6rY{8Y_g#v9*P*pgy#vPc*F#?gW7(63tH&Hu` z`3sVIF;*t=3s9`Uj*QVh{cpo{qH@$3;c9Ml9k;B?xIp2b+*1dg@Y7utA?pbi|}PES$v6SOq; zZ})oN-i*J!0;V4bFU7)fDyW5)GE=P?ksw%aR`i=DfGJ579p8TQ_-KFvTJMqbvY-zv zRsX#38p;}s&4yUGQOSdl^8*b!YP2!hUVq#KVWh~M^H=t=Zgd|c!x9>f>j2bWwC$W(EUwsAtqq+JzYgV8@hRY zIN|MjRb2Y2=`RrvS;ajOOj|eKfDj!1z=OEB^YDn5Ly})Fh@{*gkPuZ2VfLsaeGQ`* zJy20R2wgf%H*+a4FWB`T--VUX6`q1SUyBX9KJU9o zkCz4*41WQdVITb#4yEa%=%WI^4)~R*a-N^@QyqDCQ9mD|{}E{KiLSdyNJSSmlF9KH zMx}6r?e4}3^gAfx9_1FJxt>;=H%4@4kyUpK9Y%efAUoL$dBIQ5e&uvAnnaENzK~z% zSI4vNx&hU?X*mEllw;bJAJO$pnEQL-&D(c_K^GD?l45vd{suesWY-S(RhUQmH(<0% zL70=EN7FZBKrE^&Q9v z>0gPaB<*Mg)X^C@P47f7u%+K|n3NHcux0jXCZVL~fbtgXdL(NE2j)H_TnaZbV0UQZ z%Vri8%m4vOxO^h}l~KG{MK~naf!U2`}4u%+7JLLXspzL&M{nHFUna2)aZjtst9oCkI~G5n9CKw zA;C?Ua?q6?e+lN8p_O6{&fuH)Be;cof8dm4pZC8eC;25EGFB73}W? zu-Fw~wOrVXgpnC(^E(zWHbv=k7=s&R6EC=8((PeUNVnU3YIy#6Ay`2W(jBq+wAstP ze)f91@^*~&<_dX_@(0lyK`({Bhwl9iSTr%(CU6#w2s_5;fYk!C0!R3>xCw@C9$Xy0 zHDdU0-1d9izb6gF76RBhD!BrC#V-T;sHxu@bH4|^!NcMV5z9Ucn6N!F{<0pQ2B0;x zhmzn4JPIZdm?D@|Xj9wO!V?ZnKr}j;otyd@xV&&um{_#D8F_B55@-!!+g#&zqI>D3 z6wQ+Hn_N2;xc{}iMzXT+wU2A6FtD`%>*)dv4LLa!1fhe%7&{sGGDh$n+TJ2Im#P2! zpuJ_z@wIR=6}5~gg$|v{zpfIx?eRM|uhQn?dlFqGmCdVk16`%fC!H0(CHQ;uD(y#C z$@X7Y$sLAmA^+)AFZ6kA?_3~e6jH#P0DO>8Nu(`4XTNp=5}*r_Q?V(FU`{r9)8hW1 z-+yOL|G%;D6)Esc_y>U3@+<&>gU7wPAnvvRGWTrhjd%(F*ZO>;m_^`9D>z2J=Ay|( z5Kv9{y*?K@1HjSGN{mrEmJpHRGtvSh;KoJh^*=2R{w2%6DS_Leo+B~}J|M_H&$Wbg zvj|L`h#KsHB>O{$4>vV~SX~KVul2*m5A&(%g1?CAV)N_tx}u=*(E%c>94Q?Eq$0qq zv8T6hZwmqt%^k90XK85UR8|1kQflFjcAu?5`T7v*F#{^!3W$?`GiU(71%IXF75fz= z5S;>O>zgFRtrLMcOs!Q>9tEmw9d>P)B>`)OI9$+DWZ;1Z9k~^K>Be9^^xm(&Uo+tm zn#o42{9RqDhGOOCS*X28|oXtO@^pQG-VWG{!7NyOYJKHC^@3;JKK-^Jwxe)9$Ev1YM(~ z8Hl#`-|qyZ@1|Y|+-cU)raAh?Tq@?y$gw;HetD~9SaNFg;5D#tCz;%lkjSjV!AhQ& zY!OR_ac5`&hWPT4uFcZ+dA>(8^u{BjaP9U5Dw^{B3WJ-atsNNRMFhfcfdY1b_iksZ!JX*2RXo;Eus|w(y-QWfbjSyuq)S| zkO7xfJwnlmM=>~krIA>*fuRC6)L_Wpz;n7F;t~{A;jX81r+kCps0T#B{B6iUpWprz zEsh8*)08uH3MHQ|BH8$MnitP~K!Y2a-(k$NVJk~*o^a?JD^+XK6nl#SykA*qz83$YQbr!a68~ue=t*n@?($| zq1Yn+Dav8DLE?^nknR>iCeA$4fj6fH%_<-U!c|cY;;t-S_5jXY2(2e7;<}GdL8R*A zBXz$TkiTwu86qsaLyxfSM(}h3c)-E>XNS<=1(f9sHW_3M9`xz~4rXB8g%?7ggACD~ zj^>_qs{mCty8@Aa#iwDM<}>VqM*? zY^eNmU(ob$>aC5RAa$!mgP~xlD}ujgr$5n7Vb_3EU|9~i@oCB~Wn%-J7=8&Pcmk_$ zCXq0ZQPwvoC+Zf2VO;dE?Kg5yD|c@dhVfbWc4WjK$N{7#lHXA2^S{!8iFs$0=7BWx z*Qc79w>^z`i<~XcrT7kOP0_Q7fu*Xl|7ABv{R3PmETxX-4rOY^{J+rfio+}sxi(0M z)*_VZ_e%xkC1!!jQUirA4XjVjsfQN4$dNF@FX#Bu`VnWM%));KvPiR6KnvQG8oCj% zOj@i$qI@(ez)H45Pd%JK%{QL4%RL!O|7f2U9T|Aq*(Xc1$oD<4@=xW$I8lhdu7N8S zJR7L25~ahGZAGkp!=w|XwjNwn{is3jF%j&Ta+kimh9*`ya3ALJVc1T?vh!}Lg7@7f zy{*~*xVclA%pi=`+9#fpw%w%hxhz);`^ zQQ3W{`bR5wXi3T(+-Y5C!2%`NE9-IA{P*6l`B(y_=?(?{va38=or5;Vr6ik~8|8?p z46AWdKnzg30;NXHYFqZ_CAf)dyVE!2|J9%K9?Jlj>AyMv{rgy^XFE`wwGd)75Xf{F z61+Qc57WZjC;c4cl1(J3rw zw8MZR?jRS_o%Du#TujH!FQbS!`czV9bZY#$A+*ju;I_k#W2YBC&B4d{6KaW?DXcct z+8`~mA$$BeGn%%fgXX55zaY^LW~x3`3+`+vnu*tK_Gg`pV%s?DZO+TO9LhS1TYD;E zoOU;i6v1RR%vs*l*)6CE2+FfwL|q-OwLcq_8SMgEP2bZYrf8_?%73CGbK2 zMTozTY+=FN1Pe8WlV`Feliw9L$Y#MrWjtk;!#UH#66}bi5uM^HdrqF|X5nUGCB=|? zJ2iIO+;?w(!AM!Vv7s5iaZdJ8nFJ5z%eZvvwa_kKO75oT3S@4M?97~76ko8sQkzm> zUOuAgAZ8uRdai_`t!lUBT=F^BV~>S*%8J6^pN>MOmQ*XB_U^E4lOrW#8z`#W*F{cb zey*JHZqc&hmSce`5uV(2MF#@)|GE7ejRd#QE*`S28?d=at0rh!rgY;L=QqbA1UQ?? znp>enM1Q52$s7@jnsY*45LKji-Wk@v;+7_8>c$nD3Or&o8BOcszGJ7WhM@i;QcFNz zJfJ@*Ca~-3e&sGG7C-N^QWZU=-#CK8_@;jShV-?nZ(;P;&Ly90Kn?G#TEc;tzj7S| zT-tMed2LR~@>-vZ7+V6ZVNGl1#>x2_pNTJ$om9iQ7|N+kk&v@p`3^3*d2xMIC!Lo0 zSTNlth($>(DYY*_BxQ)v%PsB{EO|1wn;p{+#(&HW?2Ow%?W;2t{X{>MruveOYIf?S zD))nfCC}QK_AmJx=cqR2mkE^^EK^8~W%0!vE4(Kyn~G!=NhfcR9=+M#_DI_(V2i~W z^`;suJ@$m`*(BP=B3RsQ`8ouD=M%>l3=ordhvB0-1!7Aa`^b#??@@r4`X)R~f z9ir@-z=^Am`zZ%5eh*D5jR&6&(}|d8xjVj53k{n-G*Cr#5)K-VZ~er=^q22+_iBMr z=J`}^6CY_4_OkTlX&CHvsl{sDPgWwCrZ$0NHsfi}9(Cc3(ywF)bN`cX`s1M#?^JjABX`&=a{nw-hC@A&>BF0^tWoPjl8zevxf z!*;}FSKf{;>RYkYbBX(GTf|dO-1@xX&327exOg1ygCFH{>0?OZ_LO;TV`Ga zJB?pFU}%C4gtupu8lxc~%O&0_eaHH-$&rUEC~@@TOvqAJGJHvSZeX9x<#Y3NstG&H zb=h_j)GND(-avxg9{!Ol(4-7pf%cn=nYV397wnp88dos4@|F(}&at46JN|+X^$?q> z!OlTB3KvE_j#NrcyWMP=C2|q;XT|l*n0w$p#9-`=Lxq3VCI1 zP#}*Lf6znFoV6cQz(zQ1BxhlQFPbWL8iaU!3SQaIp~~xe>Y!IUNhw$>@{;qPwFVv9 z)WG_2sHsC{9)$c}_df{TB{bn5VG5K%i4^y@pDF4SfBQ3NM9=LuQk)nH`DfrSF;s8W z)6B(45N(u+i11bh?%WS~iBr~*bOagr{cSEegP9en2u_14Z542Lcj$zO>kw@2hGJ=8 zcL^XW)C2spHt+zuW}XrV8`3&_a+m|4lC3~MdQGe}Bnu&V@KefPF!rz#1t&E9hC|KD zQ3lW$6XGC@c^Oc&*>xRFv91qUpGK0F52*fwjLJqdU;<4a-kcyerO{&A^bJk5L=~MI zj8B`bX5Oz4spxj5c?P}-f`bNHbkx`G05r+@j2rp$Fdj3HfYXTuD^;W8m)FsLC?^7% zD|@&`WkBuq&WspB)g2WM<7R6655rh!@Em??c!e}7U5Bl!T8layE}6tX_Dpbqw54K& zM<*YORiV@*$?1>D@-0ZUf;!(pcT}nqg;2a^xJFakqM})X@#7>}=X*i6>P=sL20`32 z^8odS=ZH8Jyj z45^Q)M5n~Odn~Vn1FpqQ76vADBO1c`6u5@YxYXKoJ$#C_?R6l%Ui*84XgV?jeq z%itdFwJo*G)Qyqgne@)wpHK@1m9B3&MEDgXs&WX|7=(t%GG3Dr z)u98vlSfN7a5YVzUq;SAYv?t_MTWW0wProWqH74RIJ`R%_TgUKT|Om0x2|!w^OJf) zNFCsMGB?(=_0GmiSL(X!0of=K^q{ezApvn8Wo5mOIv*QBor?7F=Mw0d=B~00t9GugI#X)ro&ZS&J)u z0lNn^^~-*65pE`5b2!~0lVYpcDP|szB`jL|_c@D(7OWY+=vWE*v5*QUlztgB z(GYt17cY<>m9{Qc=)8p>jNk)x5T=S73T01N<9Ii!4bx5#&1Aq7pOppAp&$*gr2rU? z=jf<7KZPbF}u zD8^unIh&7?taC)pp#Ox~?@qG?)j9mRao_V=8!)BtPfms5y@<|%e~Axo%R*!eta&D2 z*NZ>=D*G@5o}%cp3Wy~{xSSPhy1o5IP#L1)R$z|SJJ=a!M_-{uo+h6xfEZac0=^+? zE1FXZ0HX||BkC*cV25x*nqr74>_8b*C@Cn8h@z-Kc-lW|{aFt*v~gBz&KZi-!G&(u_8^pqh0KMu?jelJ%iv7tgFGjc zgoU(zp)(IqQvYh!vIhc(5PK(KQ9NDJWOZ(CyWA>Zor5r{!eU56kOw8Zp=oH6z}D4v z_B#ktP?;B^1@)RUinF%0I_ln6cB#Ifgi1;0gQp7{TU8{Yc-KEqX`NFNJKGglv~|vC z{4Ec@?F=B)!B9mKLqMzg6oKn2N7sX>^(||SYs>BeSH6e$XPnlago>s=<8EGPHya&5 zbmc&TRK0DzQ|H$;1y)-@IA)w*%+ebn_mzX3v&)E@UpmUX@W#$3

jrK68Q6vg?-Vt}G&FfV7$<0%^mELdl28{@C;ycpF#Qbcn7;z| zjvJu9l6GQ*@nEwA-S8O*Bl6O^hH9SOE{l)qfn?w9f>`i=heR9Xz9n8Ppxl&j3Oq;K zTh>NpK2u_~Oz!>#h-uAgYuxE51*ZM$&xr+(GgqnCV0;)ErQKIA8-#1U)SMfH4#g2{ z?h7r)ovxDBP{7TAQ_ECIbcWk)II%~pm?n~tdJjFDB`$>J270@?*UWAj8 mWY>N|?eWm#011!Z+x4SAS1BBPR|StRMn}Uyz4)YE#Qy@D$kTKH diff --git a/img/Screenshot 2023-10-07 102222.png b/img/diagramdemo.png similarity index 100% rename from img/Screenshot 2023-10-07 102222.png rename to img/diagramdemo.png diff --git a/widget/diagramwidget/README.md b/widget/diagramwidget/README.md index e53d09bb..2759c8b0 100644 --- a/widget/diagramwidget/README.md +++ b/widget/diagramwidget/README.md @@ -1,12 +1,3 @@ -

- Go API Reference - Join us on Slack -
- Code Status - Build Status - Coverage Status -

- # DiagramWidget The DiagramWidget provides a drawing area within which a diagram can be created. The diagram itself is a collection of @@ -27,7 +18,7 @@ or resized. * [demo](../../cmd/diagramdemo/main.go)

- Diagram Widget + Diagram Widget

## DiagramElement Interface diff --git a/widget/diagramwidget/anchoredtext.go b/widget/diagramwidget/anchoredtext.go index 5109f22b..53f53f92 100644 --- a/widget/diagramwidget/anchoredtext.go +++ b/widget/diagramwidget/anchoredtext.go @@ -4,7 +4,6 @@ import ( "image/color" "fyne.io/fyne/v2" - // "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/driver/desktop" @@ -42,7 +41,6 @@ func NewAnchoredText(text string) *AnchoredText { at.textEntry = widget.NewEntryWithData(at.displayedTextBinding) at.displayedTextBinding.AddListener(at) at.textEntry.Wrapping = fyne.TextWrapOff - // TODO After upgrade to fyne 2.4.0, uncomment the following line and add container as an imported package at.textEntry.Scroll = container.ScrollNone at.textEntry.Validator = nil at.ExtendBaseWidget(at)