Initial attempt at VUMeter

This commit is contained in:
Martyn 2021-12-10 21:09:01 +01:00
parent 55971adfb6
commit f4e556aa56
8 changed files with 575 additions and 0 deletions

20
examples/vumeter/main.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
func main() {
a := app.New()
w := a.NewWindow("Diagonal")
level := binding.NewFloat()
levelPB := widget.NewProgressBarWithData(level)
levelPB.TextFormatter = func() string { return " " }
w.SetContent(container.New(layout.NewHBoxLayout(), levelPB))
w.ShowAndRun()
}

26
go.mod Normal file
View File

@ -0,0 +1,26 @@
module git.martyn.berlin/martyn/fyne-widgets
go 1.17
require fyne.io/fyne/v2 v2.1.2
require (
fyne.io/fyne v1.4.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
github.com/stretchr/testify v1.5.1 // indirect
github.com/yuin/goldmark v1.3.8 // indirect
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/text v0.3.3 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)

105
go.sum Normal file
View File

@ -0,0 +1,105 @@
fyne.io/fyne v1.4.3 h1:356CnXCiYrrfaLGsB7qLK3c6ktzyh8WR05v/2RBu51I=
fyne.io/fyne v1.4.3/go.mod h1:8kiPBNSDmuplxs9WnKCkaWYqbcXFy0DeAzwa6PBO9Z8=
fyne.io/fyne/v2 v2.1.2 h1:avp9CvLAUdvE7fDMtH1tVKyjxEWHWcpow6aI6L7Kvvw=
fyne.io/fyne/v2 v2.1.2/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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/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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU=
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be h1:Z28GdQBfKOL8tNHjvaDn3wHDO7AzTRkmAXvHvnopp98=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
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/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

52
internal/cache/widget.go vendored Normal file
View File

@ -0,0 +1,52 @@
package cache
import (
"sync"
"fyne.io/fyne/v2"
)
var renderers sync.Map
type isBaseWidget interface {
ExtendBaseWidget(fyne.Widget)
super() fyne.Widget
}
// Renderer looks up the render implementation for a widget
func Renderer(wid fyne.Widget) fyne.WidgetRenderer {
if wid == nil {
return nil
}
if wd, ok := wid.(isBaseWidget); ok {
if wd.super() != nil {
wid = wd.super()
}
}
renderer, ok := renderers.Load(wid)
if !ok {
renderer = wid.CreateRenderer()
renderers.Store(wid, renderer)
}
if renderer == nil {
return nil
}
return renderer.(fyne.WidgetRenderer)
}
// DestroyRenderer frees a render implementation for a widget.
// This is typically for internal use only.
func DestroyRenderer(wid fyne.Widget) {
Renderer(wid).Destroy()
renderers.Delete(wid)
}
// IsRendered returns true of the widget currently has a renderer.
// One will be created the first time a widget is shown but may be removed after it is hidden.
func IsRendered(wid fyne.Widget) bool {
_, found := renderers.Load(wid)
return found
}

97
internal/color/color.go Normal file
View File

@ -0,0 +1,97 @@
package color
import (
"image/color"
)
// ToNRGBA converts a color to RGBA values which are not premultiplied, unlike color.RGBA().
func ToNRGBA(c color.Color) (r, g, b, a int) {
// We use UnmultiplyAlpha with RGBA, RGBA64, and unrecognized implementations of Color.
// It works for all Colors whose RGBA() method is implemented according to spec, but is only necessary for those.
// Only RGBA and RGBA64 have components which are already premultiplied.
switch col := c.(type) {
// NRGBA and NRGBA64 are not premultiplied
case color.NRGBA:
r = int(col.R)
g = int(col.G)
b = int(col.B)
a = int(col.A)
case *color.NRGBA:
r = int(col.R)
g = int(col.G)
b = int(col.B)
a = int(col.A)
case color.NRGBA64:
r = int(col.R) >> 8
g = int(col.G) >> 8
b = int(col.B) >> 8
a = int(col.A) >> 8
case *color.NRGBA64:
r = int(col.R) >> 8
g = int(col.G) >> 8
b = int(col.B) >> 8
a = int(col.A) >> 8
// Gray and Gray16 have no alpha component
case *color.Gray:
r = int(col.Y)
g = int(col.Y)
b = int(col.Y)
a = 0xff
case color.Gray:
r = int(col.Y)
g = int(col.Y)
b = int(col.Y)
a = 0xff
case *color.Gray16:
r = int(col.Y) >> 8
g = int(col.Y) >> 8
b = int(col.Y) >> 8
a = 0xff
case color.Gray16:
r = int(col.Y) >> 8
g = int(col.Y) >> 8
b = int(col.Y) >> 8
a = 0xff
// Alpha and Alpha16 contain only an alpha component.
case color.Alpha:
r = 0xff
g = 0xff
b = 0xff
a = int(col.A)
case *color.Alpha:
r = 0xff
g = 0xff
b = 0xff
a = int(col.A)
case color.Alpha16:
r = 0xff
g = 0xff
b = 0xff
a = int(col.A) >> 8
case *color.Alpha16:
r = 0xff
g = 0xff
b = 0xff
a = int(col.A) >> 8
default: // RGBA, RGBA64, and unknown implementations of Color
r, g, b, a = unmultiplyAlpha(c)
}
return
}
// unmultiplyAlpha returns a color's RGBA components as 8-bit integers by calling c.RGBA() and then removing the alpha premultiplication.
// It is only used by ToRGBA.
func unmultiplyAlpha(c color.Color) (r, g, b, a int) {
red, green, blue, alpha := c.RGBA()
if alpha != 0 && alpha != 0xffff {
red = (red * 0xffff) / alpha
green = (green * 0xffff) / alpha
blue = (blue * 0xffff) / alpha
}
// Convert from range 0-65535 to range 0-255
r = int(red >> 8)
g = int(green >> 8)
b = int(blue >> 8)
a = int(alpha >> 8)
return
}

View File

@ -0,0 +1,32 @@
package vumeter
import "fyne.io/fyne/v2"
// BaseRenderer is a renderer base providing the most common implementations of a part of the
// widget.Renderer interface.
type BaseRenderer struct {
objects []fyne.CanvasObject
}
// NewBaseRenderer creates a new BaseRenderer.
func NewBaseRenderer(objects []fyne.CanvasObject) BaseRenderer {
return BaseRenderer{objects}
}
// Destroy does nothing in the base implementation.
//
// Implements: fyne.WidgetRenderer
func (r *BaseRenderer) Destroy() {
}
// Objects returns the objects that should be rendered.
//
// Implements: fyne.WidgetRenderer
func (r *BaseRenderer) Objects() []fyne.CanvasObject {
return r.objects
}
// SetObjects updates the objects of the renderer.
func (r *BaseRenderer) SetObjects(objects []fyne.CanvasObject) {
r.objects = objects
}

View File

@ -0,0 +1,78 @@
package vumeter
import (
"sync"
"fyne.io/fyne/v2/data/binding"
)
// basicBinder stores a DataItem and a function to be called when it changes.
// It provides a convenient way to replace data and callback independently.
type basicBinder struct {
callbackLock sync.RWMutex
callback func(binding.DataItem) // access guarded by callbackLock
dataListenerPairLock sync.RWMutex
dataListenerPair annotatedListener // access guarded by dataListenerPairLock
}
// Bind replaces the data item whose changes are tracked by the callback function.
func (binder *basicBinder) Bind(data binding.DataItem) {
listener := binding.NewDataListener(func() { // NB: listener captures `data` but always calls the up-to-date callback
binder.callbackLock.RLock()
f := binder.callback
binder.callbackLock.RUnlock()
if f != nil {
f(data)
}
})
data.AddListener(listener)
listenerInfo := annotatedListener{
data: data,
listener: listener,
}
binder.dataListenerPairLock.Lock()
binder.unbindLocked()
binder.dataListenerPair = listenerInfo
binder.dataListenerPairLock.Unlock()
}
// CallWithData passes the currently bound data item as an argument to the
// provided function.
func (binder *basicBinder) CallWithData(f func(data binding.DataItem)) {
binder.dataListenerPairLock.RLock()
data := binder.dataListenerPair.data
binder.dataListenerPairLock.RUnlock()
f(data)
}
// SetCallback replaces the function to be called when the data changes.
func (binder *basicBinder) SetCallback(f func(data binding.DataItem)) {
binder.callbackLock.Lock()
binder.callback = f
binder.callbackLock.Unlock()
}
// Unbind requests the callback to be no longer called when the previously bound
// data item changes.
func (binder *basicBinder) Unbind() {
binder.dataListenerPairLock.Lock()
binder.unbindLocked()
binder.dataListenerPairLock.Unlock()
}
// unbindLocked expects the caller to hold dataListenerPairLock.
func (binder *basicBinder) unbindLocked() {
previousListener := binder.dataListenerPair
binder.dataListenerPair = annotatedListener{nil, nil}
if previousListener.listener == nil || previousListener.data == nil {
return
}
previousListener.data.RemoveListener(previousListener.listener)
}
type annotatedListener struct {
data binding.DataItem
listener binding.DataListener
}

165
pkg/vumeter/vumeter.go Normal file
View File

@ -0,0 +1,165 @@
package vumeter
import (
"image/color"
"strconv"
"git.martyn.berlin/martyn/fyne-widgets/internal/cache"
col "git.martyn.berlin/martyn/fyne-widgets/internal/color"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
type VUMeter struct {
widget.BaseWidget
Min, Max, Value float64
TextFormatter func() string
binder basicBinder
}
type vuMeterRenderer struct {
objects []fyne.CanvasObject
background, bar *canvas.Rectangle
label *canvas.Text
meter *VUMeter
}
func (p *vuMeterRenderer) MinSize() fyne.Size {
var tsize fyne.Size
if text := p.meter.TextFormatter; text != nil {
tsize = fyne.MeasureText(text(), p.label.TextSize, p.label.TextStyle)
} else {
tsize = fyne.MeasureText("100%", p.label.TextSize, p.label.TextStyle)
}
return fyne.NewSize(tsize.Width+theme.Padding()*4, tsize.Height+theme.Padding()*2)
}
func (p *vuMeterRenderer) updateBar() {
if p.meter.Value < p.meter.Min {
p.meter.Value = p.meter.Min
}
if p.meter.Value > p.meter.Max {
p.meter.Value = p.meter.Max
}
delta := float32(p.meter.Max - p.meter.Min)
ratio := float32(p.meter.Value-p.meter.Min) / delta
if text := p.meter.TextFormatter; text != nil {
p.label.Text = text()
} else {
p.label.Text = strconv.Itoa(int(ratio*100)) + "%"
}
size := p.meter.Size()
p.bar.Resize(fyne.NewSize(size.Width*ratio, size.Height))
}
func (p *vuMeterRenderer) Layout(size fyne.Size) {
p.background.Resize(size)
p.label.Resize(size)
p.updateBar()
}
func (p *vuMeterRenderer) applyTheme() {
p.background.FillColor = vuMeterBackgroundColor()
p.bar.FillColor = theme.PrimaryColor()
p.label.Color = theme.ForegroundColor()
p.label.TextSize = theme.TextSize()
}
func (p *vuMeterRenderer) Refresh() {
p.applyTheme()
p.updateBar()
p.background.Refresh()
p.bar.Refresh()
p.meter.super()
}
func (r *vuMeterRenderer) Objects() []fyne.CanvasObject {
return r.objects
}
// SetObjects updates the objects of the renderer.
func (r *vuMeterRenderer) SetObjects(objects []fyne.CanvasObject) {
r.objects = objects
}
func (r *vuMeterRenderer) Destroy() {
}
func (p *VUMeter) Bind(data binding.Float) {
p.binder.SetCallback(p.updateFromData)
p.binder.Bind(data)
}
func (p *VUMeter) SetValue(v float64) {
p.Value = v
p.Refresh()
}
func (p *VUMeter) MinSize() fyne.Size {
p.ExtendBaseWidget(p)
return p.BaseWidget.MinSize()
}
func (p *VUMeter) CreateRenderer() fyne.WidgetRenderer {
p.ExtendBaseWidget(p)
if p.Min == 0 && p.Max == 0 {
p.Max = 1.0
}
background := canvas.NewRectangle(vuMeterBackgroundColor())
bar := canvas.NewRectangle(theme.PrimaryColor())
label := canvas.NewText("0%", theme.ForegroundColor())
label.Alignment = fyne.TextAlignCenter
return &vuMeterRenderer{[]fyne.CanvasObject{background, bar, label}, background, bar, label, p}
}
func (p *VUMeter) Unbind() {
p.binder.Unbind()
}
func NewVUMeter() *VUMeter {
p := &VUMeter{Min: 0, Max: 1}
cache.Renderer(p).Layout(p.MinSize())
return p
}
func NewVUMeterWithData(data binding.Float) *VUMeter {
p := NewVUMeter()
p.Bind(data)
return p
}
func vuMeterBackgroundColor() color.Color {
r, g, b, a := col.ToNRGBA(theme.PrimaryColor())
faded := uint8(a) / 3
return &color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: faded}
}
func (p *VUMeter) updateFromData(data binding.DataItem) {
if data == nil {
return
}
floatSource, ok := data.(binding.Float)
if !ok {
return
}
val, err := floatSource.Get()
if err != nil {
fyne.LogError("Error getting current data value", err)
return
}
p.SetValue(val)
}