Browse Source

d04 Обновление вендоринга

SVI 2 years ago
parent
commit
f47044b3b4
100 changed files with 3600 additions and 1241 deletions
  1. 4 4
      go.mod
  2. 10 42
      go.sum
  3. 90 1
      vendor/fyne.io/fyne/v2/CHANGELOG.md
  4. 0 4
      vendor/fyne.io/fyne/v2/README.md
  5. 2 2
      vendor/fyne.io/fyne/v2/SECURITY.md
  6. 15 16
      vendor/fyne.io/fyne/v2/app/app.go
  7. 3 1
      vendor/fyne.io/fyne/v2/app/app_desktop_darwin.go
  8. 3 5
      vendor/fyne.io/fyne/v2/app/app_windows.go
  9. 11 4
      vendor/fyne.io/fyne/v2/app/app_xdg.go
  10. 1 1
      vendor/fyne.io/fyne/v2/app/cloud.go
  11. 56 1
      vendor/fyne.io/fyne/v2/app/preferences.go
  12. 12 5
      vendor/fyne.io/fyne/v2/app/settings.go
  13. 1 1
      vendor/fyne.io/fyne/v2/app/settings_desktop.go
  14. 8 0
      vendor/fyne.io/fyne/v2/app/settings_noanimation.go
  15. 3 1
      vendor/fyne.io/fyne/v2/canvas/circle.go
  16. 202 20
      vendor/fyne.io/fyne/v2/canvas/image.go
  17. 4 0
      vendor/fyne.io/fyne/v2/canvas/rectangle.go
  18. 17 14
      vendor/fyne.io/fyne/v2/container/apptabs.go
  19. 2 2
      vendor/fyne.io/fyne/v2/container/container.go
  20. 5 1
      vendor/fyne.io/fyne/v2/container/doctabs.go
  21. 13 1
      vendor/fyne.io/fyne/v2/container/layouts.go
  22. 31 18
      vendor/fyne.io/fyne/v2/container/split.go
  23. 47 36
      vendor/fyne.io/fyne/v2/container/tabs.go
  24. 1816 0
      vendor/fyne.io/fyne/v2/data/binding/bindtrees.go
  25. 118 0
      vendor/fyne.io/fyne/v2/data/binding/bool.go
  26. 86 0
      vendor/fyne.io/fyne/v2/data/binding/treebinding.go
  27. 5 0
      vendor/fyne.io/fyne/v2/driver/desktop/driver.go
  28. 10 0
      vendor/fyne.io/fyne/v2/driver/mobile/driver.go
  29. 10 0
      vendor/fyne.io/fyne/v2/driver/mobile/key.go
  30. 18 0
      vendor/fyne.io/fyne/v2/geometry.go
  31. 2 2
      vendor/fyne.io/fyne/v2/internal/app/focus_manager.go
  32. 13 1
      vendor/fyne.io/fyne/v2/internal/app/lifecycle.go
  33. 1 0
      vendor/fyne.io/fyne/v2/internal/cache/base.go
  34. 8 2
      vendor/fyne.io/fyne/v2/internal/cache/canvases.go
  35. 3 1
      vendor/fyne.io/fyne/v2/internal/cache/svg.go
  36. 24 1
      vendor/fyne.io/fyne/v2/internal/cache/text.go
  37. 4 2
      vendor/fyne.io/fyne/v2/internal/clip.go
  38. 100 25
      vendor/fyne.io/fyne/v2/internal/driver/common/canvas.go
  39. 3 3
      vendor/fyne.io/fyne/v2/internal/driver/common/window.go
  40. 31 25
      vendor/fyne.io/fyne/v2/internal/driver/glfw/canvas.go
  41. 15 27
      vendor/fyne.io/fyne/v2/internal/driver/glfw/driver.go
  42. 21 11
      vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_desktop.go
  43. 6 0
      vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_notwayland.go
  44. 1 3
      vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_wayland.go
  45. 3 0
      vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_web.go
  46. 20 30
      vendor/fyne.io/fyne/v2/internal/driver/glfw/loop.go
  47. 0 5
      vendor/fyne.io/fyne/v2/internal/driver/glfw/loop_desktop.go
  48. 5 6
      vendor/fyne.io/fyne/v2/internal/driver/glfw/menu_bar.go
  49. 2 0
      vendor/fyne.io/fyne/v2/internal/driver/glfw/menu_bar_item.go
  50. 1 1
      vendor/fyne.io/fyne/v2/internal/driver/glfw/menu_darwin.go
  51. 10 0
      vendor/fyne.io/fyne/v2/internal/driver/glfw/scroll_speed_darwin.go
  52. 10 0
      vendor/fyne.io/fyne/v2/internal/driver/glfw/scroll_speed_default.go
  53. 45 32
      vendor/fyne.io/fyne/v2/internal/driver/glfw/window.go
  54. 51 5
      vendor/fyne.io/fyne/v2/internal/driver/glfw/window_desktop.go
  55. 7 3
      vendor/fyne.io/fyne/v2/internal/driver/glfw/window_goxjs.go
  56. 2 2
      vendor/fyne.io/fyne/v2/internal/driver/glfw/window_notwindows.go
  57. 4 3
      vendor/fyne.io/fyne/v2/internal/driver/glfw/window_windows.go
  58. 16 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/GoNativeActivity.java
  59. 22 1
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/android.c
  60. 26 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/android.go
  61. 2 2
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/app.go
  62. 4 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/darwin_desktop.go
  63. 4 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/darwin_ios.go
  64. 4 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/shiny.go
  65. 2 2
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/x11.c
  66. 7 2
      vendor/fyne.io/fyne/v2/internal/driver/mobile/app/x11.go
  67. 8 6
      vendor/fyne.io/fyne/v2/internal/driver/mobile/canvas.go
  68. 29 12
      vendor/fyne.io/fyne/v2/internal/driver/mobile/driver.go
  69. 2 0
      vendor/fyne.io/fyne/v2/internal/driver/mobile/event/key/key.go
  70. 0 2
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/consts.go
  71. 8 9
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/dll_windows.go
  72. 1 1
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/fn.go
  73. 16 10
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/gl.go
  74. 6 5
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/interface.go
  75. 3 3
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work.c
  76. 3 4
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work.go
  77. 1 1
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work.h
  78. 0 181
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work114.go
  79. 5 4
      vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work_windows.go
  80. 5 1
      vendor/fyne.io/fyne/v2/internal/driver/mobile/window.go
  81. 8 6
      vendor/fyne.io/fyne/v2/internal/driver/util.go
  82. 12 2
      vendor/fyne.io/fyne/v2/internal/overlay_stack.go
  83. 28 5
      vendor/fyne.io/fyne/v2/internal/painter/draw.go
  84. 121 268
      vendor/fyne.io/fyne/v2/internal/painter/font.go
  85. 1 1
      vendor/fyne.io/fyne/v2/internal/painter/gl/context.go
  86. 118 48
      vendor/fyne.io/fyne/v2/internal/painter/gl/draw.go
  87. 6 1
      vendor/fyne.io/fyne/v2/internal/painter/gl/gl.go
  88. 0 9
      vendor/fyne.io/fyne/v2/internal/painter/gl/gl_const_darwin.go
  89. 0 17
      vendor/fyne.io/fyne/v2/internal/painter/gl/gl_const_mobile.go
  90. 6 7
      vendor/fyne.io/fyne/v2/internal/painter/gl/gl_core.go
  91. 6 7
      vendor/fyne.io/fyne/v2/internal/painter/gl/gl_es.go
  92. 16 9
      vendor/fyne.io/fyne/v2/internal/painter/gl/gl_gomobile.go
  93. 6 7
      vendor/fyne.io/fyne/v2/internal/painter/gl/gl_goxjs.go
  94. 18 14
      vendor/fyne.io/fyne/v2/internal/painter/gl/painter.go
  95. 30 10
      vendor/fyne.io/fyne/v2/internal/painter/gl/shaders.go
  96. 8 41
      vendor/fyne.io/fyne/v2/internal/painter/gl/texture.go
  97. 11 136
      vendor/fyne.io/fyne/v2/internal/painter/image.go
  98. 30 30
      vendor/fyne.io/fyne/v2/internal/painter/software/draw.go
  99. 6 6
      vendor/fyne.io/fyne/v2/internal/painter/software/painter.go
  100. 0 1
      vendor/fyne.io/fyne/v2/internal/painter/vector.go

+ 4 - 4
go.mod

@@ -3,14 +3,14 @@ module wartank
 go 1.20
 go 1.20
 
 
 require (
 require (
-	fyne.io/fyne/v2 v2.3.5
+	fyne.io/fyne/v2 v2.4.0
 	github.com/charmbracelet/bubbletea v0.24.2
 	github.com/charmbracelet/bubbletea v0.24.2
 	github.com/sirupsen/logrus v1.9.3
 	github.com/sirupsen/logrus v1.9.3
 	github.com/syndtr/goleveldb v1.0.0
 	github.com/syndtr/goleveldb v1.0.0
 )
 )
 
 
 require (
 require (
-	fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b // indirect
+	fyne.io/systray v1.10.1-0.20230722100817-88df1e0ffa9a // indirect
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
@@ -21,9 +21,9 @@ require (
 	github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6 // indirect
 	github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6 // indirect
 	github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // 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-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
+	github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect
 	github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 // indirect
 	github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 // indirect
 	github.com/godbus/dbus/v5 v5.1.0 // indirect
 	github.com/godbus/dbus/v5 v5.1.0 // indirect
-	github.com/goki/freetype v1.0.1 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/gopherjs/gopherjs v1.17.2 // indirect
 	github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
 	github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
@@ -43,7 +43,7 @@ require (
 	github.com/tevino/abool v1.2.0 // indirect
 	github.com/tevino/abool v1.2.0 // indirect
 	github.com/yuin/goldmark v1.5.6 // indirect
 	github.com/yuin/goldmark v1.5.6 // indirect
 	golang.org/x/image v0.11.0 // indirect
 	golang.org/x/image v0.11.0 // indirect
-	golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
+	golang.org/x/mobile v0.0.0-20230901161150-52620a4a7557 // indirect
 	golang.org/x/net v0.14.0 // indirect
 	golang.org/x/net v0.14.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
 	golang.org/x/sys v0.11.0 // indirect
 	golang.org/x/sys v0.11.0 // indirect

+ 10 - 42
go.sum

@@ -37,14 +37,12 @@ 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.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.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=
 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 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/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=
 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=
 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-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -68,7 +66,6 @@ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:Yyn
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 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/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.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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -80,21 +77,16 @@ 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/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/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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-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 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg=
 github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
 github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 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.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-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 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 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/gl-js v0.0.0-20230506162202-1fdaa286a934 h1:dZC5aKobSN07hf71oMivxUmAofFja5GrfPK2rBlttX4=
 github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934 h1:dZC5aKobSN07hf71oMivxUmAofFja5GrfPK2rBlttX4=
 github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
 github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934/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/glfw-js v0.0.0-20220517201726-bebc2019cd33 h1:0Ayg0/do/sqX2R7NonoLZvWxGrd9utTVf3A0QvCbC88=
 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/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-20230811065323-ed435dc8bca6 h1:kZNUHSV3ZTddRiWy5JHK6RgB3zdH/875SYXmt3EoNvQ=
 github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6 h1:kZNUHSV3ZTddRiWy5JHK6RgB3zdH/875SYXmt3EoNvQ=
 github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6/go.mod h1:aX1w6epS9BQn2bePY+3rkQejetaffeFhXl0s8QjXJJk=
 github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6/go.mod h1:aX1w6epS9BQn2bePY+3rkQejetaffeFhXl0s8QjXJJk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -106,19 +98,15 @@ 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/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 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 h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 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/go.mod h1:zvWM81wAVW6QfVDI6yxfbCuoLnobSYTuMsrXU/u11y8=
+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-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
 github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
 github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
 github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
-github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE=
 github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
 github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 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 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
 github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 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/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-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/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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -216,25 +204,22 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 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-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 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=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 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=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
 github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
 github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
 github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
 github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 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/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/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/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.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 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/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-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/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-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=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -245,7 +230,6 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
 github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
 github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
 github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -268,9 +252,6 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
 github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
 github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
 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/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
-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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
 github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -291,14 +272,12 @@ github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 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/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.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/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/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
 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/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/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/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -309,10 +288,8 @@ 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/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
-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 h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
 github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
 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 h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
 github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
 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.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -332,7 +309,6 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
 github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
 github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
 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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -377,8 +353,6 @@ 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/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-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-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/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
 golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
 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/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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -396,8 +370,8 @@ golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPI
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 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-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
 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/mobile v0.0.0-20230901161150-52620a4a7557 h1:mLrcd+qwh23kzD7ej1VxCa+A23UNr+BCjSj2tNX8/NM=
+golang.org/x/mobile v0.0.0-20230901161150-52620a4a7557/go.mod h1:f0gjFM6UTH7y1WEZBm/kquBYsogL+NQtllKFy4Rdulc=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 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.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -447,7 +421,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 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-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-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.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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
 golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
@@ -491,10 +464,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/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-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-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-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-20191204072324-ce4227a45e2e/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-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -527,7 +498,6 @@ 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-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-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-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-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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -551,7 +521,6 @@ 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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/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.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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
 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/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
@@ -720,9 +689,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-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.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 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-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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

+ 90 - 1
vendor/fyne.io/fyne/v2/CHANGELOG.md

@@ -3,6 +3,96 @@
 This file lists the main changes with each version of the Fyne toolkit.
 This file lists the main changes with each version of the Fyne toolkit.
 More detailed release notes can be found on the [releases page](https://github.com/fyne-io/fyne/releases). 
 More detailed release notes can be found on the [releases page](https://github.com/fyne-io/fyne/releases). 
 
 
+## 2.4.0 - 1 September 2023
+
+## Added
+
+* Rounded corners in rectangle (#1090)
+* Support for emoji in text
+* Layout debugging (with `-tags debug` build flag) (#3314)
+* GridWrap collection widget
+* Add table headers (#1658, #3594)
+* Add mobile back button handling (#2910)
+* Add option to disable UI animations (#1813)
+* Text truncation ellipsis (#1659)
+* Add support for binding tree data, include new `NewTreeWithData`
+* Add support for OpenType fonts (#3245)
+* Add `Window.SetOnDropped` to handle window-wide item drop on desktop
+* Add lists to the types supported by preferences API
+* Keyboard focus handling for all collection widgets
+* Add APIs for refreshing individual items in collections (#3826)
+* Tapping slider moves it to that position (#3650)
+* Add `OnChangeEnded` callback to `Slider` (#3652)
+* Added keyboard controls to `Slider`
+* Add `NewWarningThemedResource` and `NewSuccessThemedResource` along with `NewColoredResource` (#4040)
+* Custom hyperlink callback for rich text hyperlinks (#3335)
+* Added `dialog.NewCustomWithoutButtons`, with a `SetButtons` method (#2127, #2782)
+* Added `SetConfirmImportance` to `dialog.ConfirmDialog`.
+* Added `FormDialog.Submit()` to close and submit the dialog if validation passes
+* Rich Text image alignment (#3810)
+* Bring back `theme.HyperlinkColor` (#3867)
+* Added `Importance` field on `Label` to color the text
+* Navigating in entry quickly with ctrl key (#2462)
+* Support `.desktop` file metadata in `FyneApp.toml` for Linux and BSD
+* Support mobile simulator on FreeBSD
+* Add data binding boolean operators `Not`, `And` and `Or`
+* Added `Entry.Append`, `Select.SetOptions`, `Check.SetText`, `FormDialog.Submit`
+* Add `ShowPopUpAtRelativePosition` and `PopUp.ShowAtRelativePosition`
+* Add desktop support to get key modifiers with `CurrentKeyModifiers`
+* Add geometry helpers `NewSquareSize` and `NewSquareOffsetPos`
+* Add `--pprof` option to fyne build commands to enable profiling
+* Support compiling from Android (termux)
+
+## Changed
+
+* Go 1.17 or later is now required.
+* Theme updated for rounded corners on buttons and input widgets
+* `widget.ButtonImportance` is now `widget.Importance`
+* The `Max` container and layout have been renamed `Stack` for clarity
+* Refreshing an image will now happen in app-thread not render process, apps may wish to add async image load
+* Icons for macOS bundles are now padded and rounded, disable with "-use-raw-icon" (#3752)
+* Update Android target SDK to 33 for Play Store releases
+* Focus handling for List/Tree/Table are now at the parent widget not child elements
+* Accordion widget now fills available space - put it inside a `VBox` container for old behavior (#4126)
+* Deprecated theme.FyneLogo() for later removal (#3296)
+* Improve look of menu shortcuts (#2722)
+* iOS and macOS packages now default to using "XCWildcard" provisioning profile
+* Improving performance of lookup for theme data
+* Improved application startup time
+
+## Fixed
+
+* Rendering performance enhancements
+* `dialog.NewProgressInfinite` is deprecated, but dialog.NewCustom isn't equivalent
+* Mouse cursor desync with Split handle when dragging (#3791)
+* Minor graphic glitch with checkbox (#3792)
+* binding.String===>Quick refresh *b.val will appear with new data reset by a call to OnChange (#3774)
+* Fyne window becomes unresponsive when in background for a while (#2791)
+* Hangs on repeated calls to `Select.SetSelected` in table. (#3684)
+* `Select` has wrong height, padding and border (#4142)
+* `widget.ImageSegment` can't be aligned. (#3505)
+* Memory leak in font metrics cache (#4108)
+* Don't panic when loading preferences with wrong type (#4039)
+* Button with icon has wrong padding on right (#4124)
+* Preferences don't all save when written in `CloseIntercept` (#3170)
+* Text size does not update in Refresh for TextGrid
+* DocTab selection underline not updated when deleting an Item (#3905)
+* Single line Entry throws away selected text on submission (#4026)
+* Significantly improve performance of large `TextGrid` and `Tree` widgets
+* `List.ScrollToBottom` not scrolling to show the totality of the last Item (#3829)
+* Setting `Position1` of canvas.Circle higher than `Position2` causes panic. (#3949)
+* Enhance scroll wheel/touchpad scroll speed on desktop (#3492)
+* Possible build issue on Windows with app metadata
+* `Form` hint text has confusing padding to next widget (#4137)
+* `Entry` Placeholder Style Only Applied On Click (#4035)
+* Backspace and Delete key Do not Fire OnChanged Event (#4117)
+* Fix `ProgressBar` text having the wrong color sometimes
+* Window doesn't render when called for the first time from system tray and the last window was closed (#4163)
+* Possible race condition in preference change listeners
+* Various vulnerabilities resolved through updating dependencies 
+* Wrong background for color dialog (#4199)
+
+
 ## 2.3.5 - 6 June 2023
 ## 2.3.5 - 6 June 2023
 
 
 ### Fixed
 ### Fixed
@@ -40,7 +130,6 @@ More detailed release notes can be found on the [releases page](https://github.c
 * VBox and HBox using heap memory that was not required
 * VBox and HBox using heap memory that was not required
 * Menu hover is slow on long menus
 * Menu hover is slow on long menus
 
 
-
 ## 2.3.3 - 24 March 2023
 ## 2.3.3 - 24 March 2023
 
 
 ### Fixed
 ### Fixed

+ 0 - 4
vendor/fyne.io/fyne/v2/README.md

@@ -42,8 +42,6 @@ To run a showcase of the features of Fyne execute the following:
     go install fyne.io/fyne/v2/cmd/fyne_demo@latest
     go install fyne.io/fyne/v2/cmd/fyne_demo@latest
     fyne_demo
     fyne_demo
 
 
-(For Go versions earlier than v1.16 use `go get fyne.io/fyne/v2/cmd/fyne_demo`)
-
 And you should see something like this (after you click a few buttons):
 And you should see something like this (after you click a few buttons):
 
 
 <p align="center" markdown="1" style="max-width: 100%">
 <p align="center" markdown="1" style="max-width: 100%">
@@ -131,8 +129,6 @@ application location you can use the fyne utility and the "install" subcommand.
     go install fyne.io/fyne/v2/cmd/fyne@latest
     go install fyne.io/fyne/v2/cmd/fyne@latest
     fyne install
     fyne install
 
 
-(for Go versions before v1.16 use `go get fyne.io/fyne/v2/cmd/fyne`)
-
 # Packaging for mobile
 # Packaging for mobile
 
 
 To run on a mobile device it is necessary to package up the application.
 To run on a mobile device it is necessary to package up the application.

+ 2 - 2
vendor/fyne.io/fyne/v2/SECURITY.md

@@ -6,8 +6,8 @@ Minor releases will receive security updates and fixes until the next minor or m
 
 
 | Version | Supported          |
 | Version | Supported          |
 | ------- | ------------------ |
 | ------- | ------------------ |
-| 2.3.x   | :white_check_mark: |
-| < 2.3.0 | :x:                |
+| 2.4.x   | :white_check_mark: |
+| < 2.4.0 | :x:                |
 
 
 ## Reporting a Vulnerability
 ## Reporting a Vulnerability
 
 

+ 15 - 16
vendor/fyne.io/fyne/v2/app/app.go

@@ -14,8 +14,6 @@ import (
 	"fyne.io/fyne/v2/internal/app"
 	"fyne.io/fyne/v2/internal/app"
 	intRepo "fyne.io/fyne/v2/internal/repository"
 	intRepo "fyne.io/fyne/v2/internal/repository"
 	"fyne.io/fyne/v2/storage/repository"
 	"fyne.io/fyne/v2/storage/repository"
-
-	"golang.org/x/sys/execabs"
 )
 )
 
 
 // Declare conformity with App interface
 // Declare conformity with App interface
@@ -33,7 +31,6 @@ type fyneApp struct {
 	prefs     fyne.Preferences
 	prefs     fyne.Preferences
 
 
 	running uint32 // atomic, 1 == running, 0 == stopped
 	running uint32 // atomic, 1 == running, 0 == stopped
-	exec    func(name string, arg ...string) *execabs.Cmd
 }
 }
 
 
 func (a *fyneApp) CloudProvider() fyne.CloudProvider {
 func (a *fyneApp) CloudProvider() fyne.CloudProvider {
@@ -72,7 +69,6 @@ func (a *fyneApp) NewWindow(title string) fyne.Window {
 func (a *fyneApp) Run() {
 func (a *fyneApp) Run() {
 	if atomic.CompareAndSwapUint32(&a.running, 0, 1) {
 	if atomic.CompareAndSwapUint32(&a.running, 0, 1) {
 		a.driver.Run()
 		a.driver.Run()
-		return
 	}
 	}
 }
 }
 
 
@@ -110,10 +106,10 @@ func (a *fyneApp) Lifecycle() fyne.Lifecycle {
 	return a.lifecycle
 	return a.lifecycle
 }
 }
 
 
-func (a *fyneApp) newDefaultPreferences() fyne.Preferences {
-	p := fyne.Preferences(newPreferences(a))
-	if pref, ok := p.(interface{ load() }); ok && a.uniqueID != "" {
-		pref.load()
+func (a *fyneApp) newDefaultPreferences() *preferences {
+	p := newPreferences(a)
+	if a.uniqueID != "" {
+		p.load()
 	}
 	}
 	return p
 	return p
 }
 }
@@ -126,11 +122,8 @@ func New() fyne.App {
 	return NewWithID(meta.ID)
 	return NewWithID(meta.ID)
 }
 }
 
 
-func makeStoreDocs(id string, p fyne.Preferences, s *store) *internal.Docs {
+func makeStoreDocs(id string, s *store) *internal.Docs {
 	if id != "" {
 	if id != "" {
-		if pref, ok := p.(interface{ load() }); ok {
-			pref.load()
-		}
 		err := os.MkdirAll(s.a.storageRoot(), 0755) // make the space before anyone can use it
 		err := os.MkdirAll(s.a.storageRoot(), 0755) // make the space before anyone can use it
 		if err != nil {
 		if err != nil {
 			fyne.LogError("Failed to create app storage space", err)
 			fyne.LogError("Failed to create app storage space", err)
@@ -144,21 +137,27 @@ func makeStoreDocs(id string, p fyne.Preferences, s *store) *internal.Docs {
 }
 }
 
 
 func newAppWithDriver(d fyne.Driver, id string) fyne.App {
 func newAppWithDriver(d fyne.Driver, id string) fyne.App {
-	newApp := &fyneApp{uniqueID: id, driver: d, exec: execabs.Command, lifecycle: &app.Lifecycle{}}
+	newApp := &fyneApp{uniqueID: id, driver: d, lifecycle: &app.Lifecycle{}}
 	fyne.SetCurrentApp(newApp)
 	fyne.SetCurrentApp(newApp)
 
 
 	newApp.prefs = newApp.newDefaultPreferences()
 	newApp.prefs = newApp.newDefaultPreferences()
+	newApp.lifecycle.(*app.Lifecycle).SetOnStoppedHookExecuted(func() {
+		if prefs, ok := newApp.prefs.(*preferences); ok {
+			prefs.forceImmediateSave()
+		}
+	})
 	newApp.settings = loadSettings()
 	newApp.settings = loadSettings()
 	store := &store{a: newApp}
 	store := &store{a: newApp}
-	store.Docs = makeStoreDocs(id, newApp.prefs, store)
+	store.Docs = makeStoreDocs(id, store)
 	newApp.storage = store
 	newApp.storage = store
 
 
 	if !d.Device().IsMobile() {
 	if !d.Device().IsMobile() {
 		newApp.settings.watchSettings()
 		newApp.settings.watchSettings()
 	}
 	}
 
 
-	repository.Register("http", intRepo.NewHTTPRepository())
-	repository.Register("https", intRepo.NewHTTPRepository())
+	httpHandler := intRepo.NewHTTPRepository()
+	repository.Register("http", httpHandler)
+	repository.Register("https", httpHandler)
 
 
 	return newApp
 	return newApp
 }
 }

+ 3 - 1
vendor/fyne.io/fyne/v2/app/app_desktop_darwin.go

@@ -19,6 +19,8 @@ import (
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 
 
+	"golang.org/x/sys/execabs"
+
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/theme"
 	"fyne.io/fyne/v2/theme"
 )
 )
@@ -52,7 +54,7 @@ func rootConfigDir() string {
 }
 }
 
 
 func (a *fyneApp) OpenURL(url *url.URL) error {
 func (a *fyneApp) OpenURL(url *url.URL) error {
-	cmd := a.exec("open", url.String())
+	cmd := execabs.Command("open", url.String())
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	return cmd.Run()
 	return cmd.Run()
 }
 }

+ 3 - 5
vendor/fyne.io/fyne/v2/app/app_windows.go

@@ -5,19 +5,17 @@ package app
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"io/ioutil"
 	"net/url"
 	"net/url"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
 	"syscall"
 	"syscall"
 
 
+	"golang.org/x/sys/execabs"
 	"golang.org/x/sys/windows/registry"
 	"golang.org/x/sys/windows/registry"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/theme"
 	"fyne.io/fyne/v2/theme"
-
-	"golang.org/x/sys/execabs"
 )
 )
 
 
 const notificationTemplate = `$title = "%s"
 const notificationTemplate = `$title = "%s"
@@ -64,7 +62,7 @@ func rootConfigDir() string {
 }
 }
 
 
 func (a *fyneApp) OpenURL(url *url.URL) error {
 func (a *fyneApp) OpenURL(url *url.URL) error {
-	cmd := a.exec("rundll32", "url.dll,FileProtocolHandler", url.String())
+	cmd := execabs.Command("rundll32", "url.dll,FileProtocolHandler", url.String())
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	return cmd.Run()
 	return cmd.Run()
 }
 }
@@ -106,7 +104,7 @@ func runScript(name, script string) {
 	fileName := fmt.Sprintf("fyne-%s-%s-%d.ps1", appID, name, scriptNum)
 	fileName := fmt.Sprintf("fyne-%s-%s-%d.ps1", appID, name, scriptNum)
 
 
 	tmpFilePath := filepath.Join(os.TempDir(), fileName)
 	tmpFilePath := filepath.Join(os.TempDir(), fileName)
-	err := ioutil.WriteFile(tmpFilePath, []byte(script), 0600)
+	err := os.WriteFile(tmpFilePath, []byte(script), 0600)
 	if err != nil {
 	if err != nil {
 		fyne.LogError("Could not write script to show notification", err)
 		fyne.LogError("Could not write script to show notification", err)
 		return
 		return

+ 11 - 4
vendor/fyne.io/fyne/v2/app/app_xdg.go

@@ -15,6 +15,7 @@ import (
 	"sync"
 	"sync"
 
 
 	"github.com/godbus/dbus/v5"
 	"github.com/godbus/dbus/v5"
+	"golang.org/x/sys/execabs"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/theme"
 	"fyne.io/fyne/v2/theme"
@@ -27,7 +28,7 @@ func defaultVariant() fyne.ThemeVariant {
 }
 }
 
 
 func (a *fyneApp) OpenURL(url *url.URL) error {
 func (a *fyneApp) OpenURL(url *url.URL) error {
-	cmd := a.exec("xdg-open", url.String())
+	cmd := execabs.Command("xdg-open", url.String())
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	return cmd.Start()
 	return cmd.Start()
 }
 }
@@ -58,13 +59,19 @@ func findFreedestktopColorScheme() fyne.ThemeVariant {
 		return theme.VariantDark
 		return theme.VariantDark
 	}
 	}
 
 
+	// See: https://github.com/flatpak/xdg-desktop-portal/blob/1.16.0/data/org.freedesktop.impl.portal.Settings.xml#L32-L46
+	// 0: No preference
+	// 1: Prefer dark appearance
+	// 2: Prefer light appearance
 	switch value {
 	switch value {
-	case 0:
+	case 2:
 		return theme.VariantLight
 		return theme.VariantLight
-	default:
+	case 1:
 		return theme.VariantDark
 		return theme.VariantDark
+	default:
+		// Default to light theme to support Gnome's default see https://github.com/fyne-io/fyne/pull/3561
+		return theme.VariantLight
 	}
 	}
-
 }
 }
 
 
 func (a *fyneApp) SendNotification(n *fyne.Notification) {
 func (a *fyneApp) SendNotification(n *fyne.Notification) {

+ 1 - 1
vendor/fyne.io/fyne/v2/app/cloud.go

@@ -33,7 +33,7 @@ func (a *fyneApp) transitionCloud(p fyne.CloudProvider) {
 		a.storage = cloud.CloudStorage(a)
 		a.storage = cloud.CloudStorage(a)
 	} else {
 	} else {
 		store := &store{a: a}
 		store := &store{a: a}
-		store.Docs = makeStoreDocs(a.uniqueID, a.prefs, store)
+		store.Docs = makeStoreDocs(a.uniqueID, store)
 		a.storage = store
 		a.storage = store
 	}
 	}
 
 

+ 56 - 1
vendor/fyne.io/fyne/v2/app/preferences.go

@@ -19,12 +19,25 @@ type preferences struct {
 	savedRecently       bool
 	savedRecently       bool
 	changedDuringSaving bool
 	changedDuringSaving bool
 
 
-	app *fyneApp
+	app                 *fyneApp
+	needsSaveBeforeExit bool
 }
 }
 
 
 // Declare conformity with Preferences interface
 // Declare conformity with Preferences interface
 var _ fyne.Preferences = (*preferences)(nil)
 var _ fyne.Preferences = (*preferences)(nil)
 
 
+// forceImmediateSave writes preferences to file immediately, ignoring the debouncing
+// logic in the change listener. Does nothing if preferences are not backed with a file.
+func (p *preferences) forceImmediateSave() {
+	if !p.needsSaveBeforeExit {
+		return
+	}
+	err := p.save()
+	if err != nil {
+		fyne.LogError("Failed on force saving preferences", err)
+	}
+}
+
 func (p *preferences) resetSavedRecently() {
 func (p *preferences) resetSavedRecently() {
 	go func() {
 	go func() {
 		time.Sleep(time.Millisecond * 100) // writes are not always atomic. 10ms worked, 100 is safer.
 		time.Sleep(time.Millisecond * 100) // writes are not always atomic. 10ms worked, 100 is safer.
@@ -109,6 +122,10 @@ func (p *preferences) loadFromFile(path string) (err error) {
 
 
 	p.InMemoryPreferences.WriteValues(func(values map[string]interface{}) {
 	p.InMemoryPreferences.WriteValues(func(values map[string]interface{}) {
 		err = decode.Decode(&values)
 		err = decode.Decode(&values)
+		if err != nil {
+			return
+		}
+		convertLists(values)
 	})
 	})
 
 
 	p.prefLock.Lock()
 	p.prefLock.Lock()
@@ -128,6 +145,7 @@ func newPreferences(app *fyneApp) *preferences {
 		return p
 		return p
 	}
 	}
 
 
+	p.needsSaveBeforeExit = true
 	p.AddChangeListener(func() {
 	p.AddChangeListener(func() {
 		if p != app.prefs {
 		if p != app.prefs {
 			return
 			return
@@ -151,3 +169,40 @@ func newPreferences(app *fyneApp) *preferences {
 	p.watch()
 	p.watch()
 	return p
 	return p
 }
 }
+
+func convertLists(values map[string]interface{}) {
+	for k, v := range values {
+		if items, ok := v.([]interface{}); ok {
+			if len(items) == 0 {
+				continue
+			}
+
+			switch items[0].(type) {
+			case bool:
+				bools := make([]bool, len(items))
+				for i, item := range items {
+					bools[i] = item.(bool)
+				}
+				values[k] = bools
+			case float64:
+				floats := make([]float64, len(items))
+				for i, item := range items {
+					floats[i] = item.(float64)
+				}
+				values[k] = floats
+			case int:
+				ints := make([]int, len(items))
+				for i, item := range items {
+					ints[i] = item.(int)
+				}
+				values[k] = ints
+			case string:
+				strings := make([]string, len(items))
+				for i, item := range items {
+					strings[i] = item.(string)
+				}
+				values[k] = strings
+			}
+		}
+	}
+}

+ 12 - 5
vendor/fyne.io/fyne/v2/app/settings.go

@@ -10,14 +10,17 @@ import (
 	"fyne.io/fyne/v2/theme"
 	"fyne.io/fyne/v2/theme"
 )
 )
 
 
+var noAnimations bool // set to true at compile time if no_animations tag is passed
+
 // SettingsSchema is used for loading and storing global settings
 // SettingsSchema is used for loading and storing global settings
 type SettingsSchema struct {
 type SettingsSchema struct {
 	// these items are used for global settings load
 	// these items are used for global settings load
-	ThemeName    string  `json:"theme"`
-	Scale        float32 `json:"scale"`
-	PrimaryColor string  `json:"primary_color"`
-	CloudName    string  `json:"cloud_name"`
-	CloudConfig  string  `json:"cloud_config"`
+	ThemeName         string  `json:"theme"`
+	Scale             float32 `json:"scale"`
+	PrimaryColor      string  `json:"primary_color"`
+	CloudName         string  `json:"cloud_name"`
+	CloudConfig       string  `json:"cloud_config"`
+	DisableAnimations bool    `json:"no_animations"`
 }
 }
 
 
 // StoragePath returns the location of the settings storage
 // StoragePath returns the location of the settings storage
@@ -70,6 +73,10 @@ func (s *settings) SetTheme(theme fyne.Theme) {
 	s.applyTheme(theme, s.variant)
 	s.applyTheme(theme, s.variant)
 }
 }
 
 
+func (s *settings) ShowAnimations() bool {
+	return !s.schema.DisableAnimations && !noAnimations
+}
+
 func (s *settings) ThemeVariant() fyne.ThemeVariant {
 func (s *settings) ThemeVariant() fyne.ThemeVariant {
 	return s.variant
 	return s.variant
 }
 }

+ 1 - 1
vendor/fyne.io/fyne/v2/app/settings_desktop.go

@@ -41,7 +41,7 @@ func watchFile(path string, callback func()) *fsnotify.Watcher {
 
 
 	go func() {
 	go func() {
 		for event := range watcher.Events {
 		for event := range watcher.Events {
-			if event.Op&fsnotify.Remove != 0 { // if it was deleted then watch again
+			if event.Op.Has(fsnotify.Remove) { // if it was deleted then watch again
 				watcher.Remove(path) // fsnotify returns false positives, see https://github.com/fsnotify/fsnotify/issues/268
 				watcher.Remove(path) // fsnotify returns false positives, see https://github.com/fsnotify/fsnotify/issues/268
 
 
 				watchFileAddTarget(watcher, path)
 				watchFileAddTarget(watcher, path)

+ 8 - 0
vendor/fyne.io/fyne/v2/app/settings_noanimation.go

@@ -0,0 +1,8 @@
+//go:build no_animations
+// +build no_animations
+
+package app
+
+func init() {
+	noAnimations = true
+}

+ 3 - 1
vendor/fyne.io/fyne/v2/canvas/circle.go

@@ -2,6 +2,7 @@ package canvas
 
 
 import (
 import (
 	"image/color"
 	"image/color"
+	"math"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 )
 )
@@ -79,7 +80,8 @@ func (c *Circle) Show() {
 
 
 // Size returns the current size of bounding box for this circle object
 // Size returns the current size of bounding box for this circle object
 func (c *Circle) Size() fyne.Size {
 func (c *Circle) Size() fyne.Size {
-	return fyne.NewSize(c.Position2.X-c.Position1.X, c.Position2.Y-c.Position1.Y)
+	return fyne.NewSize(float32(math.Abs(float64(c.Position2.X)-float64(c.Position1.X))),
+		float32(math.Abs(float64(c.Position2.Y)-float64(c.Position1.Y))))
 }
 }
 
 
 // Visible returns true if this circle is visible, false otherwise
 // Visible returns true if this circle is visible, false otherwise

+ 202 - 20
vendor/fyne.io/fyne/v2/canvas/image.go

@@ -1,12 +1,20 @@
 package canvas
 package canvas
 
 
 import (
 import (
+	"bytes"
+	"errors"
 	"image"
 	"image"
+	_ "image/jpeg" // avoid users having to import when using image widget
+	_ "image/png"  // avoid the same for PNG images
 	"io"
 	"io"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"path/filepath"
+	"sync"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/internal/cache"
+	"fyne.io/fyne/v2/internal/scale"
+	"fyne.io/fyne/v2/internal/svg"
 	"fyne.io/fyne/v2/storage"
 	"fyne.io/fyne/v2/storage"
 )
 )
 
 
@@ -51,6 +59,11 @@ var _ fyne.CanvasObject = (*Image)(nil)
 type Image struct {
 type Image struct {
 	baseObject
 	baseObject
 
 
+	aspect float32
+	icon   *svg.Decoder
+	isSVG  bool
+	lock   sync.Mutex
+
 	// one of the following sources will provide our image data
 	// one of the following sources will provide our image data
 	File     string        // Load the image from a file
 	File     string        // Load the image from a file
 	Resource fyne.Resource // Load the image from an in-memory resource
 	Resource fyne.Resource // Load the image from an in-memory resource
@@ -59,7 +72,6 @@ type Image struct {
 	Translucency float64    // Set a translucency value > 0.0 to fade the image
 	Translucency float64    // Set a translucency value > 0.0 to fade the image
 	FillMode     ImageFill  // Specify how the image should expand to fill or fit the available space
 	FillMode     ImageFill  // Specify how the image should expand to fill or fit the available space
 	ScaleMode    ImageScale // Specify the type of scaling interpolation applied to the image
 	ScaleMode    ImageScale // Specify the type of scaling interpolation applied to the image
-
 }
 }
 
 
 // Alpha is a convenience function that returns the alpha value for an image
 // Alpha is a convenience function that returns the alpha value for an image
@@ -68,6 +80,16 @@ func (i *Image) Alpha() float64 {
 	return 1.0 - i.Translucency
 	return 1.0 - i.Translucency
 }
 }
 
 
+// Aspect will return the original content aspect after it was last refreshed.
+//
+// Since: 2.4
+func (i *Image) Aspect() float32 {
+	if i.aspect == 0 {
+		i.Refresh()
+	}
+	return i.aspect
+}
+
 // Hide will set this image to not be visible
 // Hide will set this image to not be visible
 func (i *Image) Hide() {
 func (i *Image) Hide() {
 	i.baseObject.Hide()
 	i.baseObject.Hide()
@@ -75,6 +97,14 @@ func (i *Image) Hide() {
 	repaint(i)
 	repaint(i)
 }
 }
 
 
+// MinSize returns the specified minimum size, if set, or {1, 1} otherwise.
+func (i *Image) MinSize() fyne.Size {
+	if i.Image == nil || i.aspect == 0 {
+		i.Refresh()
+	}
+	return i.baseObject.MinSize()
+}
+
 // Move the image object to a new position, relative to its parent top, left corner.
 // Move the image object to a new position, relative to its parent top, left corner.
 func (i *Image) Move(pos fyne.Position) {
 func (i *Image) Move(pos fyne.Position) {
 	i.baseObject.Move(pos)
 	i.baseObject.Move(pos)
@@ -84,6 +114,58 @@ func (i *Image) Move(pos fyne.Position) {
 
 
 // Refresh causes this image to be redrawn with its configured state.
 // Refresh causes this image to be redrawn with its configured state.
 func (i *Image) Refresh() {
 func (i *Image) Refresh() {
+	i.lock.Lock()
+	defer i.lock.Unlock()
+
+	rc, err := i.updateReader()
+	if err != nil {
+		fyne.LogError("Failed to load image", err)
+		return
+	}
+	if rc != nil {
+		rcMem := rc
+		defer rcMem.Close()
+	}
+
+	if i.File != "" || i.Resource != nil || i.Image != nil {
+		r, err := i.updateAspectAndMinSize(rc)
+		if err != nil {
+			fyne.LogError("Failed to load image", err)
+			return
+		}
+		rc = io.NopCloser(r)
+	}
+
+	if i.File != "" || i.Resource != nil {
+		size := i.Size()
+		width := size.Width
+		height := size.Height
+
+		if width == 0 || height == 0 {
+			return
+		}
+
+		if i.isSVG {
+			tex, err := i.renderSVG(width, height)
+			if err != nil {
+				fyne.LogError("Failed to render SVG", err)
+				return
+			}
+			i.Image = tex
+		} else {
+			if rc == nil {
+				return
+			}
+
+			img, _, err := image.Decode(rc)
+			if err != nil {
+				fyne.LogError("Failed to render image", err)
+				return
+			}
+			i.Image = img
+		}
+	}
+
 	Refresh(i)
 	Refresh(i)
 }
 }
 
 
@@ -93,22 +175,25 @@ func (i *Image) Resize(s fyne.Size) {
 	if s == i.Size() {
 	if s == i.Size() {
 		return
 		return
 	}
 	}
-	if i.FillMode == ImageFillOriginal && i.size.Height > 2 { // don't refresh original scale images after first draw
+	i.baseObject.Resize(s)
+	if i.FillMode == ImageFillOriginal && i.size.Height > 2 { // we can just ask for a GPU redraw to align
+		Refresh(i)
 		return
 		return
 	}
 	}
 
 
 	i.baseObject.Resize(s)
 	i.baseObject.Resize(s)
-
-	Refresh(i)
+	if i.isSVG || i.Image == nil {
+		i.Refresh() // we need to rasterise at the new size
+	} else {
+		Refresh(i) // just re-size using GPU scaling
+	}
 }
 }
 
 
 // NewImageFromFile creates a new image from a local file.
 // NewImageFromFile creates a new image from a local file.
 // Images returned from this method will scale to fit the canvas object.
 // Images returned from this method will scale to fit the canvas object.
 // The method for scaling can be set using the Fill field.
 // The method for scaling can be set using the Fill field.
 func NewImageFromFile(file string) *Image {
 func NewImageFromFile(file string) *Image {
-	return &Image{
-		File: file,
-	}
+	return &Image{File: file}
 }
 }
 
 
 // NewImageFromURI creates a new image from named resource.
 // NewImageFromURI creates a new image from named resource.
@@ -120,9 +205,7 @@ func NewImageFromFile(file string) *Image {
 // Since: 2.0
 // Since: 2.0
 func NewImageFromURI(uri fyne.URI) *Image {
 func NewImageFromURI(uri fyne.URI) *Image {
 	if uri.Scheme() == "file" && len(uri.String()) > 7 {
 	if uri.Scheme() == "file" && len(uri.String()) > 7 {
-		return &Image{
-			File: uri.String()[7:],
-		}
+		return NewImageFromFile(uri.Path())
 	}
 	}
 
 
 	var read io.ReadCloser
 	var read io.ReadCloser
@@ -145,28 +228,25 @@ func NewImageFromURI(uri fyne.URI) *Image {
 //
 //
 // Since: 2.0
 // Since: 2.0
 func NewImageFromReader(read io.Reader, name string) *Image {
 func NewImageFromReader(read io.Reader, name string) *Image {
-	data, err := ioutil.ReadAll(read)
+	data, err := io.ReadAll(read)
 	if err != nil {
 	if err != nil {
 		fyne.LogError("Unable to read image data", err)
 		fyne.LogError("Unable to read image data", err)
 		return nil
 		return nil
 	}
 	}
+
 	res := &fyne.StaticResource{
 	res := &fyne.StaticResource{
 		StaticName:    name,
 		StaticName:    name,
 		StaticContent: data,
 		StaticContent: data,
 	}
 	}
 
 
-	return &Image{
-		Resource: res,
-	}
+	return NewImageFromResource(res)
 }
 }
 
 
 // NewImageFromResource creates a new image by loading the specified resource.
 // NewImageFromResource creates a new image by loading the specified resource.
 // Images returned from this method will scale to fit the canvas object.
 // Images returned from this method will scale to fit the canvas object.
 // The method for scaling can be set using the Fill field.
 // The method for scaling can be set using the Fill field.
 func NewImageFromResource(res fyne.Resource) *Image {
 func NewImageFromResource(res fyne.Resource) *Image {
-	return &Image{
-		Resource: res,
-	}
+	return &Image{Resource: res}
 }
 }
 
 
 // NewImageFromImage returns a new Image instance that is rendered from the Go
 // NewImageFromImage returns a new Image instance that is rendered from the Go
@@ -174,7 +254,109 @@ func NewImageFromResource(res fyne.Resource) *Image {
 // Images returned from this method will scale to fit the canvas object.
 // Images returned from this method will scale to fit the canvas object.
 // The method for scaling can be set using the Fill field.
 // The method for scaling can be set using the Fill field.
 func NewImageFromImage(img image.Image) *Image {
 func NewImageFromImage(img image.Image) *Image {
-	return &Image{
-		Image: img,
+	return &Image{Image: img}
+}
+
+func (i *Image) name() string {
+	if i.Resource != nil {
+		return i.Resource.Name()
+	} else if i.File != "" {
+		return i.File
+	}
+	return ""
+}
+
+func (i *Image) updateReader() (io.ReadCloser, error) {
+	i.isSVG = false
+	if i.Resource != nil {
+		i.isSVG = svg.IsResourceSVG(i.Resource)
+		return io.NopCloser(bytes.NewReader(i.Resource.Content())), nil
+	} else if i.File != "" {
+		var err error
+
+		fd, err := os.Open(i.File)
+		if err != nil {
+			return nil, err
+		}
+		i.isSVG = svg.IsFileSVG(i.File)
+		return fd, nil
+	}
+	return nil, nil
+}
+
+func (i *Image) updateAspectAndMinSize(reader io.Reader) (io.Reader, error) {
+	var pixWidth, pixHeight int
+
+	if reader != nil {
+		r, width, height, aspect, err := i.imageDetailsFromReader(reader)
+		if err != nil {
+			return nil, err
+		}
+		reader = r
+		i.aspect = aspect
+		pixWidth, pixHeight = width, height
+	} else if i.Image != nil {
+		original := i.Image.Bounds().Size()
+		i.aspect = float32(original.X) / float32(original.Y)
+		pixWidth, pixHeight = original.X, original.Y
+	} else {
+		return nil, errors.New("no matching image source")
+	}
+
+	if i.FillMode == ImageFillOriginal {
+		i.SetMinSize(scale.ToFyneSize(i, pixWidth, pixHeight))
+	}
+	return reader, nil
+}
+
+func (i *Image) imageDetailsFromReader(source io.Reader) (reader io.Reader, width, height int, aspect float32, err error) {
+	if source == nil {
+		return nil, 0, 0, 0, errors.New("no matching reading reader")
+	}
+
+	if i.isSVG {
+		var err error
+
+		i.icon, err = svg.NewDecoder(source)
+		if err != nil {
+			return nil, 0, 0, 0, err
+		}
+		config := i.icon.Config()
+		width, height = config.Width, config.Height
+		aspect = config.Aspect
+	} else {
+		var buf bytes.Buffer
+		tee := io.TeeReader(source, &buf)
+		reader = io.MultiReader(&buf, source)
+
+		config, _, err := image.DecodeConfig(tee)
+		if err != nil {
+			return nil, 0, 0, 0, err
+		}
+		width, height = config.Width, config.Height
+		aspect = float32(width) / float32(height)
+	}
+	return
+}
+
+func (i *Image) renderSVG(width, height float32) (image.Image, error) {
+	c := fyne.CurrentApp().Driver().CanvasForObject(i)
+	screenWidth, screenHeight := int(width), int(height)
+	if c != nil {
+		// We want real output pixel count not just the screen coordinate space (i.e. macOS Retina)
+		screenWidth, screenHeight = c.PixelCoordinateForPosition(fyne.Position{X: width, Y: height})
+	}
+
+	tex := cache.GetSvg(i.name(), screenWidth, screenHeight)
+	if tex != nil {
+		return tex, nil
+	}
+
+	var err error
+	tex, err = i.icon.Draw(screenWidth, screenHeight)
+	if err != nil {
+		return nil, err
 	}
 	}
+	cache.SetSvg(i.name(), tex, screenWidth, screenHeight)
+	return tex, nil
 }
 }

+ 4 - 0
vendor/fyne.io/fyne/v2/canvas/rectangle.go

@@ -16,6 +16,10 @@ type Rectangle struct {
 	FillColor   color.Color // The rectangle fill color
 	FillColor   color.Color // The rectangle fill color
 	StrokeColor color.Color // The rectangle stroke color
 	StrokeColor color.Color // The rectangle stroke color
 	StrokeWidth float32     // The stroke width of the rectangle
 	StrokeWidth float32     // The stroke width of the rectangle
+	// The radius of the rectangle corners
+	//
+	// Since: 2.4
+	CornerRadius float32
 }
 }
 
 
 // Hide will set this rectangle to not be visible
 // Hide will set this rectangle to not be visible

+ 17 - 14
vendor/fyne.io/fyne/v2/container/apptabs.go

@@ -221,7 +221,6 @@ func (t *AppTabs) SetTabLocation(l TabLocation) {
 func (t *AppTabs) Show() {
 func (t *AppTabs) Show() {
 	t.BaseWidget.Show()
 	t.BaseWidget.Show()
 	t.SelectIndex(t.current)
 	t.SelectIndex(t.current)
-	t.Refresh()
 }
 }
 
 
 func (t *AppTabs) onUnselected() func(*TabItem) {
 func (t *AppTabs) onUnselected() func(*TabItem) {
@@ -277,18 +276,22 @@ type appTabsRenderer struct {
 
 
 func (r *appTabsRenderer) Layout(size fyne.Size) {
 func (r *appTabsRenderer) Layout(size fyne.Size) {
 	// Try render as many tabs as will fit, others will appear in the overflow
 	// Try render as many tabs as will fit, others will appear in the overflow
-	for i := len(r.appTabs.Items); i > 0; i-- {
-		r.updateTabs(i)
-		barMin := r.bar.MinSize()
-		if r.appTabs.location == TabLocationLeading || r.appTabs.location == TabLocationTrailing {
-			if barMin.Height <= size.Height {
-				// Tab bar is short enough to fit
-				break
-			}
-		} else {
-			if barMin.Width <= size.Width {
-				// Tab bar is thin enough to fit
-				break
+	if len(r.appTabs.Items) == 0 {
+		r.updateTabs(0)
+	} else {
+		for i := len(r.appTabs.Items); i > 0; i-- {
+			r.updateTabs(i)
+			barMin := r.bar.MinSize()
+			if r.appTabs.location == TabLocationLeading || r.appTabs.location == TabLocationTrailing {
+				if barMin.Height <= size.Height {
+					// Tab bar is short enough to fit
+					break
+				}
+			} else {
+				if barMin.Width <= size.Width {
+					// Tab bar is thin enough to fit
+					break
+				}
 			}
 			}
 		}
 		}
 	}
 	}
@@ -435,7 +438,7 @@ func (r *appTabsRenderer) updateTabs(max int) {
 	// Set overflow action
 	// Set overflow action
 	if tabCount <= max {
 	if tabCount <= max {
 		r.action.Hide()
 		r.action.Hide()
-		r.bar.Layout = layout.NewMaxLayout()
+		r.bar.Layout = layout.NewStackLayout()
 	} else {
 	} else {
 		tabCount = max
 		tabCount = max
 		r.action.Show()
 		r.action.Show()

+ 2 - 2
vendor/fyne.io/fyne/v2/container/container.go

@@ -9,12 +9,12 @@ import (
 //
 //
 // Since: 2.0
 // Since: 2.0
 func New(layout fyne.Layout, objects ...fyne.CanvasObject) *fyne.Container {
 func New(layout fyne.Layout, objects ...fyne.CanvasObject) *fyne.Container {
-	return fyne.NewContainerWithLayout(layout, objects...)
+	return &fyne.Container{Layout: layout, Objects: objects}
 }
 }
 
 
 // NewWithoutLayout returns a new Container instance holding the specified CanvasObjects that are manually arranged.
 // NewWithoutLayout returns a new Container instance holding the specified CanvasObjects that are manually arranged.
 //
 //
 // Since: 2.0
 // Since: 2.0
 func NewWithoutLayout(objects ...fyne.CanvasObject) *fyne.Container {
 func NewWithoutLayout(objects ...fyne.CanvasObject) *fyne.Container {
-	return fyne.NewContainerWithoutLayout(objects...)
+	return &fyne.Container{Objects: objects}
 }
 }

+ 5 - 1
vendor/fyne.io/fyne/v2/container/doctabs.go

@@ -178,7 +178,6 @@ func (t *DocTabs) SetTabLocation(l TabLocation) {
 func (t *DocTabs) Show() {
 func (t *DocTabs) Show() {
 	t.BaseWidget.Show()
 	t.BaseWidget.Show()
 	t.SelectIndex(t.current)
 	t.SelectIndex(t.current)
-	t.Refresh()
 }
 }
 
 
 func (t *DocTabs) close(item *TabItem) {
 func (t *DocTabs) close(item *TabItem) {
@@ -245,7 +244,12 @@ func (r *docTabsRenderer) Layout(size fyne.Size) {
 	r.updateCreateTab()
 	r.updateCreateTab()
 	r.updateTabs()
 	r.updateTabs()
 	r.layout(r.docTabs, size)
 	r.layout(r.docTabs, size)
+
+	// lay out buttons before updating indicator, which is relative to their position
+	buttons := r.scroller.Content.(*fyne.Container)
+	buttons.Layout.Layout(buttons.Objects, buttons.Size())
 	r.updateIndicator(r.docTabs.transitioning())
 	r.updateIndicator(r.docTabs.transitioning())
+
 	if r.docTabs.transitioning() {
 	if r.docTabs.transitioning() {
 		r.docTabs.setTransitioning(false)
 		r.docTabs.setTransitioning(false)
 	}
 	}

+ 13 - 1
vendor/fyne.io/fyne/v2/container/layouts.go

@@ -87,8 +87,10 @@ func NewHBox(objects ...fyne.CanvasObject) *fyne.Container {
 // NewMax creates a new container with the specified objects filling the available space.
 // NewMax creates a new container with the specified objects filling the available space.
 //
 //
 // Since: 1.4
 // Since: 1.4
+//
+// Deprecated: Use container.NewStack() instead.
 func NewMax(objects ...fyne.CanvasObject) *fyne.Container {
 func NewMax(objects ...fyne.CanvasObject) *fyne.Container {
-	return New(layout.NewMaxLayout(), objects...)
+	return NewStack(objects...)
 }
 }
 
 
 // NewPadded creates a new container with the specified objects inset by standard padding size.
 // NewPadded creates a new container with the specified objects inset by standard padding size.
@@ -98,6 +100,16 @@ func NewPadded(objects ...fyne.CanvasObject) *fyne.Container {
 	return New(layout.NewPaddedLayout(), objects...)
 	return New(layout.NewPaddedLayout(), objects...)
 }
 }
 
 
+// NewStack returns a new container that stacks objects on top of each other.
+// Objects at the end of the container will be stacked on top of objects before.
+// Having only a single object has no impact as CanvasObjects will
+// fill the available space even without a Stack.
+//
+// Since: 2.4
+func NewStack(objects ...fyne.CanvasObject) *fyne.Container {
+	return New(layout.NewStackLayout(), objects...)
+}
+
 // NewVBox creates a new container with the specified objects and using the VBox layout.
 // NewVBox creates a new container with the specified objects and using the VBox layout.
 // The objects will be stacked in the container from top to bottom and always displayed
 // The objects will be stacked in the container from top to bottom and always displayed
 // at their vertical MinSize. Use a different layout if the objects are intended
 // at their vertical MinSize. Use a different layout if the objects are intended

+ 31 - 18
vendor/fyne.io/fyne/v2/container/split.go

@@ -217,8 +217,10 @@ var _ desktop.Hoverable = (*divider)(nil)
 
 
 type divider struct {
 type divider struct {
 	widget.BaseWidget
 	widget.BaseWidget
-	split   *Split
-	hovered bool
+	split          *Split
+	hovered        bool
+	startDragOff   *fyne.Position
+	currentDragPos fyne.Position
 }
 }
 
 
 func newDivider(split *Split) *divider {
 func newDivider(split *Split) *divider {
@@ -250,26 +252,37 @@ func (d *divider) Cursor() desktop.Cursor {
 }
 }
 
 
 func (d *divider) DragEnd() {
 func (d *divider) DragEnd() {
+	d.startDragOff = nil
 }
 }
 
 
-func (d *divider) Dragged(event *fyne.DragEvent) {
-	offset := d.split.Offset
+func (d *divider) Dragged(e *fyne.DragEvent) {
+	if d.startDragOff == nil {
+		d.currentDragPos = d.Position().Add(e.Position)
+		start := e.Position.Subtract(e.Dragged)
+		d.startDragOff = &start
+	} else {
+		d.currentDragPos = d.currentDragPos.Add(e.Dragged)
+	}
+
+	x, y := d.currentDragPos.Components()
+	var offset, leadingRatio, trailingRatio float64
 	if d.split.Horizontal {
 	if d.split.Horizontal {
-		if leadingRatio := float64(d.split.Leading.Size().Width) / float64(d.split.Size().Width); offset < leadingRatio {
-			offset = leadingRatio
-		}
-		if trailingRatio := 1. - (float64(d.split.Trailing.Size().Width) / float64(d.split.Size().Width)); offset > trailingRatio {
-			offset = trailingRatio
-		}
-		offset += float64(event.Dragged.DX) / float64(d.split.Size().Width)
+		widthFree := float64(d.split.Size().Width - dividerThickness())
+		leadingRatio = float64(d.split.Leading.MinSize().Width) / widthFree
+		trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Width) / widthFree)
+		offset = float64(x-d.startDragOff.X) / widthFree
 	} else {
 	} else {
-		if leadingRatio := float64(d.split.Leading.Size().Height) / float64(d.split.Size().Height); offset < leadingRatio {
-			offset = leadingRatio
-		}
-		if trailingRatio := 1. - (float64(d.split.Trailing.Size().Height) / float64(d.split.Size().Height)); offset > trailingRatio {
-			offset = trailingRatio
-		}
-		offset += float64(event.Dragged.DY) / float64(d.split.Size().Height)
+		heightFree := float64(d.split.Size().Height - dividerThickness())
+		leadingRatio = float64(d.split.Leading.MinSize().Height) / heightFree
+		trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Height) / heightFree)
+		offset = float64(y-d.startDragOff.Y) / heightFree
+	}
+
+	if offset < leadingRatio {
+		offset = leadingRatio
+	}
+	if offset > trailingRatio {
+		offset = trailingRatio
 	}
 	}
 	d.split.SetOffset(offset)
 	d.split.SetOffset(offset)
 }
 }

+ 47 - 36
vendor/fyne.io/fyne/v2/container/tabs.go

@@ -209,7 +209,7 @@ func selectItem(t baseTabs, item *TabItem) {
 }
 }
 
 
 func setItems(t baseTabs, items []*TabItem) {
 func setItems(t baseTabs, items []*TabItem) {
-	if mismatchedTabItems(items) {
+	if internal.HintsEnabled && mismatchedTabItems(items) {
 		internal.LogHint("Tab items should all have the same type of content (text, icons or both)")
 		internal.LogHint("Tab items should all have the same type of content (text, icons or both)")
 	}
 	}
 	t.setItems(items)
 	t.setItems(items)
@@ -303,6 +303,7 @@ func (r *baseTabsRenderer) applyTheme(t baseTabs) {
 	}
 	}
 	r.divider.FillColor = theme.ShadowColor()
 	r.divider.FillColor = theme.ShadowColor()
 	r.indicator.FillColor = theme.PrimaryColor()
 	r.indicator.FillColor = theme.PrimaryColor()
+	r.indicator.CornerRadius = theme.SelectionRadiusSize()
 }
 }
 
 
 func (r *baseTabsRenderer) layout(t baseTabs, size fyne.Size) {
 func (r *baseTabsRenderer) layout(t baseTabs, size fyne.Size) {
@@ -313,39 +314,40 @@ func (r *baseTabsRenderer) layout(t baseTabs, size fyne.Size) {
 
 
 	barMin := r.bar.MinSize()
 	barMin := r.bar.MinSize()
 
 
+	padding := theme.Padding()
 	switch t.tabLocation() {
 	switch t.tabLocation() {
 	case TabLocationTop:
 	case TabLocationTop:
 		barHeight := barMin.Height
 		barHeight := barMin.Height
 		barPos = fyne.NewPos(0, 0)
 		barPos = fyne.NewPos(0, 0)
 		barSize = fyne.NewSize(size.Width, barHeight)
 		barSize = fyne.NewSize(size.Width, barHeight)
 		dividerPos = fyne.NewPos(0, barHeight)
 		dividerPos = fyne.NewPos(0, barHeight)
-		dividerSize = fyne.NewSize(size.Width, theme.Padding())
-		contentPos = fyne.NewPos(0, barHeight+theme.Padding())
-		contentSize = fyne.NewSize(size.Width, size.Height-barHeight-theme.Padding())
+		dividerSize = fyne.NewSize(size.Width, padding)
+		contentPos = fyne.NewPos(0, barHeight+padding)
+		contentSize = fyne.NewSize(size.Width, size.Height-barHeight-padding)
 	case TabLocationLeading:
 	case TabLocationLeading:
 		barWidth := barMin.Width
 		barWidth := barMin.Width
 		barPos = fyne.NewPos(0, 0)
 		barPos = fyne.NewPos(0, 0)
 		barSize = fyne.NewSize(barWidth, size.Height)
 		barSize = fyne.NewSize(barWidth, size.Height)
 		dividerPos = fyne.NewPos(barWidth, 0)
 		dividerPos = fyne.NewPos(barWidth, 0)
-		dividerSize = fyne.NewSize(theme.Padding(), size.Height)
+		dividerSize = fyne.NewSize(padding, size.Height)
 		contentPos = fyne.NewPos(barWidth+theme.Padding(), 0)
 		contentPos = fyne.NewPos(barWidth+theme.Padding(), 0)
-		contentSize = fyne.NewSize(size.Width-barWidth-theme.Padding(), size.Height)
+		contentSize = fyne.NewSize(size.Width-barWidth-padding, size.Height)
 	case TabLocationBottom:
 	case TabLocationBottom:
 		barHeight := barMin.Height
 		barHeight := barMin.Height
 		barPos = fyne.NewPos(0, size.Height-barHeight)
 		barPos = fyne.NewPos(0, size.Height-barHeight)
 		barSize = fyne.NewSize(size.Width, barHeight)
 		barSize = fyne.NewSize(size.Width, barHeight)
-		dividerPos = fyne.NewPos(0, size.Height-barHeight-theme.Padding())
-		dividerSize = fyne.NewSize(size.Width, theme.Padding())
+		dividerPos = fyne.NewPos(0, size.Height-barHeight-padding)
+		dividerSize = fyne.NewSize(size.Width, padding)
 		contentPos = fyne.NewPos(0, 0)
 		contentPos = fyne.NewPos(0, 0)
-		contentSize = fyne.NewSize(size.Width, size.Height-barHeight-theme.Padding())
+		contentSize = fyne.NewSize(size.Width, size.Height-barHeight-padding)
 	case TabLocationTrailing:
 	case TabLocationTrailing:
 		barWidth := barMin.Width
 		barWidth := barMin.Width
 		barPos = fyne.NewPos(size.Width-barWidth, 0)
 		barPos = fyne.NewPos(size.Width-barWidth, 0)
 		barSize = fyne.NewSize(barWidth, size.Height)
 		barSize = fyne.NewSize(barWidth, size.Height)
-		dividerPos = fyne.NewPos(size.Width-barWidth-theme.Padding(), 0)
-		dividerSize = fyne.NewSize(theme.Padding(), size.Height)
+		dividerPos = fyne.NewPos(size.Width-barWidth-padding, 0)
+		dividerSize = fyne.NewSize(padding, size.Height)
 		contentPos = fyne.NewPos(0, 0)
 		contentPos = fyne.NewPos(0, 0)
-		contentSize = fyne.NewSize(size.Width-barWidth-theme.Padding(), size.Height)
+		contentSize = fyne.NewSize(size.Width-barWidth-padding, size.Height)
 	}
 	}
 
 
 	r.bar.Move(barPos)
 	r.bar.Move(barPos)
@@ -430,7 +432,7 @@ func (r *baseTabsRenderer) moveIndicator(pos fyne.Position, siz fyne.Size, anima
 	r.lastIndicatorHidden = r.indicator.Hidden
 	r.lastIndicatorHidden = r.indicator.Hidden
 	r.lastIndicatorMutex.Unlock()
 	r.lastIndicatorMutex.Unlock()
 
 
-	if animate {
+	if animate && fyne.CurrentApp().Settings().ShowAnimations() {
 		r.positionAnimation = canvas.NewPositionAnimation(r.indicator.Position(), pos, canvas.DurationShort, func(p fyne.Position) {
 		r.positionAnimation = canvas.NewPositionAnimation(r.indicator.Position(), pos, canvas.DurationShort, func(p fyne.Position) {
 			r.indicator.Move(p)
 			r.indicator.Move(p)
 			r.indicator.Refresh()
 			r.indicator.Refresh()
@@ -489,7 +491,7 @@ type tabButton struct {
 	hovered       bool
 	hovered       bool
 	icon          fyne.Resource
 	icon          fyne.Resource
 	iconPosition  buttonIconPosition
 	iconPosition  buttonIconPosition
-	importance    widget.ButtonImportance
+	importance    widget.Importance
 	onTapped      func()
 	onTapped      func()
 	onClosed      func()
 	onClosed      func()
 	text          string
 	text          string
@@ -499,6 +501,7 @@ type tabButton struct {
 func (b *tabButton) CreateRenderer() fyne.WidgetRenderer {
 func (b *tabButton) CreateRenderer() fyne.WidgetRenderer {
 	b.ExtendBaseWidget(b)
 	b.ExtendBaseWidget(b)
 	background := canvas.NewRectangle(theme.HoverColor())
 	background := canvas.NewRectangle(theme.HoverColor())
+	background.CornerRadius = theme.SelectionRadiusSize()
 	background.Hide()
 	background.Hide()
 	icon := canvas.NewImageFromResource(b.icon)
 	icon := canvas.NewImageFromResource(b.icon)
 	if b.icon == nil {
 	if b.icon == nil {
@@ -577,15 +580,16 @@ func (r *tabButtonRenderer) Layout(size fyne.Size) {
 	innerOffset := fyne.NewPos(padding.Width/2, padding.Height/2)
 	innerOffset := fyne.NewPos(padding.Width/2, padding.Height/2)
 	labelShift := float32(0)
 	labelShift := float32(0)
 	if r.icon.Visible() {
 	if r.icon.Visible() {
+		iconSize := r.iconSize()
 		var iconOffset fyne.Position
 		var iconOffset fyne.Position
 		if r.button.iconPosition == buttonIconTop {
 		if r.button.iconPosition == buttonIconTop {
-			iconOffset = fyne.NewPos((innerSize.Width-r.iconSize())/2, 0)
+			iconOffset = fyne.NewPos((innerSize.Width-iconSize)/2, 0)
 		} else {
 		} else {
-			iconOffset = fyne.NewPos(0, (innerSize.Height-r.iconSize())/2)
+			iconOffset = fyne.NewPos(0, (innerSize.Height-iconSize)/2)
 		}
 		}
-		r.icon.Resize(fyne.NewSize(r.iconSize(), r.iconSize()))
+		r.icon.Resize(fyne.NewSquareSize(iconSize))
 		r.icon.Move(innerOffset.Add(iconOffset))
 		r.icon.Move(innerOffset.Add(iconOffset))
-		labelShift = r.iconSize() + theme.Padding()
+		labelShift = iconSize + theme.Padding()
 	}
 	}
 	if r.label.Text != "" {
 	if r.label.Text != "" {
 		var labelOffset fyne.Position
 		var labelOffset fyne.Position
@@ -600,39 +604,43 @@ func (r *tabButtonRenderer) Layout(size fyne.Size) {
 		r.label.Resize(labelSize)
 		r.label.Resize(labelSize)
 		r.label.Move(innerOffset.Add(labelOffset))
 		r.label.Move(innerOffset.Add(labelOffset))
 	}
 	}
-	r.close.Move(fyne.NewPos(size.Width-theme.IconInlineSize()-theme.Padding(), (size.Height-theme.IconInlineSize())/2))
-	r.close.Resize(fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize()))
+	inlineIconSize := theme.IconInlineSize()
+	r.close.Move(fyne.NewPos(size.Width-inlineIconSize-theme.Padding(), (size.Height-inlineIconSize)/2))
+	r.close.Resize(fyne.NewSquareSize(inlineIconSize))
 }
 }
 
 
 func (r *tabButtonRenderer) MinSize() fyne.Size {
 func (r *tabButtonRenderer) MinSize() fyne.Size {
 	var contentWidth, contentHeight float32
 	var contentWidth, contentHeight float32
 	textSize := r.label.MinSize()
 	textSize := r.label.MinSize()
+	iconSize := r.iconSize()
+	padding := theme.Padding()
 	if r.button.iconPosition == buttonIconTop {
 	if r.button.iconPosition == buttonIconTop {
-		contentWidth = fyne.Max(textSize.Width, r.iconSize())
+		contentWidth = fyne.Max(textSize.Width, iconSize)
 		if r.icon.Visible() {
 		if r.icon.Visible() {
-			contentHeight += r.iconSize()
+			contentHeight += iconSize
 		}
 		}
 		if r.label.Text != "" {
 		if r.label.Text != "" {
 			if r.icon.Visible() {
 			if r.icon.Visible() {
-				contentHeight += theme.Padding()
+				contentHeight += padding
 			}
 			}
 			contentHeight += textSize.Height
 			contentHeight += textSize.Height
 		}
 		}
 	} else {
 	} else {
-		contentHeight = fyne.Max(textSize.Height, r.iconSize())
+		contentHeight = fyne.Max(textSize.Height, iconSize)
 		if r.icon.Visible() {
 		if r.icon.Visible() {
-			contentWidth += r.iconSize()
+			contentWidth += iconSize
 		}
 		}
 		if r.label.Text != "" {
 		if r.label.Text != "" {
 			if r.icon.Visible() {
 			if r.icon.Visible() {
-				contentWidth += theme.Padding()
+				contentWidth += padding
 			}
 			}
 			contentWidth += textSize.Width
 			contentWidth += textSize.Width
 		}
 		}
 	}
 	}
 	if r.button.onClosed != nil {
 	if r.button.onClosed != nil {
-		contentWidth += theme.IconInlineSize() + theme.Padding()
-		contentHeight = fyne.Max(contentHeight, theme.IconInlineSize())
+		inlineIconSize := theme.IconInlineSize()
+		contentWidth += inlineIconSize + padding
+		contentHeight = fyne.Max(contentHeight, inlineIconSize)
 	}
 	}
 	return fyne.NewSize(contentWidth, contentHeight).Add(r.padding())
 	return fyne.NewSize(contentWidth, contentHeight).Add(r.padding())
 }
 }
@@ -644,6 +652,7 @@ func (r *tabButtonRenderer) Objects() []fyne.CanvasObject {
 func (r *tabButtonRenderer) Refresh() {
 func (r *tabButtonRenderer) Refresh() {
 	if r.button.hovered && !r.button.Disabled() {
 	if r.button.hovered && !r.button.Disabled() {
 		r.background.FillColor = theme.HoverColor()
 		r.background.FillColor = theme.HoverColor()
+		r.background.CornerRadius = theme.SelectionRadiusSize()
 		r.background.Show()
 		r.background.Show()
 	} else {
 	} else {
 		r.background.Hide()
 		r.background.Hide()
@@ -659,7 +668,7 @@ func (r *tabButtonRenderer) Refresh() {
 			r.label.Color = theme.ForegroundColor()
 			r.label.Color = theme.ForegroundColor()
 		}
 		}
 	} else {
 	} else {
-		r.label.Color = theme.DisabledTextColor()
+		r.label.Color = theme.DisabledColor()
 	}
 	}
 	r.label.TextSize = theme.TextSize()
 	r.label.TextSize = theme.TextSize()
 	if r.button.text == "" {
 	if r.button.text == "" {
@@ -698,19 +707,19 @@ func (r *tabButtonRenderer) Refresh() {
 }
 }
 
 
 func (r *tabButtonRenderer) iconSize() float32 {
 func (r *tabButtonRenderer) iconSize() float32 {
-	switch r.button.iconPosition {
-	case buttonIconTop:
+	if r.button.iconPosition == buttonIconTop {
 		return 2 * theme.IconInlineSize()
 		return 2 * theme.IconInlineSize()
-	default:
-		return theme.IconInlineSize()
 	}
 	}
+
+	return theme.IconInlineSize()
 }
 }
 
 
 func (r *tabButtonRenderer) padding() fyne.Size {
 func (r *tabButtonRenderer) padding() fyne.Size {
+	padding := theme.InnerPadding()
 	if r.label.Text != "" && r.button.iconPosition == buttonIconInline {
 	if r.label.Text != "" && r.button.iconPosition == buttonIconInline {
-		return fyne.NewSize(theme.InnerPadding()*2, theme.InnerPadding()*2)
+		return fyne.NewSquareSize(padding * 2)
 	}
 	}
-	return fyne.NewSize(theme.InnerPadding(), theme.InnerPadding()*2)
+	return fyne.NewSize(padding, padding*2)
 }
 }
 
 
 var _ fyne.Widget = (*tabCloseButton)(nil)
 var _ fyne.Widget = (*tabCloseButton)(nil)
@@ -727,6 +736,7 @@ type tabCloseButton struct {
 func (b *tabCloseButton) CreateRenderer() fyne.WidgetRenderer {
 func (b *tabCloseButton) CreateRenderer() fyne.WidgetRenderer {
 	b.ExtendBaseWidget(b)
 	b.ExtendBaseWidget(b)
 	background := canvas.NewRectangle(theme.HoverColor())
 	background := canvas.NewRectangle(theme.HoverColor())
+	background.CornerRadius = theme.SelectionRadiusSize()
 	background.Hide()
 	background.Hide()
 	icon := canvas.NewImageFromResource(theme.CancelIcon())
 	icon := canvas.NewImageFromResource(theme.CancelIcon())
 
 
@@ -778,7 +788,7 @@ func (r *tabCloseButtonRenderer) Layout(size fyne.Size) {
 }
 }
 
 
 func (r *tabCloseButtonRenderer) MinSize() fyne.Size {
 func (r *tabCloseButtonRenderer) MinSize() fyne.Size {
-	return fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize())
+	return fyne.NewSquareSize(theme.IconInlineSize())
 }
 }
 
 
 func (r *tabCloseButtonRenderer) Objects() []fyne.CanvasObject {
 func (r *tabCloseButtonRenderer) Objects() []fyne.CanvasObject {
@@ -788,6 +798,7 @@ func (r *tabCloseButtonRenderer) Objects() []fyne.CanvasObject {
 func (r *tabCloseButtonRenderer) Refresh() {
 func (r *tabCloseButtonRenderer) Refresh() {
 	if r.button.hovered {
 	if r.button.hovered {
 		r.background.FillColor = theme.HoverColor()
 		r.background.FillColor = theme.HoverColor()
+		r.background.CornerRadius = theme.SelectionRadiusSize()
 		r.background.Show()
 		r.background.Show()
 	} else {
 	} else {
 		r.background.Hide()
 		r.background.Hide()

+ 1816 - 0
vendor/fyne.io/fyne/v2/data/binding/bindtrees.go

@@ -0,0 +1,1816 @@
+// auto-generated
+// **** THIS FILE IS AUTO-GENERATED, PLEASE DO NOT EDIT IT **** //
+
+package binding
+
+import (
+	"bytes"
+
+	"fyne.io/fyne/v2"
+)
+
+// BoolTree supports binding a tree of bool values.
+//
+// Since: 2.4
+type BoolTree interface {
+	DataTree
+
+	Append(parent, id string, value bool) error
+	Get() (map[string][]string, map[string]bool, error)
+	GetValue(id string) (bool, error)
+	Prepend(parent, id string, value bool) error
+	Set(ids map[string][]string, values map[string]bool) error
+	SetValue(id string, value bool) error
+}
+
+// ExternalBoolTree supports binding a tree of bool values from an external variable.
+//
+// Since: 2.4
+type ExternalBoolTree interface {
+	BoolTree
+
+	Reload() error
+}
+
+// NewBoolTree returns a bindable tree of bool values.
+//
+// Since: 2.4
+func NewBoolTree() BoolTree {
+	t := &boundBoolTree{val: &map[string]bool{}}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+	return t
+}
+
+// BindBoolTree returns a bound tree of bool values, based on the contents of the passed values.
+// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
+// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
+//
+// Since: 2.4
+func BindBoolTree(ids *map[string][]string, v *map[string]bool) ExternalBoolTree {
+	if v == nil {
+		return NewBoolTree().(ExternalBoolTree)
+	}
+
+	t := &boundBoolTree{val: v, updateExternal: true}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+
+	for parent, children := range *ids {
+		for _, leaf := range children {
+			t.appendItem(bindBoolTreeItem(v, leaf, t.updateExternal), leaf, parent)
+		}
+	}
+
+	return t
+}
+
+type boundBoolTree struct {
+	treeBase
+
+	updateExternal bool
+	val            *map[string]bool
+}
+
+func (t *boundBoolTree) Append(parent, id string, val bool) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append(ids, id)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundBoolTree) Get() (map[string][]string, map[string]bool, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.ids, *t.val, nil
+}
+
+func (t *boundBoolTree) GetValue(id string) (bool, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	if item, ok := (*t.val)[id]; ok {
+		return item, nil
+	}
+
+	return false, errOutOfBounds
+}
+
+func (t *boundBoolTree) Prepend(parent, id string, val bool) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append([]string{id}, ids...)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundBoolTree) Reload() error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doReload()
+}
+
+func (t *boundBoolTree) Set(ids map[string][]string, v map[string]bool) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	t.ids = ids
+	*t.val = v
+
+	return t.doReload()
+}
+
+func (t *boundBoolTree) doReload() (retErr error) {
+	updated := []string{}
+	fire := false
+	for id := range *t.val {
+		found := false
+		for child := range t.items {
+			if child == id { // update existing
+				updated = append(updated, id)
+				found = true
+				break
+			}
+		}
+		if found {
+			continue
+		}
+
+		// append new
+		t.appendItem(bindBoolTreeItem(t.val, id, t.updateExternal), id, parentIDFor(id, t.ids))
+		updated = append(updated, id)
+		fire = true
+	}
+
+	for id := range t.items {
+		remove := true
+		for _, done := range updated {
+			if done == id {
+				remove = false
+				break
+			}
+		}
+
+		if remove { // remove item no longer present
+			fire = true
+			t.deleteItem(id, parentIDFor(id, t.ids))
+		}
+	}
+	if fire {
+		t.trigger()
+	}
+
+	for id, item := range t.items {
+		var err error
+		if t.updateExternal {
+			item.(*boundExternalBoolTreeItem).lock.Lock()
+			err = item.(*boundExternalBoolTreeItem).setIfChanged((*t.val)[id])
+			item.(*boundExternalBoolTreeItem).lock.Unlock()
+		} else {
+			item.(*boundBoolTreeItem).lock.Lock()
+			err = item.(*boundBoolTreeItem).doSet((*t.val)[id])
+			item.(*boundBoolTreeItem).lock.Unlock()
+		}
+		if err != nil {
+			retErr = err
+		}
+	}
+	return
+}
+
+func (t *boundBoolTree) SetValue(id string, v bool) error {
+	t.lock.Lock()
+	(*t.val)[id] = v
+	t.lock.Unlock()
+
+	item, err := t.GetItem(id)
+	if err != nil {
+		return err
+	}
+	return item.(Bool).Set(v)
+}
+
+func bindBoolTreeItem(v *map[string]bool, id string, external bool) Bool {
+	if external {
+		ret := &boundExternalBoolTreeItem{old: (*v)[id]}
+		ret.val = v
+		ret.id = id
+		return ret
+	}
+
+	return &boundBoolTreeItem{id: id, val: v}
+}
+
+type boundBoolTreeItem struct {
+	base
+
+	val *map[string]bool
+	id  string
+}
+
+func (t *boundBoolTreeItem) Get() (bool, error) {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	v := *t.val
+	if item, ok := v[t.id]; ok {
+		return item, nil
+	}
+
+	return false, errOutOfBounds
+}
+
+func (t *boundBoolTreeItem) Set(val bool) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doSet(val)
+}
+
+func (t *boundBoolTreeItem) doSet(val bool) error {
+	(*t.val)[t.id] = val
+
+	t.trigger()
+	return nil
+}
+
+type boundExternalBoolTreeItem struct {
+	boundBoolTreeItem
+
+	old bool
+}
+
+func (t *boundExternalBoolTreeItem) setIfChanged(val bool) error {
+	if val == t.old {
+		return nil
+	}
+	(*t.val)[t.id] = val
+	t.old = val
+
+	t.trigger()
+	return nil
+}
+
+// BytesTree supports binding a tree of []byte values.
+//
+// Since: 2.4
+type BytesTree interface {
+	DataTree
+
+	Append(parent, id string, value []byte) error
+	Get() (map[string][]string, map[string][]byte, error)
+	GetValue(id string) ([]byte, error)
+	Prepend(parent, id string, value []byte) error
+	Set(ids map[string][]string, values map[string][]byte) error
+	SetValue(id string, value []byte) error
+}
+
+// ExternalBytesTree supports binding a tree of []byte values from an external variable.
+//
+// Since: 2.4
+type ExternalBytesTree interface {
+	BytesTree
+
+	Reload() error
+}
+
+// NewBytesTree returns a bindable tree of []byte values.
+//
+// Since: 2.4
+func NewBytesTree() BytesTree {
+	t := &boundBytesTree{val: &map[string][]byte{}}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+	return t
+}
+
+// BindBytesTree returns a bound tree of []byte values, based on the contents of the passed values.
+// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
+// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
+//
+// Since: 2.4
+func BindBytesTree(ids *map[string][]string, v *map[string][]byte) ExternalBytesTree {
+	if v == nil {
+		return NewBytesTree().(ExternalBytesTree)
+	}
+
+	t := &boundBytesTree{val: v, updateExternal: true}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+
+	for parent, children := range *ids {
+		for _, leaf := range children {
+			t.appendItem(bindBytesTreeItem(v, leaf, t.updateExternal), leaf, parent)
+		}
+	}
+
+	return t
+}
+
+type boundBytesTree struct {
+	treeBase
+
+	updateExternal bool
+	val            *map[string][]byte
+}
+
+func (t *boundBytesTree) Append(parent, id string, val []byte) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append(ids, id)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundBytesTree) Get() (map[string][]string, map[string][]byte, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.ids, *t.val, nil
+}
+
+func (t *boundBytesTree) GetValue(id string) ([]byte, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	if item, ok := (*t.val)[id]; ok {
+		return item, nil
+	}
+
+	return nil, errOutOfBounds
+}
+
+func (t *boundBytesTree) Prepend(parent, id string, val []byte) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append([]string{id}, ids...)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundBytesTree) Reload() error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doReload()
+}
+
+func (t *boundBytesTree) Set(ids map[string][]string, v map[string][]byte) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	t.ids = ids
+	*t.val = v
+
+	return t.doReload()
+}
+
+func (t *boundBytesTree) doReload() (retErr error) {
+	updated := []string{}
+	fire := false
+	for id := range *t.val {
+		found := false
+		for child := range t.items {
+			if child == id { // update existing
+				updated = append(updated, id)
+				found = true
+				break
+			}
+		}
+		if found {
+			continue
+		}
+
+		// append new
+		t.appendItem(bindBytesTreeItem(t.val, id, t.updateExternal), id, parentIDFor(id, t.ids))
+		updated = append(updated, id)
+		fire = true
+	}
+
+	for id := range t.items {
+		remove := true
+		for _, done := range updated {
+			if done == id {
+				remove = false
+				break
+			}
+		}
+
+		if remove { // remove item no longer present
+			fire = true
+			t.deleteItem(id, parentIDFor(id, t.ids))
+		}
+	}
+	if fire {
+		t.trigger()
+	}
+
+	for id, item := range t.items {
+		var err error
+		if t.updateExternal {
+			item.(*boundExternalBytesTreeItem).lock.Lock()
+			err = item.(*boundExternalBytesTreeItem).setIfChanged((*t.val)[id])
+			item.(*boundExternalBytesTreeItem).lock.Unlock()
+		} else {
+			item.(*boundBytesTreeItem).lock.Lock()
+			err = item.(*boundBytesTreeItem).doSet((*t.val)[id])
+			item.(*boundBytesTreeItem).lock.Unlock()
+		}
+		if err != nil {
+			retErr = err
+		}
+	}
+	return
+}
+
+func (t *boundBytesTree) SetValue(id string, v []byte) error {
+	t.lock.Lock()
+	(*t.val)[id] = v
+	t.lock.Unlock()
+
+	item, err := t.GetItem(id)
+	if err != nil {
+		return err
+	}
+	return item.(Bytes).Set(v)
+}
+
+func bindBytesTreeItem(v *map[string][]byte, id string, external bool) Bytes {
+	if external {
+		ret := &boundExternalBytesTreeItem{old: (*v)[id]}
+		ret.val = v
+		ret.id = id
+		return ret
+	}
+
+	return &boundBytesTreeItem{id: id, val: v}
+}
+
+type boundBytesTreeItem struct {
+	base
+
+	val *map[string][]byte
+	id  string
+}
+
+func (t *boundBytesTreeItem) Get() ([]byte, error) {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	v := *t.val
+	if item, ok := v[t.id]; ok {
+		return item, nil
+	}
+
+	return nil, errOutOfBounds
+}
+
+func (t *boundBytesTreeItem) Set(val []byte) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doSet(val)
+}
+
+func (t *boundBytesTreeItem) doSet(val []byte) error {
+	(*t.val)[t.id] = val
+
+	t.trigger()
+	return nil
+}
+
+type boundExternalBytesTreeItem struct {
+	boundBytesTreeItem
+
+	old []byte
+}
+
+func (t *boundExternalBytesTreeItem) setIfChanged(val []byte) error {
+	if bytes.Equal(val, t.old) {
+		return nil
+	}
+	(*t.val)[t.id] = val
+	t.old = val
+
+	t.trigger()
+	return nil
+}
+
+// FloatTree supports binding a tree of float64 values.
+//
+// Since: 2.4
+type FloatTree interface {
+	DataTree
+
+	Append(parent, id string, value float64) error
+	Get() (map[string][]string, map[string]float64, error)
+	GetValue(id string) (float64, error)
+	Prepend(parent, id string, value float64) error
+	Set(ids map[string][]string, values map[string]float64) error
+	SetValue(id string, value float64) error
+}
+
+// ExternalFloatTree supports binding a tree of float64 values from an external variable.
+//
+// Since: 2.4
+type ExternalFloatTree interface {
+	FloatTree
+
+	Reload() error
+}
+
+// NewFloatTree returns a bindable tree of float64 values.
+//
+// Since: 2.4
+func NewFloatTree() FloatTree {
+	t := &boundFloatTree{val: &map[string]float64{}}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+	return t
+}
+
+// BindFloatTree returns a bound tree of float64 values, based on the contents of the passed values.
+// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
+// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
+//
+// Since: 2.4
+func BindFloatTree(ids *map[string][]string, v *map[string]float64) ExternalFloatTree {
+	if v == nil {
+		return NewFloatTree().(ExternalFloatTree)
+	}
+
+	t := &boundFloatTree{val: v, updateExternal: true}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+
+	for parent, children := range *ids {
+		for _, leaf := range children {
+			t.appendItem(bindFloatTreeItem(v, leaf, t.updateExternal), leaf, parent)
+		}
+	}
+
+	return t
+}
+
+type boundFloatTree struct {
+	treeBase
+
+	updateExternal bool
+	val            *map[string]float64
+}
+
+func (t *boundFloatTree) Append(parent, id string, val float64) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append(ids, id)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundFloatTree) Get() (map[string][]string, map[string]float64, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.ids, *t.val, nil
+}
+
+func (t *boundFloatTree) GetValue(id string) (float64, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	if item, ok := (*t.val)[id]; ok {
+		return item, nil
+	}
+
+	return 0.0, errOutOfBounds
+}
+
+func (t *boundFloatTree) Prepend(parent, id string, val float64) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append([]string{id}, ids...)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundFloatTree) Reload() error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doReload()
+}
+
+func (t *boundFloatTree) Set(ids map[string][]string, v map[string]float64) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	t.ids = ids
+	*t.val = v
+
+	return t.doReload()
+}
+
+func (t *boundFloatTree) doReload() (retErr error) {
+	updated := []string{}
+	fire := false
+	for id := range *t.val {
+		found := false
+		for child := range t.items {
+			if child == id { // update existing
+				updated = append(updated, id)
+				found = true
+				break
+			}
+		}
+		if found {
+			continue
+		}
+
+		// append new
+		t.appendItem(bindFloatTreeItem(t.val, id, t.updateExternal), id, parentIDFor(id, t.ids))
+		updated = append(updated, id)
+		fire = true
+	}
+
+	for id := range t.items {
+		remove := true
+		for _, done := range updated {
+			if done == id {
+				remove = false
+				break
+			}
+		}
+
+		if remove { // remove item no longer present
+			fire = true
+			t.deleteItem(id, parentIDFor(id, t.ids))
+		}
+	}
+	if fire {
+		t.trigger()
+	}
+
+	for id, item := range t.items {
+		var err error
+		if t.updateExternal {
+			item.(*boundExternalFloatTreeItem).lock.Lock()
+			err = item.(*boundExternalFloatTreeItem).setIfChanged((*t.val)[id])
+			item.(*boundExternalFloatTreeItem).lock.Unlock()
+		} else {
+			item.(*boundFloatTreeItem).lock.Lock()
+			err = item.(*boundFloatTreeItem).doSet((*t.val)[id])
+			item.(*boundFloatTreeItem).lock.Unlock()
+		}
+		if err != nil {
+			retErr = err
+		}
+	}
+	return
+}
+
+func (t *boundFloatTree) SetValue(id string, v float64) error {
+	t.lock.Lock()
+	(*t.val)[id] = v
+	t.lock.Unlock()
+
+	item, err := t.GetItem(id)
+	if err != nil {
+		return err
+	}
+	return item.(Float).Set(v)
+}
+
+func bindFloatTreeItem(v *map[string]float64, id string, external bool) Float {
+	if external {
+		ret := &boundExternalFloatTreeItem{old: (*v)[id]}
+		ret.val = v
+		ret.id = id
+		return ret
+	}
+
+	return &boundFloatTreeItem{id: id, val: v}
+}
+
+type boundFloatTreeItem struct {
+	base
+
+	val *map[string]float64
+	id  string
+}
+
+func (t *boundFloatTreeItem) Get() (float64, error) {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	v := *t.val
+	if item, ok := v[t.id]; ok {
+		return item, nil
+	}
+
+	return 0.0, errOutOfBounds
+}
+
+func (t *boundFloatTreeItem) Set(val float64) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doSet(val)
+}
+
+func (t *boundFloatTreeItem) doSet(val float64) error {
+	(*t.val)[t.id] = val
+
+	t.trigger()
+	return nil
+}
+
+type boundExternalFloatTreeItem struct {
+	boundFloatTreeItem
+
+	old float64
+}
+
+func (t *boundExternalFloatTreeItem) setIfChanged(val float64) error {
+	if val == t.old {
+		return nil
+	}
+	(*t.val)[t.id] = val
+	t.old = val
+
+	t.trigger()
+	return nil
+}
+
+// IntTree supports binding a tree of int values.
+//
+// Since: 2.4
+type IntTree interface {
+	DataTree
+
+	Append(parent, id string, value int) error
+	Get() (map[string][]string, map[string]int, error)
+	GetValue(id string) (int, error)
+	Prepend(parent, id string, value int) error
+	Set(ids map[string][]string, values map[string]int) error
+	SetValue(id string, value int) error
+}
+
+// ExternalIntTree supports binding a tree of int values from an external variable.
+//
+// Since: 2.4
+type ExternalIntTree interface {
+	IntTree
+
+	Reload() error
+}
+
+// NewIntTree returns a bindable tree of int values.
+//
+// Since: 2.4
+func NewIntTree() IntTree {
+	t := &boundIntTree{val: &map[string]int{}}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+	return t
+}
+
+// BindIntTree returns a bound tree of int values, based on the contents of the passed values.
+// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
+// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
+//
+// Since: 2.4
+func BindIntTree(ids *map[string][]string, v *map[string]int) ExternalIntTree {
+	if v == nil {
+		return NewIntTree().(ExternalIntTree)
+	}
+
+	t := &boundIntTree{val: v, updateExternal: true}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+
+	for parent, children := range *ids {
+		for _, leaf := range children {
+			t.appendItem(bindIntTreeItem(v, leaf, t.updateExternal), leaf, parent)
+		}
+	}
+
+	return t
+}
+
+type boundIntTree struct {
+	treeBase
+
+	updateExternal bool
+	val            *map[string]int
+}
+
+func (t *boundIntTree) Append(parent, id string, val int) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append(ids, id)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundIntTree) Get() (map[string][]string, map[string]int, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.ids, *t.val, nil
+}
+
+func (t *boundIntTree) GetValue(id string) (int, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	if item, ok := (*t.val)[id]; ok {
+		return item, nil
+	}
+
+	return 0, errOutOfBounds
+}
+
+func (t *boundIntTree) Prepend(parent, id string, val int) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append([]string{id}, ids...)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundIntTree) Reload() error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doReload()
+}
+
+func (t *boundIntTree) Set(ids map[string][]string, v map[string]int) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	t.ids = ids
+	*t.val = v
+
+	return t.doReload()
+}
+
+func (t *boundIntTree) doReload() (retErr error) {
+	updated := []string{}
+	fire := false
+	for id := range *t.val {
+		found := false
+		for child := range t.items {
+			if child == id { // update existing
+				updated = append(updated, id)
+				found = true
+				break
+			}
+		}
+		if found {
+			continue
+		}
+
+		// append new
+		t.appendItem(bindIntTreeItem(t.val, id, t.updateExternal), id, parentIDFor(id, t.ids))
+		updated = append(updated, id)
+		fire = true
+	}
+
+	for id := range t.items {
+		remove := true
+		for _, done := range updated {
+			if done == id {
+				remove = false
+				break
+			}
+		}
+
+		if remove { // remove item no longer present
+			fire = true
+			t.deleteItem(id, parentIDFor(id, t.ids))
+		}
+	}
+	if fire {
+		t.trigger()
+	}
+
+	for id, item := range t.items {
+		var err error
+		if t.updateExternal {
+			item.(*boundExternalIntTreeItem).lock.Lock()
+			err = item.(*boundExternalIntTreeItem).setIfChanged((*t.val)[id])
+			item.(*boundExternalIntTreeItem).lock.Unlock()
+		} else {
+			item.(*boundIntTreeItem).lock.Lock()
+			err = item.(*boundIntTreeItem).doSet((*t.val)[id])
+			item.(*boundIntTreeItem).lock.Unlock()
+		}
+		if err != nil {
+			retErr = err
+		}
+	}
+	return
+}
+
+func (t *boundIntTree) SetValue(id string, v int) error {
+	t.lock.Lock()
+	(*t.val)[id] = v
+	t.lock.Unlock()
+
+	item, err := t.GetItem(id)
+	if err != nil {
+		return err
+	}
+	return item.(Int).Set(v)
+}
+
+func bindIntTreeItem(v *map[string]int, id string, external bool) Int {
+	if external {
+		ret := &boundExternalIntTreeItem{old: (*v)[id]}
+		ret.val = v
+		ret.id = id
+		return ret
+	}
+
+	return &boundIntTreeItem{id: id, val: v}
+}
+
+type boundIntTreeItem struct {
+	base
+
+	val *map[string]int
+	id  string
+}
+
+func (t *boundIntTreeItem) Get() (int, error) {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	v := *t.val
+	if item, ok := v[t.id]; ok {
+		return item, nil
+	}
+
+	return 0, errOutOfBounds
+}
+
+func (t *boundIntTreeItem) Set(val int) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doSet(val)
+}
+
+func (t *boundIntTreeItem) doSet(val int) error {
+	(*t.val)[t.id] = val
+
+	t.trigger()
+	return nil
+}
+
+type boundExternalIntTreeItem struct {
+	boundIntTreeItem
+
+	old int
+}
+
+func (t *boundExternalIntTreeItem) setIfChanged(val int) error {
+	if val == t.old {
+		return nil
+	}
+	(*t.val)[t.id] = val
+	t.old = val
+
+	t.trigger()
+	return nil
+}
+
+// RuneTree supports binding a tree of rune values.
+//
+// Since: 2.4
+type RuneTree interface {
+	DataTree
+
+	Append(parent, id string, value rune) error
+	Get() (map[string][]string, map[string]rune, error)
+	GetValue(id string) (rune, error)
+	Prepend(parent, id string, value rune) error
+	Set(ids map[string][]string, values map[string]rune) error
+	SetValue(id string, value rune) error
+}
+
+// ExternalRuneTree supports binding a tree of rune values from an external variable.
+//
+// Since: 2.4
+type ExternalRuneTree interface {
+	RuneTree
+
+	Reload() error
+}
+
+// NewRuneTree returns a bindable tree of rune values.
+//
+// Since: 2.4
+func NewRuneTree() RuneTree {
+	t := &boundRuneTree{val: &map[string]rune{}}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+	return t
+}
+
+// BindRuneTree returns a bound tree of rune values, based on the contents of the passed values.
+// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
+// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
+//
+// Since: 2.4
+func BindRuneTree(ids *map[string][]string, v *map[string]rune) ExternalRuneTree {
+	if v == nil {
+		return NewRuneTree().(ExternalRuneTree)
+	}
+
+	t := &boundRuneTree{val: v, updateExternal: true}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+
+	for parent, children := range *ids {
+		for _, leaf := range children {
+			t.appendItem(bindRuneTreeItem(v, leaf, t.updateExternal), leaf, parent)
+		}
+	}
+
+	return t
+}
+
+type boundRuneTree struct {
+	treeBase
+
+	updateExternal bool
+	val            *map[string]rune
+}
+
+func (t *boundRuneTree) Append(parent, id string, val rune) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append(ids, id)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundRuneTree) Get() (map[string][]string, map[string]rune, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.ids, *t.val, nil
+}
+
+func (t *boundRuneTree) GetValue(id string) (rune, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	if item, ok := (*t.val)[id]; ok {
+		return item, nil
+	}
+
+	return rune(0), errOutOfBounds
+}
+
+func (t *boundRuneTree) Prepend(parent, id string, val rune) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append([]string{id}, ids...)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundRuneTree) Reload() error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doReload()
+}
+
+func (t *boundRuneTree) Set(ids map[string][]string, v map[string]rune) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	t.ids = ids
+	*t.val = v
+
+	return t.doReload()
+}
+
+func (t *boundRuneTree) doReload() (retErr error) {
+	updated := []string{}
+	fire := false
+	for id := range *t.val {
+		found := false
+		for child := range t.items {
+			if child == id { // update existing
+				updated = append(updated, id)
+				found = true
+				break
+			}
+		}
+		if found {
+			continue
+		}
+
+		// append new
+		t.appendItem(bindRuneTreeItem(t.val, id, t.updateExternal), id, parentIDFor(id, t.ids))
+		updated = append(updated, id)
+		fire = true
+	}
+
+	for id := range t.items {
+		remove := true
+		for _, done := range updated {
+			if done == id {
+				remove = false
+				break
+			}
+		}
+
+		if remove { // remove item no longer present
+			fire = true
+			t.deleteItem(id, parentIDFor(id, t.ids))
+		}
+	}
+	if fire {
+		t.trigger()
+	}
+
+	for id, item := range t.items {
+		var err error
+		if t.updateExternal {
+			item.(*boundExternalRuneTreeItem).lock.Lock()
+			err = item.(*boundExternalRuneTreeItem).setIfChanged((*t.val)[id])
+			item.(*boundExternalRuneTreeItem).lock.Unlock()
+		} else {
+			item.(*boundRuneTreeItem).lock.Lock()
+			err = item.(*boundRuneTreeItem).doSet((*t.val)[id])
+			item.(*boundRuneTreeItem).lock.Unlock()
+		}
+		if err != nil {
+			retErr = err
+		}
+	}
+	return
+}
+
+func (t *boundRuneTree) SetValue(id string, v rune) error {
+	t.lock.Lock()
+	(*t.val)[id] = v
+	t.lock.Unlock()
+
+	item, err := t.GetItem(id)
+	if err != nil {
+		return err
+	}
+	return item.(Rune).Set(v)
+}
+
+func bindRuneTreeItem(v *map[string]rune, id string, external bool) Rune {
+	if external {
+		ret := &boundExternalRuneTreeItem{old: (*v)[id]}
+		ret.val = v
+		ret.id = id
+		return ret
+	}
+
+	return &boundRuneTreeItem{id: id, val: v}
+}
+
+type boundRuneTreeItem struct {
+	base
+
+	val *map[string]rune
+	id  string
+}
+
+func (t *boundRuneTreeItem) Get() (rune, error) {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	v := *t.val
+	if item, ok := v[t.id]; ok {
+		return item, nil
+	}
+
+	return rune(0), errOutOfBounds
+}
+
+func (t *boundRuneTreeItem) Set(val rune) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doSet(val)
+}
+
+func (t *boundRuneTreeItem) doSet(val rune) error {
+	(*t.val)[t.id] = val
+
+	t.trigger()
+	return nil
+}
+
+type boundExternalRuneTreeItem struct {
+	boundRuneTreeItem
+
+	old rune
+}
+
+func (t *boundExternalRuneTreeItem) setIfChanged(val rune) error {
+	if val == t.old {
+		return nil
+	}
+	(*t.val)[t.id] = val
+	t.old = val
+
+	t.trigger()
+	return nil
+}
+
+// StringTree supports binding a tree of string values.
+//
+// Since: 2.4
+type StringTree interface {
+	DataTree
+
+	Append(parent, id string, value string) error
+	Get() (map[string][]string, map[string]string, error)
+	GetValue(id string) (string, error)
+	Prepend(parent, id string, value string) error
+	Set(ids map[string][]string, values map[string]string) error
+	SetValue(id string, value string) error
+}
+
+// ExternalStringTree supports binding a tree of string values from an external variable.
+//
+// Since: 2.4
+type ExternalStringTree interface {
+	StringTree
+
+	Reload() error
+}
+
+// NewStringTree returns a bindable tree of string values.
+//
+// Since: 2.4
+func NewStringTree() StringTree {
+	t := &boundStringTree{val: &map[string]string{}}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+	return t
+}
+
+// BindStringTree returns a bound tree of string values, based on the contents of the passed values.
+// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
+// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
+//
+// Since: 2.4
+func BindStringTree(ids *map[string][]string, v *map[string]string) ExternalStringTree {
+	if v == nil {
+		return NewStringTree().(ExternalStringTree)
+	}
+
+	t := &boundStringTree{val: v, updateExternal: true}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+
+	for parent, children := range *ids {
+		for _, leaf := range children {
+			t.appendItem(bindStringTreeItem(v, leaf, t.updateExternal), leaf, parent)
+		}
+	}
+
+	return t
+}
+
+type boundStringTree struct {
+	treeBase
+
+	updateExternal bool
+	val            *map[string]string
+}
+
+func (t *boundStringTree) Append(parent, id string, val string) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append(ids, id)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundStringTree) Get() (map[string][]string, map[string]string, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.ids, *t.val, nil
+}
+
+func (t *boundStringTree) GetValue(id string) (string, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	if item, ok := (*t.val)[id]; ok {
+		return item, nil
+	}
+
+	return "", errOutOfBounds
+}
+
+func (t *boundStringTree) Prepend(parent, id string, val string) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append([]string{id}, ids...)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundStringTree) Reload() error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doReload()
+}
+
+func (t *boundStringTree) Set(ids map[string][]string, v map[string]string) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	t.ids = ids
+	*t.val = v
+
+	return t.doReload()
+}
+
+func (t *boundStringTree) doReload() (retErr error) {
+	updated := []string{}
+	fire := false
+	for id := range *t.val {
+		found := false
+		for child := range t.items {
+			if child == id { // update existing
+				updated = append(updated, id)
+				found = true
+				break
+			}
+		}
+		if found {
+			continue
+		}
+
+		// append new
+		t.appendItem(bindStringTreeItem(t.val, id, t.updateExternal), id, parentIDFor(id, t.ids))
+		updated = append(updated, id)
+		fire = true
+	}
+
+	for id := range t.items {
+		remove := true
+		for _, done := range updated {
+			if done == id {
+				remove = false
+				break
+			}
+		}
+
+		if remove { // remove item no longer present
+			fire = true
+			t.deleteItem(id, parentIDFor(id, t.ids))
+		}
+	}
+	if fire {
+		t.trigger()
+	}
+
+	for id, item := range t.items {
+		var err error
+		if t.updateExternal {
+			item.(*boundExternalStringTreeItem).lock.Lock()
+			err = item.(*boundExternalStringTreeItem).setIfChanged((*t.val)[id])
+			item.(*boundExternalStringTreeItem).lock.Unlock()
+		} else {
+			item.(*boundStringTreeItem).lock.Lock()
+			err = item.(*boundStringTreeItem).doSet((*t.val)[id])
+			item.(*boundStringTreeItem).lock.Unlock()
+		}
+		if err != nil {
+			retErr = err
+		}
+	}
+	return
+}
+
+func (t *boundStringTree) SetValue(id string, v string) error {
+	t.lock.Lock()
+	(*t.val)[id] = v
+	t.lock.Unlock()
+
+	item, err := t.GetItem(id)
+	if err != nil {
+		return err
+	}
+	return item.(String).Set(v)
+}
+
+func bindStringTreeItem(v *map[string]string, id string, external bool) String {
+	if external {
+		ret := &boundExternalStringTreeItem{old: (*v)[id]}
+		ret.val = v
+		ret.id = id
+		return ret
+	}
+
+	return &boundStringTreeItem{id: id, val: v}
+}
+
+type boundStringTreeItem struct {
+	base
+
+	val *map[string]string
+	id  string
+}
+
+func (t *boundStringTreeItem) Get() (string, error) {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	v := *t.val
+	if item, ok := v[t.id]; ok {
+		return item, nil
+	}
+
+	return "", errOutOfBounds
+}
+
+func (t *boundStringTreeItem) Set(val string) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doSet(val)
+}
+
+func (t *boundStringTreeItem) doSet(val string) error {
+	(*t.val)[t.id] = val
+
+	t.trigger()
+	return nil
+}
+
+type boundExternalStringTreeItem struct {
+	boundStringTreeItem
+
+	old string
+}
+
+func (t *boundExternalStringTreeItem) setIfChanged(val string) error {
+	if val == t.old {
+		return nil
+	}
+	(*t.val)[t.id] = val
+	t.old = val
+
+	t.trigger()
+	return nil
+}
+
+// URITree supports binding a tree of fyne.URI values.
+//
+// Since: 2.4
+type URITree interface {
+	DataTree
+
+	Append(parent, id string, value fyne.URI) error
+	Get() (map[string][]string, map[string]fyne.URI, error)
+	GetValue(id string) (fyne.URI, error)
+	Prepend(parent, id string, value fyne.URI) error
+	Set(ids map[string][]string, values map[string]fyne.URI) error
+	SetValue(id string, value fyne.URI) error
+}
+
+// ExternalURITree supports binding a tree of fyne.URI values from an external variable.
+//
+// Since: 2.4
+type ExternalURITree interface {
+	URITree
+
+	Reload() error
+}
+
+// NewURITree returns a bindable tree of fyne.URI values.
+//
+// Since: 2.4
+func NewURITree() URITree {
+	t := &boundURITree{val: &map[string]fyne.URI{}}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+	return t
+}
+
+// BindURITree returns a bound tree of fyne.URI values, based on the contents of the passed values.
+// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
+// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
+//
+// Since: 2.4
+func BindURITree(ids *map[string][]string, v *map[string]fyne.URI) ExternalURITree {
+	if v == nil {
+		return NewURITree().(ExternalURITree)
+	}
+
+	t := &boundURITree{val: v, updateExternal: true}
+	t.ids = make(map[string][]string)
+	t.items = make(map[string]DataItem)
+
+	for parent, children := range *ids {
+		for _, leaf := range children {
+			t.appendItem(bindURITreeItem(v, leaf, t.updateExternal), leaf, parent)
+		}
+	}
+
+	return t
+}
+
+type boundURITree struct {
+	treeBase
+
+	updateExternal bool
+	val            *map[string]fyne.URI
+}
+
+func (t *boundURITree) Append(parent, id string, val fyne.URI) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append(ids, id)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundURITree) Get() (map[string][]string, map[string]fyne.URI, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.ids, *t.val, nil
+}
+
+func (t *boundURITree) GetValue(id string) (fyne.URI, error) {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	if item, ok := (*t.val)[id]; ok {
+		return item, nil
+	}
+
+	return fyne.URI(nil), errOutOfBounds
+}
+
+func (t *boundURITree) Prepend(parent, id string, val fyne.URI) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	t.ids[parent] = append([]string{id}, ids...)
+	v := *t.val
+	v[id] = val
+
+	return t.doReload()
+}
+
+func (t *boundURITree) Reload() error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doReload()
+}
+
+func (t *boundURITree) Set(ids map[string][]string, v map[string]fyne.URI) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+	t.ids = ids
+	*t.val = v
+
+	return t.doReload()
+}
+
+func (t *boundURITree) doReload() (retErr error) {
+	updated := []string{}
+	fire := false
+	for id := range *t.val {
+		found := false
+		for child := range t.items {
+			if child == id { // update existing
+				updated = append(updated, id)
+				found = true
+				break
+			}
+		}
+		if found {
+			continue
+		}
+
+		// append new
+		t.appendItem(bindURITreeItem(t.val, id, t.updateExternal), id, parentIDFor(id, t.ids))
+		updated = append(updated, id)
+		fire = true
+	}
+
+	for id := range t.items {
+		remove := true
+		for _, done := range updated {
+			if done == id {
+				remove = false
+				break
+			}
+		}
+
+		if remove { // remove item no longer present
+			fire = true
+			t.deleteItem(id, parentIDFor(id, t.ids))
+		}
+	}
+	if fire {
+		t.trigger()
+	}
+
+	for id, item := range t.items {
+		var err error
+		if t.updateExternal {
+			item.(*boundExternalURITreeItem).lock.Lock()
+			err = item.(*boundExternalURITreeItem).setIfChanged((*t.val)[id])
+			item.(*boundExternalURITreeItem).lock.Unlock()
+		} else {
+			item.(*boundURITreeItem).lock.Lock()
+			err = item.(*boundURITreeItem).doSet((*t.val)[id])
+			item.(*boundURITreeItem).lock.Unlock()
+		}
+		if err != nil {
+			retErr = err
+		}
+	}
+	return
+}
+
+func (t *boundURITree) SetValue(id string, v fyne.URI) error {
+	t.lock.Lock()
+	(*t.val)[id] = v
+	t.lock.Unlock()
+
+	item, err := t.GetItem(id)
+	if err != nil {
+		return err
+	}
+	return item.(URI).Set(v)
+}
+
+func bindURITreeItem(v *map[string]fyne.URI, id string, external bool) URI {
+	if external {
+		ret := &boundExternalURITreeItem{old: (*v)[id]}
+		ret.val = v
+		ret.id = id
+		return ret
+	}
+
+	return &boundURITreeItem{id: id, val: v}
+}
+
+type boundURITreeItem struct {
+	base
+
+	val *map[string]fyne.URI
+	id  string
+}
+
+func (t *boundURITreeItem) Get() (fyne.URI, error) {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	v := *t.val
+	if item, ok := v[t.id]; ok {
+		return item, nil
+	}
+
+	return fyne.URI(nil), errOutOfBounds
+}
+
+func (t *boundURITreeItem) Set(val fyne.URI) error {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	return t.doSet(val)
+}
+
+func (t *boundURITreeItem) doSet(val fyne.URI) error {
+	(*t.val)[t.id] = val
+
+	t.trigger()
+	return nil
+}
+
+type boundExternalURITreeItem struct {
+	boundURITreeItem
+
+	old fyne.URI
+}
+
+func (t *boundExternalURITreeItem) setIfChanged(val fyne.URI) error {
+	if compareURI(val, t.old) {
+		return nil
+	}
+	(*t.val)[t.id] = val
+	t.old = val
+
+	t.trigger()
+	return nil
+}

+ 118 - 0
vendor/fyne.io/fyne/v2/data/binding/bool.go

@@ -0,0 +1,118 @@
+package binding
+
+type not struct {
+	Bool
+}
+
+var _ Bool = (*not)(nil)
+
+// Not returns a Bool binding that invert the value of the given data binding.
+// This is providing the logical Not boolean operation as a data binding.
+//
+// Since 2.4
+func Not(data Bool) Bool {
+	return &not{Bool: data}
+}
+
+func (n *not) Get() (bool, error) {
+	v, err := n.Bool.Get()
+	return !v, err
+}
+
+func (n *not) Set(value bool) error {
+	return n.Bool.Set(!value)
+}
+
+type and struct {
+	booleans
+}
+
+var _ Bool = (*and)(nil)
+
+// And returns a Bool binding that return true when all the passed Bool binding are
+// true and false otherwise. It does apply a logical and boolean operation on all passed
+// Bool bindings. This binding is two way. In case of a Set, it will propagate the value
+// identically to all the Bool bindings used for its construction.
+//
+// Since 2.4
+func And(data ...Bool) Bool {
+	return &and{booleans: booleans{data: data}}
+}
+
+func (a *and) Get() (bool, error) {
+	for _, d := range a.data {
+		v, err := d.Get()
+		if err != nil {
+			return false, err
+		}
+		if !v {
+			return false, nil
+		}
+	}
+	return true, nil
+}
+
+func (a *and) Set(value bool) error {
+	for _, d := range a.data {
+		err := d.Set(value)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type or struct {
+	booleans
+}
+
+var _ Bool = (*or)(nil)
+
+// Or returns a Bool binding that return true when at least one of the passed Bool binding
+// is true and false otherwise. It does apply a logical or boolean operation on all passed
+// Bool bindings. This binding is two way. In case of a Set, it will propagate the value
+// identically to all the Bool bindings used for its construction.
+//
+// Since 2.4
+func Or(data ...Bool) Bool {
+	return &or{booleans: booleans{data: data}}
+}
+
+func (o *or) Get() (bool, error) {
+	for _, d := range o.data {
+		v, err := d.Get()
+		if err != nil {
+			return false, err
+		}
+		if v {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+func (o *or) Set(value bool) error {
+	for _, d := range o.data {
+		err := d.Set(value)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type booleans struct {
+	data []Bool
+}
+
+func (g *booleans) AddListener(listener DataListener) {
+	for _, d := range g.data {
+		d.AddListener(listener)
+	}
+}
+
+func (g *booleans) RemoveListener(listener DataListener) {
+	for _, d := range g.data {
+		d.RemoveListener(listener)
+	}
+}

+ 86 - 0
vendor/fyne.io/fyne/v2/data/binding/treebinding.go

@@ -0,0 +1,86 @@
+package binding
+
+// DataTreeRootID const is the value used as ID for the root of any tree binding.
+const DataTreeRootID = ""
+
+// DataTree is the base interface for all bindable data trees.
+//
+// Since: 2.4
+type DataTree interface {
+	DataItem
+	GetItem(id string) (DataItem, error)
+	ChildIDs(string) []string
+}
+
+type treeBase struct {
+	base
+
+	ids   map[string][]string
+	items map[string]DataItem
+}
+
+// GetItem returns the DataItem at the specified id.
+func (t *treeBase) GetItem(id string) (DataItem, error) {
+	if item, ok := t.items[id]; ok {
+		return item, nil
+	}
+
+	return nil, errOutOfBounds
+}
+
+// ChildIDs returns the ordered IDs of items in this data tree that are children of the specified ID.
+func (t *treeBase) ChildIDs(id string) []string {
+	if ids, ok := t.ids[id]; ok {
+		return ids
+	}
+
+	return []string{}
+}
+
+func (t *treeBase) appendItem(i DataItem, id, parent string) {
+	t.items[id] = i
+	ids, ok := t.ids[parent]
+	if !ok {
+		ids = make([]string, 0)
+	}
+
+	for _, in := range ids {
+		if in == id {
+			return
+		}
+	}
+	t.ids[parent] = append(ids, id)
+}
+
+func (t *treeBase) deleteItem(id, parent string) {
+	delete(t.items, id)
+
+	ids, ok := t.ids[parent]
+	if !ok {
+		return
+	}
+
+	off := -1
+	for i, id2 := range ids {
+		if id2 == id {
+			off = i
+			break
+		}
+	}
+	if off == -1 {
+		return
+	}
+	t.ids[parent] = append(ids[:off], ids[off+1:]...)
+}
+
+func parentIDFor(id string, ids map[string][]string) string {
+	for parent, list := range ids {
+		for _, child := range list {
+			if child == id {
+				return parent
+			}
+		}
+	}
+
+	return ""
+}

+ 5 - 0
vendor/fyne.io/fyne/v2/driver/desktop/driver.go

@@ -7,4 +7,9 @@ import "fyne.io/fyne/v2"
 type Driver interface {
 type Driver interface {
 	// Create a new borderless window that is centered on screen
 	// Create a new borderless window that is centered on screen
 	CreateSplashWindow() fyne.Window
 	CreateSplashWindow() fyne.Window
+
+	// Gets the set of key modifiers that are currently active
+	//
+	// Since: 2.4
+	CurrentKeyModifiers() fyne.KeyModifier
 }
 }

+ 10 - 0
vendor/fyne.io/fyne/v2/driver/mobile/driver.go

@@ -0,0 +1,10 @@
+// Package mobile provides desktop specific mobile functionality.
+package mobile
+
+// Driver represents the extended capabilities of a mobile driver
+//
+// Since: 2.4
+type Driver interface {
+	// GoBack asks the OS to go to the previous app / activity, where supported
+	GoBack()
+}

+ 10 - 0
vendor/fyne.io/fyne/v2/driver/mobile/key.go

@@ -0,0 +1,10 @@
+package mobile
+
+import (
+	"fyne.io/fyne/v2"
+)
+
+const (
+	// KeyBack represents the back button which may be hardware or software
+	KeyBack fyne.KeyName = "Back"
+)

+ 18 - 0
vendor/fyne.io/fyne/v2/geometry.go

@@ -42,9 +42,17 @@ func NewPos(x float32, y float32) Position {
 	return Position{x, y}
 	return Position{x, y}
 }
 }
 
 
+// NewSquareOffsetPos returns a newly allocated Position with the same x and y position.
+//
+// Since: 2.4
+func NewSquareOffsetPos(length float32) Position {
+	return Position{length, length}
+}
+
 // Add returns a new Position that is the result of offsetting the current
 // Add returns a new Position that is the result of offsetting the current
 // position by p2 X and Y.
 // position by p2 X and Y.
 func (p Position) Add(v Vector2) Position {
 func (p Position) Add(v Vector2) Position {
+	// NOTE: Do not simplify to `return p.AddXY(v.Components())`, it prevents inlining.
 	x, y := v.Components()
 	x, y := v.Components()
 	return Position{p.X + x, p.Y + y}
 	return Position{p.X + x, p.Y + y}
 }
 }
@@ -67,6 +75,7 @@ func (p Position) IsZero() bool {
 // Subtract returns a new Position that is the result of offsetting the current
 // Subtract returns a new Position that is the result of offsetting the current
 // position by p2 -X and -Y.
 // position by p2 -X and -Y.
 func (p Position) Subtract(v Vector2) Position {
 func (p Position) Subtract(v Vector2) Position {
+	// NOTE: Do not simplify to `return p.SubtractXY(v.Components())`, it prevents inlining.
 	x, y := v.Components()
 	x, y := v.Components()
 	return Position{p.X - x, p.Y - y}
 	return Position{p.X - x, p.Y - y}
 }
 }
@@ -87,9 +96,17 @@ func NewSize(w float32, h float32) Size {
 	return Size{w, h}
 	return Size{w, h}
 }
 }
 
 
+// NewSquareSize returns a newly allocated Size with the same width and height.
+//
+// Since: 2.4
+func NewSquareSize(side float32) Size {
+	return Size{side, side}
+}
+
 // Add returns a new Size that is the result of increasing the current size by
 // Add returns a new Size that is the result of increasing the current size by
 // s2 Width and Height.
 // s2 Width and Height.
 func (s Size) Add(v Vector2) Size {
 func (s Size) Add(v Vector2) Size {
+	// NOTE: Do not simplify to `return s.AddXY(v.Components())`, it prevents inlining.
 	w, h := v.Components()
 	w, h := v.Components()
 	return Size{s.Width + w, s.Height + h}
 	return Size{s.Width + w, s.Height + h}
 }
 }
@@ -132,6 +149,7 @@ func (s Size) Components() (float32, float32) {
 // Subtract returns a new Size that is the result of decreasing the current size
 // Subtract returns a new Size that is the result of decreasing the current size
 // by s2 Width and Height.
 // by s2 Width and Height.
 func (s Size) Subtract(v Vector2) Size {
 func (s Size) Subtract(v Vector2) Size {
+	// NOTE: Do not simplify to `return s.SubtractXY(v.Components())`, it prevents inlining.
 	w, h := v.Components()
 	w, h := v.Components()
 	return Size{s.Width - w, s.Height - h}
 	return Size{s.Width - w, s.Height - h}
 }
 }

+ 2 - 2
vendor/fyne.io/fyne/v2/internal/app/focus_manager.go

@@ -39,7 +39,7 @@ func (f *FocusManager) Focus(obj fyne.Focusable) bool {
 				}
 				}
 				return false
 				return false
 			},
 			},
-			func(object, _ fyne.CanvasObject) {
+			func(object fyne.CanvasObject, pos fyne.Position, _ fyne.CanvasObject) {
 				if hiddenAncestor == object {
 				if hiddenAncestor == object {
 					hiddenAncestor = nil
 					hiddenAncestor = nil
 				}
 				}
@@ -158,5 +158,5 @@ func (f *FocusManager) previousInChain(current fyne.Focusable) fyne.Focusable {
 type walkerFunc func(
 type walkerFunc func(
 	fyne.CanvasObject,
 	fyne.CanvasObject,
 	func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
 	func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
-	func(fyne.CanvasObject, fyne.CanvasObject),
+	func(fyne.CanvasObject, fyne.Position, fyne.CanvasObject),
 ) bool
 ) bool

+ 13 - 1
vendor/fyne.io/fyne/v2/internal/app/lifecycle.go

@@ -16,6 +16,14 @@ type Lifecycle struct {
 	onBackground atomic.Value // func()
 	onBackground atomic.Value // func()
 	onStarted    atomic.Value // func()
 	onStarted    atomic.Value // func()
 	onStopped    atomic.Value // func()
 	onStopped    atomic.Value // func()
+
+	onStoppedHookExecuted func()
+}
+
+// SetOnStoppedHookExecuted is an internal function that lets Fyne schedule a clean-up after
+// the user-provided stopped hook. It should only be called once during an application start-up.
+func (l *Lifecycle) SetOnStoppedHookExecuted(f func()) {
+	l.onStoppedHookExecuted = f
 }
 }
 
 
 // SetOnEnteredForeground hooks into the the app becoming foreground.
 // SetOnEnteredForeground hooks into the the app becoming foreground.
@@ -64,10 +72,14 @@ func (l *Lifecycle) TriggerStarted() {
 	}
 	}
 }
 }
 
 
-// TriggerStopped will call the stopped hook, if one is registered.
+// TriggerStopped will call the stopped hook, if one is registered,
+// and an internal stopped hook after that.
 func (l *Lifecycle) TriggerStopped() {
 func (l *Lifecycle) TriggerStopped() {
 	f := l.onStopped.Load()
 	f := l.onStopped.Load()
 	if ff, ok := f.(func()); ok && ff != nil {
 	if ff, ok := f.(func()); ok && ff != nil {
 		ff()
 		ff()
 	}
 	}
+	if l.onStoppedHookExecuted != nil {
+		l.onStoppedHookExecuted()
+	}
 }
 }

+ 1 - 0
vendor/fyne.io/fyne/v2/internal/cache/base.go

@@ -45,6 +45,7 @@ func Clean(canvasRefreshed bool) {
 		return
 		return
 	}
 	}
 	destroyExpiredSvgs(now)
 	destroyExpiredSvgs(now)
+	destroyExpiredFontMetrics(now)
 	if canvasRefreshed {
 	if canvasRefreshed {
 		// Destroy renderers on canvas refresh to avoid flickering screen.
 		// Destroy renderers on canvas refresh to avoid flickering screen.
 		destroyExpiredRenderers(now)
 		destroyExpiredRenderers(now)

+ 8 - 2
vendor/fyne.io/fyne/v2/internal/cache/canvases.go

@@ -22,12 +22,18 @@ func GetCanvasForObject(obj fyne.CanvasObject) fyne.Canvas {
 }
 }
 
 
 // SetCanvasForObject sets the canvas for the specified object.
 // SetCanvasForObject sets the canvas for the specified object.
-func SetCanvasForObject(obj fyne.CanvasObject, canvas fyne.Canvas) {
-	cinfo := &canvasInfo{canvas: canvas}
+// The passed function will be called if the item was not previously attached to this canvas
+func SetCanvasForObject(obj fyne.CanvasObject, c fyne.Canvas, setup func()) {
+	cinfo := &canvasInfo{canvas: c}
 	cinfo.setAlive()
 	cinfo.setAlive()
 	canvasesLock.Lock()
 	canvasesLock.Lock()
+	old, found := canvases[obj]
 	canvases[obj] = cinfo
 	canvases[obj] = cinfo
 	canvasesLock.Unlock()
 	canvasesLock.Unlock()
+
+	if (!found || old.canvas != c) && setup != nil {
+		setup()
+	}
 }
 }
 
 
 type canvasInfo struct {
 type canvasInfo struct {

+ 3 - 1
vendor/fyne.io/fyne/v2/internal/cache/svg.go

@@ -35,7 +35,9 @@ func SetSvg(name string, pix *image.NRGBA, w int, h int) {
 }
 }
 
 
 type svgInfo struct {
 type svgInfo struct {
-	expiringCacheNoLock
+	// An svgInfo can be accessed from different goroutines, e.g., systray.
+	// Use expiringCache instead of expiringCacheNoLock.
+	expiringCache
 	pix  *image.NRGBA
 	pix  *image.NRGBA
 	w, h int
 	w, h int
 }
 }

+ 24 - 1
vendor/fyne.io/fyne/v2/internal/cache/text.go

@@ -2,6 +2,7 @@ package cache
 
 
 import (
 import (
 	"sync"
 	"sync"
+	"time"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 )
 )
@@ -12,6 +13,7 @@ var (
 )
 )
 
 
 type fontMetric struct {
 type fontMetric struct {
+	expiringCache
 	size     fyne.Size
 	size     fyne.Size
 	baseLine float32
 	baseLine float32
 }
 }
@@ -31,13 +33,34 @@ func GetFontMetrics(text string, fontSize float32, style fyne.TextStyle) (size f
 	if !ok {
 	if !ok {
 		return fyne.Size{Width: 0, Height: 0}, 0
 		return fyne.Size{Width: 0, Height: 0}, 0
 	}
 	}
+	ret.setAlive()
 	return ret.size, ret.baseLine
 	return ret.size, ret.baseLine
 }
 }
 
 
 // SetFontMetrics stores a calculated font size and baseline for parameters that were missing from the cache.
 // SetFontMetrics stores a calculated font size and baseline for parameters that were missing from the cache.
 func SetFontMetrics(text string, fontSize float32, style fyne.TextStyle, size fyne.Size, base float32) {
 func SetFontMetrics(text string, fontSize float32, style fyne.TextStyle, size fyne.Size, base float32) {
 	ent := fontSizeEntry{text, fontSize, style}
 	ent := fontSizeEntry{text, fontSize, style}
+	metric := fontMetric{size: size, baseLine: base}
+	metric.setAlive()
 	fontSizeLock.Lock()
 	fontSizeLock.Lock()
-	fontSizeCache[ent] = fontMetric{size: size, baseLine: base}
+	fontSizeCache[ent] = metric
+	fontSizeLock.Unlock()
+}
+
+// destroyExpiredFontMetrics destroys expired fontSizeCache entries
+func destroyExpiredFontMetrics(now time.Time) {
+	expiredObjs := make([]fontSizeEntry, 0, 50)
+	fontSizeLock.RLock()
+	for k, v := range fontSizeCache {
+		if v.isExpired(now) {
+			expiredObjs = append(expiredObjs, k)
+		}
+	}
+	fontSizeLock.RUnlock()
+
+	fontSizeLock.Lock()
+	for _, k := range expiredObjs {
+		delete(fontSizeCache, k)
+	}
 	fontSizeLock.Unlock()
 	fontSizeLock.Unlock()
 }
 }

+ 4 - 2
vendor/fyne.io/fyne/v2/internal/clip.go

@@ -15,8 +15,10 @@ func (c *ClipStack) Pop() *ClipItem {
 		return nil
 		return nil
 	}
 	}
 
 
-	ret := c.clips[len(c.clips)-1]
-	c.clips = c.clips[:len(c.clips)-1]
+	top := len(c.clips) - 1
+	ret := c.clips[top]
+	c.clips[top] = nil // release memory reference
+	c.clips = c.clips[:top]
 	return ret
 	return ret
 }
 }
 
 

+ 100 - 25
vendor/fyne.io/fyne/v2/internal/driver/common/canvas.go

@@ -1,10 +1,13 @@
 package common
 package common
 
 
 import (
 import (
+	"image/color"
+	"reflect"
 	"sync"
 	"sync"
 	"sync/atomic"
 	"sync/atomic"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
+	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal/app"
 	"fyne.io/fyne/v2/internal/app"
 	"fyne.io/fyne/v2/internal/async"
 	"fyne.io/fyne/v2/internal/async"
@@ -56,6 +59,27 @@ func (c *Canvas) AddShortcut(shortcut fyne.Shortcut, handler func(shortcut fyne.
 	c.shortcut.AddShortcut(shortcut, handler)
 	c.shortcut.AddShortcut(shortcut, handler)
 }
 }
 
 
+func (c *Canvas) DrawDebugOverlay(obj fyne.CanvasObject, pos fyne.Position, size fyne.Size) {
+	switch obj.(type) {
+	case fyne.Widget:
+		r := canvas.NewRectangle(color.Transparent)
+		r.StrokeColor = color.NRGBA{R: 0xcc, G: 0x33, B: 0x33, A: 0xff}
+		r.StrokeWidth = 1
+		r.Resize(obj.Size())
+		c.Painter().Paint(r, pos, size)
+
+		t := canvas.NewText(reflect.ValueOf(obj).Elem().Type().Name(), r.StrokeColor)
+		t.TextSize = 10
+		c.Painter().Paint(t, pos.AddXY(2, 2), size)
+	case *fyne.Container:
+		r := canvas.NewRectangle(color.Transparent)
+		r.StrokeColor = color.NRGBA{R: 0x33, G: 0x33, B: 0xcc, A: 0xff}
+		r.StrokeWidth = 1
+		r.Resize(obj.Size())
+		c.Painter().Paint(r, pos, size)
+	}
+}
+
 // EnsureMinSize ensure canvas min size.
 // EnsureMinSize ensure canvas min size.
 //
 //
 // This function uses lock.
 // This function uses lock.
@@ -63,53 +87,66 @@ func (c *Canvas) EnsureMinSize() bool {
 	if c.impl.Content() == nil {
 	if c.impl.Content() == nil {
 		return false
 		return false
 	}
 	}
-	var lastParent fyne.CanvasObject
-
 	windowNeedsMinSizeUpdate := false
 	windowNeedsMinSizeUpdate := false
 	csize := c.impl.Size()
 	csize := c.impl.Size()
 	min := c.impl.MinSize()
 	min := c.impl.MinSize()
 
 
-	ensureMinSize := func(node *RenderCacheNode) {
+	c.RLock()
+	defer c.RUnlock()
+
+	var parentNeedingUpdate *RenderCacheNode
+
+	ensureMinSize := func(node *RenderCacheNode, pos fyne.Position) {
 		obj := node.obj
 		obj := node.obj
-		cache.SetCanvasForObject(obj, c.impl)
+		cache.SetCanvasForObject(obj, c.impl, func() {
+			if img, ok := obj.(*canvas.Image); ok {
+				c.RUnlock()
+				img.Refresh() // this may now have a different texScale
+				c.RLock()
+			}
+		})
+
+		if parentNeedingUpdate == node {
+			c.updateLayout(obj)
+			parentNeedingUpdate = nil
+		}
 
 
+		c.RUnlock()
 		if !obj.Visible() {
 		if !obj.Visible() {
+			c.RLock()
 			return
 			return
 		}
 		}
 		minSize := obj.MinSize()
 		minSize := obj.MinSize()
+		c.RLock()
+
 		minSizeChanged := node.minSize != minSize
 		minSizeChanged := node.minSize != minSize
 		if minSizeChanged {
 		if minSizeChanged {
-			objToLayout := obj
 			node.minSize = minSize
 			node.minSize = minSize
 			if node.parent != nil {
 			if node.parent != nil {
-				objToLayout = node.parent.obj
+				parentNeedingUpdate = node.parent
 			} else {
 			} else {
 				windowNeedsMinSizeUpdate = true
 				windowNeedsMinSizeUpdate = true
+				c.RUnlock()
 				size := obj.Size()
 				size := obj.Size()
+				c.RLock()
 				expectedSize := minSize.Max(size)
 				expectedSize := minSize.Max(size)
 				if expectedSize != size && size != csize {
 				if expectedSize != size && size != csize {
-					objToLayout = nil
+					c.RUnlock()
 					obj.Resize(expectedSize)
 					obj.Resize(expectedSize)
+					c.RLock()
+				} else {
+					c.updateLayout(obj)
 				}
 				}
 			}
 			}
-
-			if objToLayout != lastParent {
-				updateLayout(lastParent)
-				lastParent = objToLayout
-			}
 		}
 		}
 	}
 	}
 	c.WalkTrees(nil, ensureMinSize)
 	c.WalkTrees(nil, ensureMinSize)
 
 
 	shouldResize := windowNeedsMinSizeUpdate && (csize.Width < min.Width || csize.Height < min.Height)
 	shouldResize := windowNeedsMinSizeUpdate && (csize.Width < min.Width || csize.Height < min.Height)
 	if shouldResize {
 	if shouldResize {
+		c.RUnlock()
 		c.impl.Resize(csize.Max(min))
 		c.impl.Resize(csize.Max(min))
-	}
-
-	if lastParent != nil {
 		c.RLock()
 		c.RLock()
-		updateLayout(lastParent)
-		c.RUnlock()
 	}
 	}
 	return windowNeedsMinSizeUpdate
 	return windowNeedsMinSizeUpdate
 }
 }
@@ -196,12 +233,25 @@ func (c *Canvas) FocusPrevious() {
 func (c *Canvas) FreeDirtyTextures() (freed uint64) {
 func (c *Canvas) FreeDirtyTextures() (freed uint64) {
 	freeObject := func(object fyne.CanvasObject) {
 	freeObject := func(object fyne.CanvasObject) {
 		freeWalked := func(obj fyne.CanvasObject, _ fyne.Position, _ fyne.Position, _ fyne.Size) bool {
 		freeWalked := func(obj fyne.CanvasObject, _ fyne.Position, _ fyne.Position, _ fyne.Size) bool {
+			// No image refresh while recursing to avoid double texture upload.
+			if _, ok := obj.(*canvas.Image); ok {
+				return false
+			}
 			if c.painter != nil {
 			if c.painter != nil {
 				c.painter.Free(obj)
 				c.painter.Free(obj)
 			}
 			}
 			return false
 			return false
 		}
 		}
-		driver.WalkCompleteObjectTree(object, freeWalked, nil)
+
+		// Image.Refresh will trigger a refresh specific to the object, while recursing on parent widget would just lead to
+		// a double texture upload.
+		if img, ok := object.(*canvas.Image); ok {
+			if c.painter != nil {
+				c.painter.Free(img)
+			}
+		} else {
+			driver.WalkCompleteObjectTree(object, freeWalked, nil)
+		}
 	}
 	}
 
 
 	// Within a frame, refresh tasks are requested from the Refresh method,
 	// Within a frame, refresh tasks are requested from the Refresh method,
@@ -288,6 +338,23 @@ func (c *Canvas) Painter() gl.Painter {
 
 
 // Refresh refreshes a canvas object.
 // Refresh refreshes a canvas object.
 func (c *Canvas) Refresh(obj fyne.CanvasObject) {
 func (c *Canvas) Refresh(obj fyne.CanvasObject) {
+	walkNeeded := false
+	switch obj.(type) {
+	case *fyne.Container:
+		walkNeeded = true
+	case fyne.Widget:
+		walkNeeded = true
+	}
+
+	if walkNeeded {
+		driver.WalkCompleteObjectTree(obj, func(co fyne.CanvasObject, p1, p2 fyne.Position, s fyne.Size) bool {
+			if i, ok := co.(*canvas.Image); ok {
+				i.Refresh()
+			}
+			return false
+		}, nil)
+	}
+
 	c.refreshQueue.In(obj)
 	c.refreshQueue.In(obj)
 	c.SetDirty()
 	c.SetDirty()
 }
 }
@@ -366,7 +433,7 @@ func (c *Canvas) Unfocus() {
 // WalkTrees walks over the trees.
 // WalkTrees walks over the trees.
 func (c *Canvas) WalkTrees(
 func (c *Canvas) WalkTrees(
 	beforeChildren func(*RenderCacheNode, fyne.Position),
 	beforeChildren func(*RenderCacheNode, fyne.Position),
-	afterChildren func(*RenderCacheNode),
+	afterChildren func(*RenderCacheNode, fyne.Position),
 ) {
 ) {
 	c.walkTree(c.contentTree, beforeChildren, afterChildren)
 	c.walkTree(c.contentTree, beforeChildren, afterChildren)
 	if c.mWindowHeadTree != nil && c.mWindowHeadTree.root.obj != nil {
 	if c.mWindowHeadTree != nil && c.mWindowHeadTree.root.obj != nil {
@@ -408,7 +475,7 @@ func (c *Canvas) isMenuActive() bool {
 func (c *Canvas) walkTree(
 func (c *Canvas) walkTree(
 	tree *renderCacheTree,
 	tree *renderCacheTree,
 	beforeChildren func(*RenderCacheNode, fyne.Position),
 	beforeChildren func(*RenderCacheNode, fyne.Position),
-	afterChildren func(*RenderCacheNode),
+	afterChildren func(*RenderCacheNode, fyne.Position),
 ) {
 ) {
 	tree.Lock()
 	tree.Lock()
 	defer tree.Unlock()
 	defer tree.Unlock()
@@ -442,7 +509,7 @@ func (c *Canvas) walkTree(
 		node = parent.firstChild
 		node = parent.firstChild
 		return false
 		return false
 	}
 	}
-	ac := func(obj fyne.CanvasObject, _ fyne.CanvasObject) {
+	ac := func(obj fyne.CanvasObject, pos fyne.Position, _ fyne.CanvasObject) {
 		node = parent
 		node = parent
 		parent = node.parent
 		parent = node.parent
 		if prev != nil && prev.parent != parent {
 		if prev != nil && prev.parent != parent {
@@ -450,7 +517,7 @@ func (c *Canvas) walkTree(
 		}
 		}
 
 
 		if afterChildren != nil {
 		if afterChildren != nil {
-			afterChildren(node)
+			afterChildren(node, pos)
 		}
 		}
 
 
 		prev = node
 		prev = node
@@ -517,6 +584,7 @@ func (o *overlayStack) add(overlay fyne.CanvasObject) {
 func (o *overlayStack) remove(overlay fyne.CanvasObject) {
 func (o *overlayStack) remove(overlay fyne.CanvasObject) {
 	o.OverlayStack.Remove(overlay)
 	o.OverlayStack.Remove(overlay)
 	overlayCount := len(o.List())
 	overlayCount := len(o.List())
+	o.renderCaches[overlayCount] = nil // release memory reference to removed element
 	o.renderCaches = o.renderCaches[:overlayCount]
 	o.renderCaches = o.renderCaches[:overlayCount]
 }
 }
 
 
@@ -525,13 +593,20 @@ type renderCacheTree struct {
 	root *RenderCacheNode
 	root *RenderCacheNode
 }
 }
 
 
-func updateLayout(objToLayout fyne.CanvasObject) {
+func (c *Canvas) updateLayout(objToLayout fyne.CanvasObject) {
 	switch cont := objToLayout.(type) {
 	switch cont := objToLayout.(type) {
 	case *fyne.Container:
 	case *fyne.Container:
 		if cont.Layout != nil {
 		if cont.Layout != nil {
-			cont.Layout.Layout(cont.Objects, cont.Size())
+			layout := cont.Layout
+			objects := cont.Objects
+			c.RUnlock()
+			layout.Layout(objects, cont.Size())
+			c.RLock()
 		}
 		}
 	case fyne.Widget:
 	case fyne.Widget:
-		cache.Renderer(cont).Layout(cont.Size())
+		renderer := cache.Renderer(cont)
+		c.RUnlock()
+		renderer.Layout(cont.Size())
+		c.RLock()
 	}
 	}
 }
 }

+ 3 - 3
vendor/fyne.io/fyne/v2/internal/driver/common/window.go

@@ -38,14 +38,14 @@ func (w *Window) RunEventQueue() {
 
 
 // WaitForEvents wait for all the events.
 // WaitForEvents wait for all the events.
 func (w *Window) WaitForEvents() {
 func (w *Window) WaitForEvents() {
-	done := donePool.Get().(chan struct{})
-	defer donePool.Put(done)
+	done := DonePool.Get().(chan struct{})
+	defer DonePool.Put(done)
 
 
 	w.eventQueue.In() <- func() { done <- struct{}{} }
 	w.eventQueue.In() <- func() { done <- struct{}{} }
 	<-done
 	<-done
 }
 }
 
 
-var donePool = sync.Pool{
+var DonePool = sync.Pool{
 	New: func() interface{} {
 	New: func() interface{} {
 		return make(chan struct{})
 		return make(chan struct{})
 	},
 	},

+ 31 - 25
vendor/fyne.io/fyne/v2/internal/driver/glfw/canvas.go

@@ -20,10 +20,10 @@ var _ fyne.Canvas = (*glCanvas)(nil)
 type glCanvas struct {
 type glCanvas struct {
 	common.Canvas
 	common.Canvas
 
 
-	content fyne.CanvasObject
-	menu    fyne.CanvasObject
-	padded  bool
-	size    fyne.Size
+	content       fyne.CanvasObject
+	menu          fyne.CanvasObject
+	padded, debug bool
+	size          fyne.Size
 
 
 	onTypedRune func(rune)
 	onTypedRune func(rune)
 	onTypedKey  func(*fyne.KeyEvent)
 	onTypedKey  func(*fyne.KeyEvent)
@@ -125,14 +125,20 @@ func (c *glCanvas) Resize(size fyne.Size) {
 	}
 	}
 
 
 	c.RLock()
 	c.RLock()
-	c.content.Resize(c.contentSize(nearestSize))
-	c.content.Move(c.contentPos())
+	content := c.content
+	contentSize := c.contentSize(nearestSize)
+	contentPos := c.contentPos()
+	menu := c.menu
+	menuHeight := c.menuHeight()
+	c.RUnlock()
 
 
-	if c.menu != nil {
-		c.menu.Refresh()
-		c.menu.Resize(fyne.NewSize(nearestSize.Width, c.menu.MinSize().Height))
+	content.Resize(contentSize)
+	content.Move(contentPos)
+
+	if menu != nil {
+		menu.Refresh()
+		menu.Resize(fyne.NewSize(nearestSize.Width, menuHeight))
 	}
 	}
-	c.RUnlock()
 }
 }
 
 
 func (c *glCanvas) Scale() float32 {
 func (c *glCanvas) Scale() float32 {
@@ -191,7 +197,7 @@ func (c *glCanvas) reloadScale() {
 	}
 	}
 
 
 	c.Lock()
 	c.Lock()
-	c.scale = c.context.(*window).calculatedScale()
+	c.scale = w.calculatedScale()
 	c.Unlock()
 	c.Unlock()
 	c.SetDirty()
 	c.SetDirty()
 
 
@@ -231,8 +237,7 @@ func (c *glCanvas) buildMenu(w *window, m *fyne.MainMenu) {
 func (c *glCanvas) canvasSize(contentSize fyne.Size) fyne.Size {
 func (c *glCanvas) canvasSize(contentSize fyne.Size) fyne.Size {
 	canvasSize := contentSize.Add(fyne.NewSize(0, c.menuHeight()))
 	canvasSize := contentSize.Add(fyne.NewSize(0, c.menuHeight()))
 	if c.Padded() {
 	if c.Padded() {
-		pad := theme.Padding() * 2
-		canvasSize = canvasSize.Add(fyne.NewSize(pad, pad))
+		return canvasSize.Add(fyne.NewSquareSize(theme.Padding() * 2))
 	}
 	}
 	return canvasSize
 	return canvasSize
 }
 }
@@ -240,7 +245,7 @@ func (c *glCanvas) canvasSize(contentSize fyne.Size) fyne.Size {
 func (c *glCanvas) contentPos() fyne.Position {
 func (c *glCanvas) contentPos() fyne.Position {
 	contentPos := fyne.NewPos(0, c.menuHeight())
 	contentPos := fyne.NewPos(0, c.menuHeight())
 	if c.Padded() {
 	if c.Padded() {
-		contentPos = contentPos.Add(fyne.NewPos(theme.Padding(), theme.Padding()))
+		return contentPos.Add(fyne.NewSquareOffsetPos(theme.Padding()))
 	}
 	}
 	return contentPos
 	return contentPos
 }
 }
@@ -248,20 +253,17 @@ func (c *glCanvas) contentPos() fyne.Position {
 func (c *glCanvas) contentSize(canvasSize fyne.Size) fyne.Size {
 func (c *glCanvas) contentSize(canvasSize fyne.Size) fyne.Size {
 	contentSize := fyne.NewSize(canvasSize.Width, canvasSize.Height-c.menuHeight())
 	contentSize := fyne.NewSize(canvasSize.Width, canvasSize.Height-c.menuHeight())
 	if c.Padded() {
 	if c.Padded() {
-		pad := theme.Padding() * 2
-		contentSize = contentSize.Subtract(fyne.NewSize(pad, pad))
+		return contentSize.Subtract(fyne.NewSquareSize(theme.Padding() * 2))
 	}
 	}
 	return contentSize
 	return contentSize
 }
 }
 
 
 func (c *glCanvas) menuHeight() float32 {
 func (c *glCanvas) menuHeight() float32 {
-	switch c.menu {
-	case nil:
-		// no menu or native menu -> does not consume space on the canvas
-		return 0
-	default:
-		return c.menu.MinSize().Height
+	if c.menu == nil {
+		return 0 // no menu or native menu -> does not consume space on the canvas
 	}
 	}
+
+	return c.menu.MinSize().Height
 }
 }
 
 
 func (c *glCanvas) overlayChanged() {
 func (c *glCanvas) overlayChanged() {
@@ -286,7 +288,7 @@ func (c *glCanvas) paint(size fyne.Size) {
 		}
 		}
 		c.Painter().Paint(obj, pos, size)
 		c.Painter().Paint(obj, pos, size)
 	}
 	}
-	afterPaint := func(node *common.RenderCacheNode) {
+	afterPaint := func(node *common.RenderCacheNode, pos fyne.Position) {
 		if _, ok := node.Obj().(fyne.Scrollable); ok {
 		if _, ok := node.Obj().(fyne.Scrollable); ok {
 			clips.Pop()
 			clips.Pop()
 			if top := clips.Top(); top != nil {
 			if top := clips.Top(); top != nil {
@@ -295,6 +297,10 @@ func (c *glCanvas) paint(size fyne.Size) {
 				c.Painter().StopClipping()
 				c.Painter().StopClipping()
 			}
 			}
 		}
 		}
+
+		if c.debug {
+			c.DrawDebugOverlay(node.Obj(), pos, size)
+		}
 	}
 	}
 	c.WalkTrees(paint, afterPaint)
 	c.WalkTrees(paint, afterPaint)
 }
 }
@@ -330,9 +336,9 @@ func (c *glCanvas) applyThemeOutOfTreeObjects() {
 }
 }
 
 
 func newCanvas() *glCanvas {
 func newCanvas() *glCanvas {
-	c := &glCanvas{scale: 1.0, texScale: 1.0}
+	c := &glCanvas{scale: 1.0, texScale: 1.0, padded: true}
 	c.Initialize(c, c.overlayChanged)
 	c.Initialize(c, c.overlayChanged)
 	c.setContent(&canvas.Rectangle{FillColor: theme.BackgroundColor()})
 	c.setContent(&canvas.Rectangle{FillColor: theme.BackgroundColor()})
-	c.padded = true
+	c.debug = fyne.CurrentApp().Settings().BuildType() == fyne.BuildDebug
 	return c
 	return c
 }
 }

+ 15 - 27
vendor/fyne.io/fyne/v2/internal/driver/glfw/driver.go

@@ -6,10 +6,8 @@ import (
 	"bytes"
 	"bytes"
 	"image"
 	"image"
 	"os"
 	"os"
-	"os/signal"
 	"runtime"
 	"runtime"
 	"sync"
 	"sync"
-	"syscall"
 
 
 	"github.com/fyne-io/image/ico"
 	"github.com/fyne-io/image/ico"
 
 
@@ -29,15 +27,13 @@ import (
 // influence of a garbage collector.
 // influence of a garbage collector.
 var mainGoroutineID uint64
 var mainGoroutineID uint64
 
 
-var (
-	curWindow *window
-	isWayland = false
-)
+var curWindow *window
 
 
 // Declare conformity with Driver
 // Declare conformity with Driver
 var _ fyne.Driver = (*gLDriver)(nil)
 var _ fyne.Driver = (*gLDriver)(nil)
 
 
-var drawOnMainThread bool // A workaround on Apple M1, just use 1 thread until fixed upstream
+// A workaround on Apple M1/M2, just use 1 thread until fixed upstream.
+const drawOnMainThread bool = runtime.GOOS == "darwin" && runtime.GOARCH == "arm64"
 
 
 type gLDriver struct {
 type gLDriver struct {
 	windowLock sync.RWMutex
 	windowLock sync.RWMutex
@@ -48,6 +44,8 @@ type gLDriver struct {
 
 
 	animation *animation.Runner
 	animation *animation.Runner
 
 
+	currentKeyModifiers fyne.KeyModifier // desktop driver only
+
 	trayStart, trayStop func()     // shut down the system tray, if used
 	trayStart, trayStop func()     // shut down the system tray, if used
 	systrayMenu         *fyne.Menu // cache the menu set so we know when to refresh
 	systrayMenu         *fyne.Menu // cache the menu set so we know when to refresh
 }
 }
@@ -150,12 +148,13 @@ func (d *gLDriver) windowList() []fyne.Window {
 func (d *gLDriver) initFailed(msg string, err error) {
 func (d *gLDriver) initFailed(msg string, err error) {
 	logError(msg, err)
 	logError(msg, err)
 
 
-	run.Lock()
-	if !run.flag {
-		run.Unlock()
+	run.L.Lock()
+	running := !run.flag
+	run.L.Unlock()
+
+	if running {
 		d.Quit()
 		d.Quit()
 	} else {
 	} else {
-		run.Unlock()
 		os.Exit(1)
 		os.Exit(1)
 	}
 	}
 }
 }
@@ -165,28 +164,17 @@ func (d *gLDriver) Run() {
 		panic("Run() or ShowAndRun() must be called from main goroutine")
 		panic("Run() or ShowAndRun() must be called from main goroutine")
 	}
 	}
 
 
-	go catchTerm(d)
+	go d.catchTerm()
 	d.runGL()
 	d.runGL()
 }
 }
 
 
 // NewGLDriver sets up a new Driver instance implemented using the GLFW Go library and OpenGL bindings.
 // NewGLDriver sets up a new Driver instance implemented using the GLFW Go library and OpenGL bindings.
 func NewGLDriver() fyne.Driver {
 func NewGLDriver() fyne.Driver {
-	d := new(gLDriver)
-	d.done = make(chan interface{})
-	d.drawDone = make(chan interface{})
-	d.animation = &animation.Runner{}
-
 	repository.Register("file", intRepo.NewFileRepository())
 	repository.Register("file", intRepo.NewFileRepository())
 
 
-	return d
-}
-
-func catchTerm(d *gLDriver) {
-	terminateSignals := make(chan os.Signal, 1)
-	signal.Notify(terminateSignals, syscall.SIGINT, syscall.SIGTERM)
-
-	for range terminateSignals {
-		d.Quit()
-		break
+	return &gLDriver{
+		done:      make(chan interface{}),
+		drawDone:  make(chan interface{}),
+		animation: &animation.Runner{},
 	}
 	}
 }
 }

+ 21 - 11
vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_desktop.go

@@ -6,11 +6,15 @@ package glfw
 import (
 import (
 	"bytes"
 	"bytes"
 	"image/png"
 	"image/png"
+	"os"
+	"os/signal"
 	"runtime"
 	"runtime"
 	"sync"
 	"sync"
+	"syscall"
 
 
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/internal/painter"
 	"fyne.io/fyne/v2/internal/painter"
+	"fyne.io/fyne/v2/internal/svg"
 	"fyne.io/systray"
 	"fyne.io/systray"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
@@ -39,7 +43,7 @@ func (d *gLDriver) SetSystemTrayMenu(m *fyne.Menu) {
 			} else if fyne.CurrentApp().Icon() != nil {
 			} else if fyne.CurrentApp().Icon() != nil {
 				d.SetSystemTrayIcon(fyne.CurrentApp().Icon())
 				d.SetSystemTrayIcon(fyne.CurrentApp().Icon())
 			} else {
 			} else {
-				d.SetSystemTrayIcon(theme.FyneLogo())
+				d.SetSystemTrayIcon(theme.BrokenImageIcon())
 			}
 			}
 
 
 			// it must be refreshed after init, so an earlier call would have been ineffective
 			// it must be refreshed after init, so an earlier call would have been ineffective
@@ -51,12 +55,8 @@ func (d *gLDriver) SetSystemTrayMenu(m *fyne.Menu) {
 		// the only way we know the app was asked to quit is if this window is asked to close...
 		// the only way we know the app was asked to quit is if this window is asked to close...
 		w := d.CreateWindow("SystrayMonitor")
 		w := d.CreateWindow("SystrayMonitor")
 		w.(*window).create()
 		w.(*window).create()
-		w.SetCloseIntercept(func() {
-			d.Quit()
-		})
-		w.SetOnClosed(func() {
-			systray.Quit()
-		})
+		w.SetCloseIntercept(d.Quit)
+		w.SetOnClosed(systray.Quit)
 	})
 	})
 
 
 	d.refreshSystray(m)
 	d.refreshSystray(m)
@@ -91,7 +91,7 @@ func itemForMenuItem(i *fyne.MenuItem, parent *systray.MenuItem) *systray.MenuIt
 	}
 	}
 	if i.Icon != nil {
 	if i.Icon != nil {
 		data := i.Icon.Content()
 		data := i.Icon.Content()
-		if painter.IsResourceSVG(i.Icon) {
+		if svg.IsResourceSVG(i.Icon) {
 			b := &bytes.Buffer{}
 			b := &bytes.Buffer{}
 			res := i.Icon
 			res := i.Icon
 			if runtime.GOOS == "windows" && isDark() { // windows menus don't match dark mode so invert icons
 			if runtime.GOOS == "windows" && isDark() { // windows menus don't match dark mode so invert icons
@@ -161,6 +161,18 @@ func (d *gLDriver) SystemTrayMenu() *fyne.Menu {
 	return d.systrayMenu
 	return d.systrayMenu
 }
 }
 
 
+func (d *gLDriver) CurrentKeyModifiers() fyne.KeyModifier {
+	return d.currentKeyModifiers
+}
+
+func (d *gLDriver) catchTerm() {
+	terminateSignal := make(chan os.Signal, 1)
+	signal.Notify(terminateSignal, syscall.SIGINT, syscall.SIGTERM)
+
+	<-terminateSignal
+	d.Quit()
+}
+
 func addMissingQuitForMenu(menu *fyne.Menu, d *gLDriver) {
 func addMissingQuitForMenu(menu *fyne.Menu, d *gLDriver) {
 	var lastItem *fyne.MenuItem
 	var lastItem *fyne.MenuItem
 	if len(menu.Items) > 0 {
 	if len(menu.Items) > 0 {
@@ -176,9 +188,7 @@ func addMissingQuitForMenu(menu *fyne.Menu, d *gLDriver) {
 	}
 	}
 	for _, item := range menu.Items {
 	for _, item := range menu.Items {
 		if item.IsQuit && item.Action == nil {
 		if item.IsQuit && item.Action == nil {
-			item.Action = func() {
-				d.Quit()
-			}
+			item.Action = d.Quit
 		}
 		}
 	}
 	}
 }
 }

+ 6 - 0
vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_notwayland.go

@@ -0,0 +1,6 @@
+//go:build !wayland
+// +build !wayland
+
+package glfw
+
+const isWayland = false

+ 1 - 3
vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_wayland.go

@@ -3,6 +3,4 @@
 
 
 package glfw
 package glfw
 
 
-func init() {
-	isWayland = true
-}
+const isWayland = true

+ 3 - 0
vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_mobile.go → vendor/fyne.io/fyne/v2/internal/driver/glfw/driver_web.go

@@ -8,3 +8,6 @@ import "fyne.io/fyne/v2"
 func (d *gLDriver) SetSystemTrayMenu(m *fyne.Menu) {
 func (d *gLDriver) SetSystemTrayMenu(m *fyne.Menu) {
 	// no-op for mobile apps using this driver
 	// no-op for mobile apps using this driver
 }
 }
+
+func (d *gLDriver) catchTerm() {
+}

+ 20 - 30
vendor/fyne.io/fyne/v2/internal/driver/glfw/loop.go

@@ -6,10 +6,11 @@ import (
 	"time"
 	"time"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
-	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal/app"
 	"fyne.io/fyne/v2/internal/app"
 	"fyne.io/fyne/v2/internal/cache"
 	"fyne.io/fyne/v2/internal/cache"
+	"fyne.io/fyne/v2/internal/driver/common"
 	"fyne.io/fyne/v2/internal/painter"
 	"fyne.io/fyne/v2/internal/painter"
+	"fyne.io/fyne/v2/internal/scale"
 )
 )
 
 
 type funcData struct {
 type funcData struct {
@@ -24,47 +25,35 @@ type drawData struct {
 }
 }
 
 
 type runFlag struct {
 type runFlag struct {
-	sync.Mutex
+	sync.Cond
 	flag bool
 	flag bool
-	cond *sync.Cond
 }
 }
 
 
 // channel for queuing functions on the main thread
 // channel for queuing functions on the main thread
 var funcQueue = make(chan funcData)
 var funcQueue = make(chan funcData)
 var drawFuncQueue = make(chan drawData)
 var drawFuncQueue = make(chan drawData)
-var run *runFlag
+var run = &runFlag{Cond: sync.Cond{L: &sync.Mutex{}}}
 var initOnce = &sync.Once{}
 var initOnce = &sync.Once{}
-var donePool = &sync.Pool{New: func() interface{} {
-	return make(chan struct{})
-}}
-
-func newRun() *runFlag {
-	r := runFlag{}
-	r.cond = sync.NewCond(&r)
-	return &r
-}
 
 
 // Arrange that main.main runs on main thread.
 // Arrange that main.main runs on main thread.
 func init() {
 func init() {
 	runtime.LockOSThread()
 	runtime.LockOSThread()
 	mainGoroutineID = goroutineID()
 	mainGoroutineID = goroutineID()
-
-	run = newRun()
 }
 }
 
 
 // force a function f to run on the main thread
 // force a function f to run on the main thread
 func runOnMain(f func()) {
 func runOnMain(f func()) {
 	// If we are on main just execute - otherwise add it to the main queue and wait.
 	// If we are on main just execute - otherwise add it to the main queue and wait.
 	// The "running" variable is normally false when we are on the main thread.
 	// The "running" variable is normally false when we are on the main thread.
-	run.Lock()
-	if !run.flag {
+	run.L.Lock()
+	running := !run.flag
+	run.L.Unlock()
+
+	if running {
 		f()
 		f()
-		run.Unlock()
 	} else {
 	} else {
-		run.Unlock()
-
-		done := donePool.Get().(chan struct{})
-		defer donePool.Put(done)
+		done := common.DonePool.Get().(chan struct{})
+		defer common.DonePool.Put(done)
 
 
 		funcQueue <- funcData{f: f, done: done}
 		funcQueue <- funcData{f: f, done: done}
 
 
@@ -78,8 +67,8 @@ func runOnDraw(w *window, f func()) {
 		runOnMain(func() { w.RunWithContext(f) })
 		runOnMain(func() { w.RunWithContext(f) })
 		return
 		return
 	}
 	}
-	done := donePool.Get().(chan struct{})
-	defer donePool.Put(done)
+	done := common.DonePool.Get().(chan struct{})
+	defer common.DonePool.Put(done)
 
 
 	drawFuncQueue <- drawData{f: f, win: w, done: done}
 	drawFuncQueue <- drawData{f: f, win: w, done: done}
 	<-done
 	<-done
@@ -111,10 +100,11 @@ func (d *gLDriver) drawSingleFrame() {
 
 
 func (d *gLDriver) runGL() {
 func (d *gLDriver) runGL() {
 	eventTick := time.NewTicker(time.Second / 60)
 	eventTick := time.NewTicker(time.Second / 60)
-	run.Lock()
+
+	run.L.Lock()
 	run.flag = true
 	run.flag = true
-	run.Unlock()
-	run.cond.Broadcast()
+	run.L.Unlock()
+	run.Broadcast()
 
 
 	d.initGLFW()
 	d.initGLFW()
 	if d.trayStart != nil {
 	if d.trayStart != nil {
@@ -196,7 +186,7 @@ func (d *gLDriver) runGL() {
 func (d *gLDriver) repaintWindow(w *window) {
 func (d *gLDriver) repaintWindow(w *window) {
 	canvas := w.canvas
 	canvas := w.canvas
 	w.RunWithContext(func() {
 	w.RunWithContext(func() {
-		if w.canvas.EnsureMinSize() {
+		if canvas.EnsureMinSize() {
 			w.viewLock.Lock()
 			w.viewLock.Lock()
 			w.shouldExpand = true
 			w.shouldExpand = true
 			w.viewLock.Unlock()
 			w.viewLock.Unlock()
@@ -267,8 +257,8 @@ func updateGLContext(w *window) {
 	size := canvas.Size()
 	size := canvas.Size()
 
 
 	// w.width and w.height are not correct if we are maximised, so figure from canvas
 	// w.width and w.height are not correct if we are maximised, so figure from canvas
-	winWidth := float32(internal.ScaleInt(canvas, size.Width)) * canvas.texScale
-	winHeight := float32(internal.ScaleInt(canvas, size.Height)) * canvas.texScale
+	winWidth := float32(scale.ToScreenCoordinate(canvas, size.Width)) * canvas.texScale
+	winHeight := float32(scale.ToScreenCoordinate(canvas, size.Height)) * canvas.texScale
 
 
 	canvas.Painter().SetFrameBufferScale(canvas.texScale)
 	canvas.Painter().SetFrameBufferScale(canvas.texScale)
 	w.canvas.Painter().SetOutputSize(int(winWidth), int(winHeight))
 	w.canvas.Painter().SetOutputSize(int(winWidth), int(winHeight))

+ 0 - 5
vendor/fyne.io/fyne/v2/internal/driver/glfw/loop_desktop.go

@@ -5,7 +5,6 @@ package glfw
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"runtime"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 
 
@@ -14,10 +13,6 @@ import (
 
 
 func (d *gLDriver) initGLFW() {
 func (d *gLDriver) initGLFW() {
 	initOnce.Do(func() {
 	initOnce.Do(func() {
-		if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
-			drawOnMainThread = true
-		}
-
 		err := glfw.Init()
 		err := glfw.Init()
 		if err != nil {
 		if err != nil {
 			fyne.LogError("failed to initialise GLFW", err)
 			fyne.LogError("failed to initialise GLFW", err)

+ 5 - 6
vendor/fyne.io/fyne/v2/internal/driver/glfw/menu_bar.go

@@ -68,9 +68,7 @@ func (b *MenuBar) Toggle() {
 }
 }
 
 
 func (b *MenuBar) activateChild(item *menuBarItem) {
 func (b *MenuBar) activateChild(item *menuBarItem) {
-	if !b.active {
-		b.active = true
-	}
+	b.active = true
 	if item.Child() != nil {
 	if item.Child() != nil {
 		item.Child().DeactivateChild()
 		item.Child().DeactivateChild()
 	}
 	}
@@ -142,13 +140,14 @@ func (r *menuBarRenderer) Layout(size fyne.Size) {
 	} else {
 	} else {
 		r.underlay.Resize(fyne.NewSize(0, 0))
 		r.underlay.Resize(fyne.NewSize(0, 0))
 	}
 	}
-	r.cont.Resize(fyne.NewSize(size.Width-2*theme.InnerPadding(), size.Height))
-	r.cont.Move(fyne.NewPos(theme.InnerPadding(), 0))
+	innerPadding := theme.InnerPadding()
+	r.cont.Resize(fyne.NewSize(size.Width-2*innerPadding, size.Height))
+	r.cont.Move(fyne.NewPos(innerPadding, 0))
 	if item := r.b.activeItem; item != nil {
 	if item := r.b.activeItem; item != nil {
 		if item.Child().Size().IsZero() {
 		if item.Child().Size().IsZero() {
 			item.Child().Resize(item.Child().MinSize())
 			item.Child().Resize(item.Child().MinSize())
 		}
 		}
-		item.Child().Move(fyne.NewPos(item.Position().X+theme.InnerPadding(), item.Size().Height))
+		item.Child().Move(fyne.NewPos(item.Position().X+innerPadding, item.Size().Height))
 	}
 	}
 	r.background.Resize(size)
 	r.background.Resize(size)
 }
 }

+ 2 - 0
vendor/fyne.io/fyne/v2/internal/driver/glfw/menu_bar_item.go

@@ -39,6 +39,7 @@ func (i *menuBarItem) Child() *publicWidget.Menu {
 // Implements: fyne.Widget
 // Implements: fyne.Widget
 func (i *menuBarItem) CreateRenderer() fyne.WidgetRenderer {
 func (i *menuBarItem) CreateRenderer() fyne.WidgetRenderer {
 	background := canvas.NewRectangle(theme.HoverColor())
 	background := canvas.NewRectangle(theme.HoverColor())
+	background.CornerRadius = theme.SelectionRadiusSize()
 	background.Hide()
 	background.Hide()
 	text := canvas.NewText(i.Menu.Label, theme.ForegroundColor())
 	text := canvas.NewText(i.Menu.Label, theme.ForegroundColor())
 	objects := []fyne.CanvasObject{background, text}
 	objects := []fyne.CanvasObject{background, text}
@@ -160,6 +161,7 @@ func (r *menuBarItemRenderer) MinSize() fyne.Size {
 }
 }
 
 
 func (r *menuBarItemRenderer) Refresh() {
 func (r *menuBarItemRenderer) Refresh() {
+	r.background.CornerRadius = theme.SelectionRadiusSize()
 	if r.i.active && r.i.Parent.active {
 	if r.i.active && r.i.Parent.active {
 		r.background.FillColor = theme.FocusColor()
 		r.background.FillColor = theme.FocusColor()
 		r.background.Show()
 		r.background.Show()

+ 1 - 1
vendor/fyne.io/fyne/v2/internal/driver/glfw/menu_darwin.go

@@ -172,7 +172,7 @@ func insertNativeMenuItem(nsMenu unsafe.Pointer, item *fyne.MenuItem, nextItemID
 	var imgData unsafe.Pointer
 	var imgData unsafe.Pointer
 	var imgDataLength uint
 	var imgDataLength uint
 	if item.Icon != nil {
 	if item.Icon != nil {
-		if painter.IsResourceSVG(item.Icon) {
+		if svg.IsResourceSVG(item.Icon) {
 			rsc := item.Icon
 			rsc := item.Icon
 			if _, isThemed := rsc.(*theme.ThemedResource); isThemed {
 			if _, isThemed := rsc.(*theme.ThemedResource); isThemed {
 				var r, g, b, a C.int
 				var r, g, b, a C.int

+ 10 - 0
vendor/fyne.io/fyne/v2/internal/driver/glfw/scroll_speed_darwin.go

@@ -0,0 +1,10 @@
+//go:build darwin
+// +build darwin
+
+package glfw
+
+const (
+	scrollAccelerateRate   = float64(5)
+	scrollAccelerateCutoff = float64(5)
+	scrollSpeed            = float32(10)
+)

+ 10 - 0
vendor/fyne.io/fyne/v2/internal/driver/glfw/scroll_speed_default.go

@@ -0,0 +1,10 @@
+//go:build !darwin
+// +build !darwin
+
+package glfw
+
+const (
+	scrollAccelerateRate   = float64(125)
+	scrollAccelerateCutoff = float64(10)
+	scrollSpeed            = float32(25)
+)

+ 45 - 32
vendor/fyne.io/fyne/v2/internal/driver/glfw/window.go

@@ -9,20 +9,17 @@ import (
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/driver/desktop"
 	"fyne.io/fyne/v2/driver/desktop"
-	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal/app"
 	"fyne.io/fyne/v2/internal/app"
 	"fyne.io/fyne/v2/internal/cache"
 	"fyne.io/fyne/v2/internal/cache"
 	"fyne.io/fyne/v2/internal/driver"
 	"fyne.io/fyne/v2/internal/driver"
 	"fyne.io/fyne/v2/internal/driver/common"
 	"fyne.io/fyne/v2/internal/driver/common"
+	"fyne.io/fyne/v2/internal/scale"
 )
 )
 
 
 const (
 const (
-	scrollAccelerateRate   = float64(5)
-	scrollAccelerateCutoff = float64(5)
-	scrollSpeed            = float32(10)
-	doubleClickDelay       = 300 // ms (maximum interval between clicks for double click detection)
-	dragMoveThreshold      = 2   // how far can we move before it is a drag
-	windowIconSize         = 256
+	doubleClickDelay  = 300 // ms (maximum interval between clicks for double click detection)
+	dragMoveThreshold = 2   // how far can we move before it is a drag
+	windowIconSize    = 256
 )
 )
 
 
 func (w *window) Title() string {
 func (w *window) Title() string {
@@ -49,7 +46,7 @@ func (w *window) minSizeOnScreen() (int, int) {
 
 
 // screenSize computes the actual output size of the given content size in screen pixels
 // screenSize computes the actual output size of the given content size in screen pixels
 func (w *window) screenSize(canvasSize fyne.Size) (int, int) {
 func (w *window) screenSize(canvasSize fyne.Size) (int, int) {
-	return internal.ScaleInt(w.canvas, canvasSize.Width), internal.ScaleInt(w.canvas, canvasSize.Height)
+	return scale.ToScreenCoordinate(w.canvas, canvasSize.Width), scale.ToScreenCoordinate(w.canvas, canvasSize.Height)
 }
 }
 
 
 func (w *window) Resize(size fyne.Size) {
 func (w *window) Resize(size fyne.Size) {
@@ -58,7 +55,7 @@ func (w *window) Resize(size fyne.Size) {
 	w.runOnMainWhenCreated(func() {
 	w.runOnMainWhenCreated(func() {
 		w.viewLock.Lock()
 		w.viewLock.Lock()
 
 
-		width, height := internal.ScaleInt(w.canvas, bigEnough.Width), internal.ScaleInt(w.canvas, bigEnough.Height)
+		width, height := scale.ToScreenCoordinate(w.canvas, bigEnough.Width), scale.ToScreenCoordinate(w.canvas, bigEnough.Height)
 		if w.fixedSize || !w.visible { // fixed size ignores future `resized` and if not visible we may not get the event
 		if w.fixedSize || !w.visible { // fixed size ignores future `resized` and if not visible we may not get the event
 			w.shouldWidth, w.shouldHeight = width, height
 			w.shouldWidth, w.shouldHeight = width, height
 			w.width, w.height = width, height
 			w.width, w.height = width, height
@@ -137,11 +134,11 @@ func (w *window) doShow() {
 		return
 		return
 	}
 	}
 
 
-	run.Lock()
+	run.L.Lock()
 	for !run.flag {
 	for !run.flag {
-		run.cond.Wait()
+		run.Wait()
 	}
 	}
-	run.Unlock()
+	run.L.Unlock()
 
 
 	w.createLock.Do(w.create)
 	w.createLock.Do(w.create)
 	if w.view() == nil {
 	if w.view() == nil {
@@ -173,6 +170,10 @@ func (w *window) doShow() {
 	// show top canvas element
 	// show top canvas element
 	if w.canvas.Content() != nil {
 	if w.canvas.Content() != nil {
 		w.canvas.Content().Show()
 		w.canvas.Content().Show()
+
+		runOnDraw(w, func() {
+			w.driver.repaintWindow(w)
+		})
 	}
 	}
 }
 }
 
 
@@ -218,7 +219,7 @@ func (w *window) Close() {
 		})
 		})
 	})
 	})
 
 
-	w.canvas.WalkTrees(nil, func(node *common.RenderCacheNode) {
+	w.canvas.WalkTrees(nil, func(node *common.RenderCacheNode, _ fyne.Position) {
 		if wid, ok := node.Obj().(fyne.Widget); ok {
 		if wid, ok := node.Obj().(fyne.Widget); ok {
 			cache.DestroyRenderer(wid)
 			cache.DestroyRenderer(wid)
 		}
 		}
@@ -306,8 +307,8 @@ func (w *window) processMoved(x, y int) {
 func (w *window) processResized(width, height int) {
 func (w *window) processResized(width, height int) {
 	canvasSize := w.computeCanvasSize(width, height)
 	canvasSize := w.computeCanvasSize(width, height)
 	if !w.fullScreen {
 	if !w.fullScreen {
-		w.width = internal.ScaleInt(w.canvas, canvasSize.Width)
-		w.height = internal.ScaleInt(w.canvas, canvasSize.Height)
+		w.width = scale.ToScreenCoordinate(w.canvas, canvasSize.Width)
+		w.height = scale.ToScreenCoordinate(w.canvas, canvasSize.Height)
 	}
 	}
 
 
 	if !w.visible { // don't redraw if hidden
 	if !w.visible { // don't redraw if hidden
@@ -353,7 +354,7 @@ func (w *window) findObjectAtPositionMatching(canvas *glCanvas, mouse fyne.Posit
 func (w *window) processMouseMoved(xpos float64, ypos float64) {
 func (w *window) processMouseMoved(xpos float64, ypos float64) {
 	w.mouseLock.Lock()
 	w.mouseLock.Lock()
 	previousPos := w.mousePos
 	previousPos := w.mousePos
-	w.mousePos = fyne.NewPos(internal.UnscaleInt(w.canvas, int(xpos)), internal.UnscaleInt(w.canvas, int(ypos)))
+	w.mousePos = fyne.NewPos(scale.ToFyneCoordinate(w.canvas, int(xpos)), scale.ToFyneCoordinate(w.canvas, int(ypos)))
 	mousePos := w.mousePos
 	mousePos := w.mousePos
 	mouseButton := w.mouseButton
 	mouseButton := w.mouseButton
 	mouseDragPos := w.mouseDragPos
 	mouseDragPos := w.mouseDragPos
@@ -410,10 +411,9 @@ func (w *window) processMouseMoved(xpos float64, ypos float64) {
 	isMouseOverDragged := w.objIsDragged(mouseOver)
 	isMouseOverDragged := w.objIsDragged(mouseOver)
 	w.mouseLock.RUnlock()
 	w.mouseLock.RUnlock()
 	if obj != nil && !isObjDragged {
 	if obj != nil && !isObjDragged {
-		ev := new(desktop.MouseEvent)
+		ev := &desktop.MouseEvent{Button: mouseButton}
 		ev.AbsolutePosition = mousePos
 		ev.AbsolutePosition = mousePos
 		ev.Position = pos
 		ev.Position = pos
-		ev.Button = mouseButton
 
 
 		if hovered, ok := obj.(desktop.Hoverable); ok {
 		if hovered, ok := obj.(desktop.Hoverable); ok {
 			if hovered == mouseOver {
 			if hovered == mouseOver {
@@ -450,7 +450,7 @@ func (w *window) processMouseMoved(xpos float64, ypos float64) {
 	if mouseDragged != nil && mouseButton != desktop.MouseButtonSecondary {
 	if mouseDragged != nil && mouseButton != desktop.MouseButtonSecondary {
 		if w.mouseButton > 0 {
 		if w.mouseButton > 0 {
 			draggedObjDelta := mouseDraggedObjStart.Subtract(mouseDragged.(fyne.CanvasObject).Position())
 			draggedObjDelta := mouseDraggedObjStart.Subtract(mouseDragged.(fyne.CanvasObject).Position())
-			ev := new(fyne.DragEvent)
+			ev := &fyne.DragEvent{}
 			ev.AbsolutePosition = mousePos
 			ev.AbsolutePosition = mousePos
 			ev.Position = mousePos.Subtract(mouseDraggedOffset).Add(draggedObjDelta)
 			ev.Position = mousePos.Subtract(mouseDraggedOffset).Add(draggedObjDelta)
 			ev.Dragged = fyne.NewDelta(mousePos.X-mouseDragPos.X, mousePos.Y-mouseDragPos.Y)
 			ev.Dragged = fyne.NewDelta(mousePos.X-mouseDragPos.X, mousePos.Y-mouseDragPos.Y)
@@ -507,7 +507,7 @@ func (w *window) processMouseClicked(button desktop.MouseButton, action action,
 	if mousePos.IsZero() { // window may not be focused (darwin mostly) and so position callbacks not happening
 	if mousePos.IsZero() { // window may not be focused (darwin mostly) and so position callbacks not happening
 		xpos, ypos := w.view().GetCursorPos()
 		xpos, ypos := w.view().GetCursorPos()
 		w.mouseLock.Lock()
 		w.mouseLock.Lock()
-		w.mousePos = fyne.NewPos(internal.UnscaleInt(w.canvas, int(xpos)), internal.UnscaleInt(w.canvas, int(ypos)))
+		w.mousePos = fyne.NewPos(scale.ToFyneCoordinate(w.canvas, int(xpos)), scale.ToFyneCoordinate(w.canvas, int(ypos)))
 		mousePos = w.mousePos
 		mousePos = w.mousePos
 		w.mouseLock.Unlock()
 		w.mouseLock.Unlock()
 	}
 	}
@@ -524,22 +524,37 @@ func (w *window) processMouseClicked(button desktop.MouseButton, action action,
 
 
 		return false
 		return false
 	})
 	})
-	ev := new(fyne.PointEvent)
-	ev.Position = pos
-	ev.AbsolutePosition = mousePos
+	ev := &fyne.PointEvent{
+		Position:         pos,
+		AbsolutePosition: mousePos,
+	}
 
 
 	coMouse := co
 	coMouse := co
 	if wid, ok := co.(desktop.Mouseable); ok {
 	if wid, ok := co.(desktop.Mouseable); ok {
-		mev := new(desktop.MouseEvent)
+		mev := &desktop.MouseEvent{
+			Button:   button,
+			Modifier: modifiers,
+		}
 		mev.Position = ev.Position
 		mev.Position = ev.Position
 		mev.AbsolutePosition = mousePos
 		mev.AbsolutePosition = mousePos
-		mev.Button = button
-		mev.Modifier = modifiers
 		w.mouseClickedHandleMouseable(mev, action, wid)
 		w.mouseClickedHandleMouseable(mev, action, wid)
 	}
 	}
 
 
 	if wid, ok := co.(fyne.Focusable); !ok || wid != w.canvas.Focused() {
 	if wid, ok := co.(fyne.Focusable); !ok || wid != w.canvas.Focused() {
-		w.canvas.Unfocus()
+		ignore := false
+		_, _, _ = w.findObjectAtPositionMatching(w.canvas, mousePos, func(object fyne.CanvasObject) bool {
+			switch object.(type) {
+			case fyne.Focusable:
+				ignore = true
+				return true
+			}
+
+			return false
+		})
+
+		if !ignore { // if a parent item under the mouse has focus then ignore this tap unfocus
+			w.canvas.Unfocus()
+		}
 	}
 	}
 
 
 	w.mouseLock.Lock()
 	w.mouseLock.Lock()
@@ -572,7 +587,7 @@ func (w *window) processMouseClicked(button desktop.MouseButton, action action,
 	}
 	}
 
 
 	_, tap := co.(fyne.Tappable)
 	_, tap := co.(fyne.Tappable)
-	_, altTap := co.(fyne.SecondaryTappable)
+	secondary, altTap := co.(fyne.SecondaryTappable)
 	if tap || altTap {
 	if tap || altTap {
 		if action == press {
 		if action == press {
 			w.mouseLock.Lock()
 			w.mouseLock.Lock()
@@ -581,7 +596,7 @@ func (w *window) processMouseClicked(button desktop.MouseButton, action action,
 		} else if action == release {
 		} else if action == release {
 			if co == mousePressed {
 			if co == mousePressed {
 				if button == desktop.MouseButtonSecondary && altTap {
 				if button == desktop.MouseButtonSecondary && altTap {
-					w.QueueEvent(func() { co.(fyne.SecondaryTappable).TappedSecondary(ev) })
+					w.QueueEvent(func() { secondary.TappedSecondary(ev) })
 				}
 				}
 			}
 			}
 		}
 		}
@@ -910,9 +925,7 @@ func (w *window) RunWithContext(f func()) {
 }
 }
 
 
 func (w *window) RescaleContext() {
 func (w *window) RescaleContext() {
-	runOnMain(func() {
-		w.rescaleOnMain()
-	})
+	runOnMain(w.rescaleOnMain)
 }
 }
 
 
 func (w *window) Context() interface{} {
 func (w *window) Context() interface{} {

+ 51 - 5
vendor/fyne.io/fyne/v2/internal/driver/glfw/window_desktop.go

@@ -14,10 +14,12 @@ import (
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/driver/desktop"
 	"fyne.io/fyne/v2/driver/desktop"
-	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal/driver/common"
 	"fyne.io/fyne/v2/internal/driver/common"
 	"fyne.io/fyne/v2/internal/painter"
 	"fyne.io/fyne/v2/internal/painter"
 	"fyne.io/fyne/v2/internal/painter/gl"
 	"fyne.io/fyne/v2/internal/painter/gl"
+	"fyne.io/fyne/v2/internal/scale"
+	"fyne.io/fyne/v2/internal/svg"
+	"fyne.io/fyne/v2/storage"
 
 
 	"github.com/go-gl/glfw/v3.3/glfw"
 	"github.com/go-gl/glfw/v3.3/glfw"
 )
 )
@@ -140,6 +142,23 @@ func (w *window) CenterOnScreen() {
 	}
 	}
 }
 }
 
 
+func (w *window) SetOnDropped(dropped func(pos fyne.Position, items []fyne.URI)) {
+	w.runOnMainWhenCreated(func() {
+		w.viewport.SetDropCallback(func(win *glfw.Window, names []string) {
+			if dropped == nil {
+				return
+			}
+
+			uris := make([]fyne.URI, len(names))
+			for i, name := range names {
+				uris[i] = storage.NewFileURI(name)
+			}
+
+			dropped(w.mousePos, uris)
+		})
+	})
+}
+
 func (w *window) doCenterOnScreen() {
 func (w *window) doCenterOnScreen() {
 	viewWidth, viewHeight := w.screenSize(w.canvas.size)
 	viewWidth, viewHeight := w.screenSize(w.canvas.size)
 	if w.width > viewWidth { // in case our window has not called back to canvas size yet
 	if w.width > viewWidth { // in case our window has not called back to canvas size yet
@@ -189,7 +208,7 @@ func (w *window) SetIcon(icon fyne.Resource) {
 		}
 		}
 
 
 		var img image.Image
 		var img image.Image
-		if painter.IsResourceSVG(w.icon) {
+		if svg.IsResourceSVG(w.icon) {
 			img = painter.PaintImage(&canvas.Image{Resource: w.icon}, nil, windowIconSize, windowIconSize)
 			img = painter.PaintImage(&canvas.Image{Resource: w.icon}, nil, windowIconSize, windowIconSize)
 		} else {
 		} else {
 			pix, _, err := image.Decode(bytes.NewReader(w.icon.Content()))
 			pix, _, err := image.Decode(bytes.NewReader(w.icon.Content()))
@@ -600,6 +619,7 @@ func convertASCII(key glfw.Key) fyne.KeyName {
 func (w *window) keyPressed(_ *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
 func (w *window) keyPressed(_ *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
 	keyName := keyToName(key, scancode)
 	keyName := keyToName(key, scancode)
 	keyDesktopModifier := desktopModifier(mods)
 	keyDesktopModifier := desktopModifier(mods)
+	w.driver.currentKeyModifiers = desktopModifierCorrected(mods, key, action)
 	keyAction := convertAction(action)
 	keyAction := convertAction(action)
 	keyASCII := convertASCII(key)
 	keyASCII := convertASCII(key)
 
 
@@ -623,6 +643,32 @@ func desktopModifier(mods glfw.ModifierKey) fyne.KeyModifier {
 	return m
 	return m
 }
 }
 
 
+func desktopModifierCorrected(mods glfw.ModifierKey, key glfw.Key, action glfw.Action) fyne.KeyModifier {
+	// On X11, pressing/releasing modifier keys does not include newly pressed/released keys in 'mod' mask.
+	// https://github.com/glfw/glfw/issues/1630
+	if action == glfw.Press {
+		mods |= glfwKeyToModifier(key)
+	} else {
+		mods &= ^glfwKeyToModifier(key)
+	}
+	return desktopModifier(mods)
+}
+
+func glfwKeyToModifier(key glfw.Key) glfw.ModifierKey {
+	var m glfw.ModifierKey
+	switch key {
+	case glfw.KeyLeftControl, glfw.KeyRightControl:
+		m = glfw.ModControl
+	case glfw.KeyLeftAlt, glfw.KeyRightAlt:
+		m = glfw.ModAlt
+	case glfw.KeyLeftShift, glfw.KeyRightShift:
+		m = glfw.ModShift
+	case glfw.KeyLeftSuper, glfw.KeyRightSuper:
+		m = glfw.ModSuper
+	}
+	return m
+}
+
 // charInput defines the character with modifiers callback which is called when a
 // charInput defines the character with modifiers callback which is called when a
 // Unicode character is input.
 // Unicode character is input.
 //
 //
@@ -648,8 +694,8 @@ func (w *window) rescaleOnMain() {
 	if w.fullScreen {
 	if w.fullScreen {
 		w.width, w.height = w.viewport.GetSize()
 		w.width, w.height = w.viewport.GetSize()
 		scaledFull := fyne.NewSize(
 		scaledFull := fyne.NewSize(
-			internal.UnscaleInt(w.canvas, w.width),
-			internal.UnscaleInt(w.canvas, w.height))
+			scale.ToFyneCoordinate(w.canvas, w.width),
+			scale.ToFyneCoordinate(w.canvas, w.height))
 		w.canvas.Resize(scaledFull)
 		w.canvas.Resize(scaledFull)
 		return
 		return
 	}
 	}
@@ -737,7 +783,7 @@ func (w *window) create() {
 
 
 		if w.FixedSize() && (w.requestedWidth == 0 || w.requestedHeight == 0) {
 		if w.FixedSize() && (w.requestedWidth == 0 || w.requestedHeight == 0) {
 			bigEnough := w.canvas.canvasSize(w.canvas.Content().MinSize())
 			bigEnough := w.canvas.canvasSize(w.canvas.Content().MinSize())
-			w.width, w.height = internal.ScaleInt(w.canvas, bigEnough.Width), internal.ScaleInt(w.canvas, bigEnough.Height)
+			w.width, w.height = scale.ToScreenCoordinate(w.canvas, bigEnough.Width), scale.ToScreenCoordinate(w.canvas, bigEnough.Height)
 			w.shouldWidth, w.shouldHeight = w.width, w.height
 			w.shouldWidth, w.shouldHeight = w.width, w.height
 		}
 		}
 
 

+ 7 - 3
vendor/fyne.io/fyne/v2/internal/driver/glfw/window_goxjs.go

@@ -11,9 +11,9 @@ import (
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/driver/desktop"
 	"fyne.io/fyne/v2/driver/desktop"
-	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal/driver/common"
 	"fyne.io/fyne/v2/internal/driver/common"
 	"fyne.io/fyne/v2/internal/painter/gl"
 	"fyne.io/fyne/v2/internal/painter/gl"
+	"fyne.io/fyne/v2/internal/scale"
 
 
 	"github.com/fyne-io/glfw-js"
 	"github.com/fyne-io/glfw-js"
 )
 )
@@ -107,6 +107,10 @@ func (w *window) CenterOnScreen() {
 	w.centered = true
 	w.centered = true
 }
 }
 
 
+func (w *window) SetOnDropped(dropped func(pos fyne.Position, items []fyne.URI)) {
+	// FIXME: not implemented yet
+}
+
 func (w *window) doCenterOnScreen() {
 func (w *window) doCenterOnScreen() {
 	// FIXME: no meaning for defining center on screen in WebGL
 	// FIXME: no meaning for defining center on screen in WebGL
 }
 }
@@ -474,8 +478,8 @@ func (w *window) rescaleOnMain() {
 	//	if w.fullScreen {
 	//	if w.fullScreen {
 	w.width, w.height = w.viewport.GetSize()
 	w.width, w.height = w.viewport.GetSize()
 	scaledFull := fyne.NewSize(
 	scaledFull := fyne.NewSize(
-		internal.UnscaleInt(w.canvas, w.width),
-		internal.UnscaleInt(w.canvas, w.height))
+		scale.ToFyneCoordinate(w.canvas, w.width),
+		scale.ToFyneCoordinate(w.canvas, w.height))
 	w.canvas.Resize(scaledFull)
 	w.canvas.Resize(scaledFull)
 	return
 	return
 	//	}
 	//	}

+ 2 - 2
vendor/fyne.io/fyne/v2/internal/driver/glfw/window_notwindows.go

@@ -5,12 +5,12 @@ package glfw
 
 
 import (
 import (
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
-	"fyne.io/fyne/v2/internal"
+	"fyne.io/fyne/v2/internal/scale"
 )
 )
 
 
 func (w *window) setDarkMode() {
 func (w *window) setDarkMode() {
 }
 }
 
 
 func (w *window) computeCanvasSize(width, height int) fyne.Size {
 func (w *window) computeCanvasSize(width, height int) fyne.Size {
-	return fyne.NewSize(internal.UnscaleInt(w.canvas, width), internal.UnscaleInt(w.canvas, height))
+	return fyne.NewSize(scale.ToFyneCoordinate(w.canvas, width), scale.ToFyneCoordinate(w.canvas, height))
 }
 }

+ 4 - 3
vendor/fyne.io/fyne/v2/internal/driver/glfw/window_windows.go

@@ -6,7 +6,8 @@ import (
 	"unsafe"
 	"unsafe"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
-	"fyne.io/fyne/v2/internal"
+	"fyne.io/fyne/v2/internal/scale"
+
 	"golang.org/x/sys/windows/registry"
 	"golang.org/x/sys/windows/registry"
 )
 )
 
 
@@ -45,7 +46,7 @@ func isDark() bool {
 
 
 func (w *window) computeCanvasSize(width, height int) fyne.Size {
 func (w *window) computeCanvasSize(width, height int) fyne.Size {
 	if w.fixedSize {
 	if w.fixedSize {
-		return fyne.NewSize(internal.UnscaleInt(w.canvas, w.width), internal.UnscaleInt(w.canvas, w.height))
+		return fyne.NewSize(scale.ToFyneCoordinate(w.canvas, w.width), scale.ToFyneCoordinate(w.canvas, w.height))
 	}
 	}
-	return fyne.NewSize(internal.UnscaleInt(w.canvas, width), internal.UnscaleInt(w.canvas, height))
+	return fyne.NewSize(scale.ToFyneCoordinate(w.canvas, width), scale.ToFyneCoordinate(w.canvas, height))
 }
 }

+ 16 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/GoNativeActivity.java

@@ -43,6 +43,7 @@ public class GoNativeActivity extends NativeActivity {
     private native void insetsChanged(int top, int bottom, int left, int right);
     private native void insetsChanged(int top, int bottom, int left, int right);
     private native void keyboardTyped(String str);
     private native void keyboardTyped(String str);
     private native void keyboardDelete();
     private native void keyboardDelete();
+    private native void backPressed();
     private native void setDarkMode(boolean dark);
     private native void setDarkMode(boolean dark);
 
 
 	private EditText mTextEdit;
 	private EditText mTextEdit;
@@ -313,6 +314,21 @@ public class GoNativeActivity extends NativeActivity {
         filePickerReturned(uri.toString());
         filePickerReturned(uri.toString());
     }
     }
 
 
+    @Override
+    public void onBackPressed() {
+        // skip the default behaviour - we can call finishActivity if we want to go back
+        backPressed();
+    }
+
+    public void finishActivity() {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                GoNativeActivity.super.onBackPressed();
+            }
+        });
+    }
+
     @Override
     @Override
     public void onConfigurationChanged(Configuration config) {
     public void onConfigurationChanged(Configuration config) {
         super.onConfigurationChanged(config);
         super.onConfigurationChanged(config);

+ 22 - 1
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/android.c

@@ -53,6 +53,7 @@ static jmethodID show_keyboard_method;
 static jmethodID hide_keyboard_method;
 static jmethodID hide_keyboard_method;
 static jmethodID show_file_open_method;
 static jmethodID show_file_open_method;
 static jmethodID show_file_save_method;
 static jmethodID show_file_save_method;
+static jmethodID finish_method;
 
 
 jint JNI_OnLoad(JavaVM* vm, void* reserved) {
 jint JNI_OnLoad(JavaVM* vm, void* reserved) {
 	JNIEnv* env;
 	JNIEnv* env;
@@ -65,6 +66,14 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
 
 
 static int main_running = 0;
 static int main_running = 0;
 
 
+// ensure we refresh context on resume in case something has changed...
+void processOnResume(ANativeActivity *activity) {
+	JNIEnv* env = activity->env;
+	setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz));
+
+    onResume(activity);
+}
+
 // Entry point from our subclassed NativeActivity.
 // Entry point from our subclassed NativeActivity.
 //
 //
 // By here, the Go runtime has been initialized (as we are running in
 // By here, the Go runtime has been initialized (as we are running in
@@ -85,6 +94,7 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_
 		hide_keyboard_method = find_static_method(env, current_class, "hideKeyboard", "()V");
 		hide_keyboard_method = find_static_method(env, current_class, "hideKeyboard", "()V");
 		show_file_open_method = find_static_method(env, current_class, "showFileOpen", "(Ljava/lang/String;)V");
 		show_file_open_method = find_static_method(env, current_class, "showFileOpen", "(Ljava/lang/String;)V");
 		show_file_save_method = find_static_method(env, current_class, "showFileSave", "(Ljava/lang/String;Ljava/lang/String;)V");
 		show_file_save_method = find_static_method(env, current_class, "showFileSave", "(Ljava/lang/String;Ljava/lang/String;)V");
+		finish_method = find_method(env, current_class, "finishActivity", "()V");
 
 
 		setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz));
 		setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz));
 
 
@@ -117,7 +127,7 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_
 	// Note that onNativeWindowResized is not called on resize. Avoid it.
 	// Note that onNativeWindowResized is not called on resize. Avoid it.
 	// https://code.google.com/p/android/issues/detail?id=180645
 	// https://code.google.com/p/android/issues/detail?id=180645
 	activity->callbacks->onStart = onStart;
 	activity->callbacks->onStart = onStart;
-	activity->callbacks->onResume = onResume;
+	activity->callbacks->onResume = processOnResume;
 	activity->callbacks->onSaveInstanceState = onSaveInstanceState;
 	activity->callbacks->onSaveInstanceState = onSaveInstanceState;
 	activity->callbacks->onPause = onPause;
 	activity->callbacks->onPause = onPause;
 	activity->callbacks->onStop = onStop;
 	activity->callbacks->onStop = onStop;
@@ -204,6 +214,13 @@ char* destroyEGLSurface() {
 	return NULL;
 	return NULL;
 }
 }
 
 
+void finish(JNIEnv* env, jobject ctx) {
+    (*env)->CallVoidMethod(
+        env,
+        ctx,
+        finish_method);
+}
+
 int32_t getKeyRune(JNIEnv* env, AInputEvent* e) {
 int32_t getKeyRune(JNIEnv* env, AInputEvent* e) {
 	return (int32_t)(*env)->CallStaticIntMethod(
 	return (int32_t)(*env)->CallStaticIntMethod(
 		env,
 		env,
@@ -272,6 +289,10 @@ void Java_org_golang_app_GoNativeActivity_keyboardDelete(JNIEnv *env, jclass cla
     keyboardDelete();
     keyboardDelete();
 }
 }
 
 
+void Java_org_golang_app_GoNativeActivity_backPressed(JNIEnv *env, jclass clazz) {
+    onBackPressed();
+}
+
 void Java_org_golang_app_GoNativeActivity_setDarkMode(JNIEnv *env, jclass clazz, jboolean dark) {
 void Java_org_golang_app_GoNativeActivity_setDarkMode(JNIEnv *env, jclass clazz, jboolean dark) {
     setDarkMode((bool)dark);
     setDarkMode((bool)dark);
 }
 }

+ 26 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/android.go

@@ -48,6 +48,7 @@ void showKeyboard(JNIEnv* env, int keyboardType);
 void hideKeyboard(JNIEnv* env);
 void hideKeyboard(JNIEnv* env);
 void showFileOpen(JNIEnv* env, char* mimes);
 void showFileOpen(JNIEnv* env, char* mimes);
 void showFileSave(JNIEnv* env, char* mimes, char* filename);
 void showFileSave(JNIEnv* env, char* mimes, char* filename);
+void finish(JNIEnv* env, jobject ctx);
 
 
 void Java_org_golang_app_GoNativeActivity_filePickerReturned(JNIEnv *env, jclass clazz, jstring str);
 void Java_org_golang_app_GoNativeActivity_filePickerReturned(JNIEnv *env, jclass clazz, jstring str);
 */
 */
@@ -77,6 +78,18 @@ var mimeMap = map[string]string{
 	".txt": "text/plain",
 	".txt": "text/plain",
 }
 }
 
 
+// GoBack asks the OS to go to the previous app / activity
+func GoBack() {
+	err := RunOnJVM(func(_, jniEnv, ctx uintptr) error {
+		env := (*C.JNIEnv)(unsafe.Pointer(jniEnv))
+		C.finish(env, C.jobject(ctx))
+		return nil
+	})
+	if err != nil {
+		log.Fatalf("app: %v", err)
+	}
+}
+
 // RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv.
 // RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv.
 //
 //
 // RunOnJVM blocks until the call to fn is complete. Any Java
 // RunOnJVM blocks until the call to fn is complete. Any Java
@@ -139,6 +152,19 @@ func onPause(activity *C.ANativeActivity) {
 func onStop(activity *C.ANativeActivity) {
 func onStop(activity *C.ANativeActivity) {
 }
 }
 
 
+//export onBackPressed
+func onBackPressed() {
+	k := key.Event{
+		Code:      key.CodeBackButton,
+		Direction: key.DirPress,
+	}
+	log.Println("Logging key event back")
+	theApp.events.In() <- k
+
+	k.Direction = key.DirRelease
+	theApp.events.In() <- k
+}
+
 //export onCreate
 //export onCreate
 func onCreate(activity *C.ANativeActivity) {
 func onCreate(activity *C.ANativeActivity) {
 	// Set the initial configuration.
 	// Set the initial configuration.

+ 2 - 2
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/app.go

@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
 
 
-//go:build linux || darwin || windows
-// +build linux darwin windows
+//go:build freebsd || linux || darwin || windows
+// +build freebsd linux darwin windows
 
 
 package app
 package app
 
 

+ 4 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/darwin_desktop.go

@@ -63,6 +63,10 @@ func main(f func(App)) {
 	C.runApp()
 	C.runApp()
 }
 }
 
 
+func GoBack() {
+	// When simulating mobile there are no other activities open (and we can't just force background)
+}
+
 // loop is the primary drawing loop.
 // loop is the primary drawing loop.
 //
 //
 // After Cocoa has captured the initial OS thread for processing Cocoa
 // After Cocoa has captured the initial OS thread for processing Cocoa

+ 4 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/darwin_ios.go

@@ -82,6 +82,10 @@ var DisplayMetrics struct {
 	HeightPx int
 	HeightPx int
 }
 }
 
 
+func GoBack() {
+	// Apple do not permit apps to exit in any way other than user pressing home button / gesture
+}
+
 //export setDisplayMetrics
 //export setDisplayMetrics
 func setDisplayMetrics(width, height int, scale int) {
 func setDisplayMetrics(width, height int, scale int) {
 	DisplayMetrics.WidthPx = width
 	DisplayMetrics.WidthPx = width

+ 4 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/shiny.go

@@ -15,6 +15,10 @@ func main(f func(a App)) {
 	fmt.Errorf("Running mobile simulation mode does not currently work on Windows.")
 	fmt.Errorf("Running mobile simulation mode does not currently work on Windows.")
 }
 }
 
 
+func GoBack() {
+	// When simulating mobile there are no other activities open (and we can't just force background)
+}
+
 // driverShowVirtualKeyboard does nothing on desktop
 // driverShowVirtualKeyboard does nothing on desktop
 func driverShowVirtualKeyboard(KeyboardType) {
 func driverShowVirtualKeyboard(KeyboardType) {
 }
 }

+ 2 - 2
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/x11.c

@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
 
 
-//go:build linux && !android
-// +build linux,!android
+//go:build (linux && !android) || freebsd
+// +build linux,!android freebsd
 
 
 #include "_cgo_export.h"
 #include "_cgo_export.h"
 #include <EGL/egl.h>
 #include <EGL/egl.h>

+ 7 - 2
vendor/fyne.io/fyne/v2/internal/driver/mobile/app/x11.go

@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
 
 
-//go:build linux && !android
-// +build linux,!android
+//go:build (linux && !android) || freebsd
+// +build linux,!android freebsd
 
 
 package app
 package app
 
 
@@ -15,6 +15,7 @@ than screens with touch panels.
 
 
 /*
 /*
 #cgo LDFLAGS: -lEGL -lGLESv2 -lX11
 #cgo LDFLAGS: -lEGL -lGLESv2 -lX11
+#cgo freebsd CFLAGS: -I/usr/local/include/
 
 
 void createWindow(void);
 void createWindow(void);
 void processEvents(void);
 void processEvents(void);
@@ -79,6 +80,10 @@ func main(f func(App)) {
 	}
 	}
 }
 }
 
 
+func GoBack() {
+	// When simulating mobile there are no other activities open (and we can't just force background)
+}
+
 //export onResize
 //export onResize
 func onResize(w, h int) {
 func onResize(w, h int) {
 	// TODO(nigeltao): don't assume 72 DPI. DisplayWidth and DisplayWidthMM
 	// TODO(nigeltao): don't assume 72 DPI. DisplayWidth and DisplayWidthMM

+ 8 - 6
vendor/fyne.io/fyne/v2/internal/driver/mobile/canvas.go

@@ -29,8 +29,8 @@ type mobileCanvas struct {
 	scale            float32
 	scale            float32
 	size             fyne.Size
 	size             fyne.Size
 
 
-	touched map[int]mobile.Touchable
-	padded  bool
+	touched       map[int]mobile.Touchable
+	padded, debug bool
 
 
 	onTypedRune func(rune)
 	onTypedRune func(rune)
 	onTypedKey  func(event *fyne.KeyEvent)
 	onTypedKey  func(event *fyne.KeyEvent)
@@ -49,6 +49,7 @@ type mobileCanvas struct {
 // NewCanvas creates a new gomobile mobileCanvas. This is a mobileCanvas that will render on a mobile device using OpenGL.
 // NewCanvas creates a new gomobile mobileCanvas. This is a mobileCanvas that will render on a mobile device using OpenGL.
 func NewCanvas() fyne.Canvas {
 func NewCanvas() fyne.Canvas {
 	ret := &mobileCanvas{padded: true}
 	ret := &mobileCanvas{padded: true}
+	ret.debug = fyne.CurrentApp().Settings().BuildType() == fyne.BuildDebug
 	ret.scale = fyne.CurrentDevice().SystemScaleForWindow(nil) // we don't need a window parameter on mobile
 	ret.scale = fyne.CurrentDevice().SystemScaleForWindow(nil) // we don't need a window parameter on mobile
 	ret.touched = make(map[int]mobile.Touchable)
 	ret.touched = make(map[int]mobile.Touchable)
 	ret.lastTapDownPos = make(map[int]fyne.Position)
 	ret.lastTapDownPos = make(map[int]fyne.Position)
@@ -292,7 +293,7 @@ func (c *mobileCanvas) tapMove(pos fyne.Position, tapID int,
 		}
 		}
 	}
 	}
 
 
-	ev := new(fyne.DragEvent)
+	ev := &fyne.DragEvent{}
 	draggedObjDelta := c.dragStart.Subtract(c.dragging.(fyne.CanvasObject).Position())
 	draggedObjDelta := c.dragStart.Subtract(c.dragging.(fyne.CanvasObject).Position())
 	ev.Position = pos.Subtract(c.dragOffset).Add(draggedObjDelta)
 	ev.Position = pos.Subtract(c.dragOffset).Add(draggedObjDelta)
 	ev.Dragged = fyne.Delta{DX: deltaX, DY: deltaY}
 	ev.Dragged = fyne.Delta{DX: deltaX, DY: deltaY}
@@ -344,9 +345,10 @@ func (c *mobileCanvas) tapUp(pos fyne.Position, tapID int,
 		c.touched[tapID] = nil
 		c.touched[tapID] = nil
 	}
 	}
 
 
-	ev := new(fyne.PointEvent)
-	ev.Position = objPos
-	ev.AbsolutePosition = pos
+	ev := &fyne.PointEvent{
+		Position:         objPos,
+		AbsolutePosition: pos,
+	}
 
 
 	if duration < tapSecondaryDelay {
 	if duration < tapSecondaryDelay {
 		_, doubleTap := co.(fyne.DoubleTappable)
 		_, doubleTap := co.(fyne.DoubleTappable)

+ 29 - 12
vendor/fyne.io/fyne/v2/internal/driver/mobile/driver.go

@@ -7,6 +7,7 @@ import (
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/canvas"
+	"fyne.io/fyne/v2/driver/mobile"
 	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal/animation"
 	"fyne.io/fyne/v2/internal/animation"
 	intapp "fyne.io/fyne/v2/internal/app"
 	intapp "fyne.io/fyne/v2/internal/app"
@@ -22,6 +23,7 @@ import (
 	"fyne.io/fyne/v2/internal/driver/mobile/gl"
 	"fyne.io/fyne/v2/internal/driver/mobile/gl"
 	"fyne.io/fyne/v2/internal/painter"
 	"fyne.io/fyne/v2/internal/painter"
 	pgl "fyne.io/fyne/v2/internal/painter/gl"
 	pgl "fyne.io/fyne/v2/internal/painter/gl"
+	"fyne.io/fyne/v2/internal/scale"
 	"fyne.io/fyne/v2/theme"
 	"fyne.io/fyne/v2/theme"
 )
 )
 
 
@@ -126,6 +128,10 @@ func (d *mobileDriver) AbsolutePositionForObject(co fyne.CanvasObject) fyne.Posi
 	return pos.Subtract(inset)
 	return pos.Subtract(inset)
 }
 }
 
 
+func (d *mobileDriver) GoBack() {
+	app.GoBack()
+}
+
 func (d *mobileDriver) Quit() {
 func (d *mobileDriver) Quit() {
 	// Android and iOS guidelines say this should not be allowed!
 	// Android and iOS guidelines say this should not be allowed!
 }
 }
@@ -294,7 +300,7 @@ func (d *mobileDriver) paintWindow(window fyne.Window, size fyne.Size) {
 		}
 		}
 		c.Painter().Paint(obj, pos, size)
 		c.Painter().Paint(obj, pos, size)
 	}
 	}
-	afterDraw := func(node *common.RenderCacheNode) {
+	afterDraw := func(node *common.RenderCacheNode, pos fyne.Position) {
 		if _, ok := node.Obj().(fyne.Scrollable); ok {
 		if _, ok := node.Obj().(fyne.Scrollable); ok {
 			c.Painter().StopClipping()
 			c.Painter().StopClipping()
 			clips.Pop()
 			clips.Pop()
@@ -302,6 +308,10 @@ func (d *mobileDriver) paintWindow(window fyne.Window, size fyne.Size) {
 				c.Painter().StartClipping(top.Rect())
 				c.Painter().StartClipping(top.Rect())
 			}
 			}
 		}
 		}
+
+		if c.debug {
+			c.DrawDebugOverlay(node.Obj(), pos, size)
+		}
 	}
 	}
 
 
 	c.WalkTrees(draw, afterDraw)
 	c.WalkTrees(draw, afterDraw)
@@ -330,16 +340,16 @@ func (d *mobileDriver) setTheme(dark bool) {
 }
 }
 
 
 func (d *mobileDriver) tapDownCanvas(w *window, x, y float32, tapID touch.Sequence) {
 func (d *mobileDriver) tapDownCanvas(w *window, x, y float32, tapID touch.Sequence) {
-	tapX := internal.UnscaleInt(w.canvas, int(x))
-	tapY := internal.UnscaleInt(w.canvas, int(y))
+	tapX := scale.ToFyneCoordinate(w.canvas, int(x))
+	tapY := scale.ToFyneCoordinate(w.canvas, int(y))
 	pos := fyne.NewPos(tapX, tapY+tapYOffset)
 	pos := fyne.NewPos(tapX, tapY+tapYOffset)
 
 
 	w.canvas.tapDown(pos, int(tapID))
 	w.canvas.tapDown(pos, int(tapID))
 }
 }
 
 
 func (d *mobileDriver) tapMoveCanvas(w *window, x, y float32, tapID touch.Sequence) {
 func (d *mobileDriver) tapMoveCanvas(w *window, x, y float32, tapID touch.Sequence) {
-	tapX := internal.UnscaleInt(w.canvas, int(x))
-	tapY := internal.UnscaleInt(w.canvas, int(y))
+	tapX := scale.ToFyneCoordinate(w.canvas, int(x))
+	tapY := scale.ToFyneCoordinate(w.canvas, int(y))
 	pos := fyne.NewPos(tapX, tapY+tapYOffset)
 	pos := fyne.NewPos(tapX, tapY+tapYOffset)
 
 
 	w.canvas.tapMove(pos, int(tapID), func(wid fyne.Draggable, ev *fyne.DragEvent) {
 	w.canvas.tapMove(pos, int(tapID), func(wid fyne.Draggable, ev *fyne.DragEvent) {
@@ -348,8 +358,8 @@ func (d *mobileDriver) tapMoveCanvas(w *window, x, y float32, tapID touch.Sequen
 }
 }
 
 
 func (d *mobileDriver) tapUpCanvas(w *window, x, y float32, tapID touch.Sequence) {
 func (d *mobileDriver) tapUpCanvas(w *window, x, y float32, tapID touch.Sequence) {
-	tapX := internal.UnscaleInt(w.canvas, int(x))
-	tapY := internal.UnscaleInt(w.canvas, int(y))
+	tapX := scale.ToFyneCoordinate(w.canvas, int(x))
+	tapY := scale.ToFyneCoordinate(w.canvas, int(y))
 	pos := fyne.NewPos(tapX, tapY+tapYOffset)
 	pos := fyne.NewPos(tapX, tapY+tapYOffset)
 
 
 	w.canvas.tapUp(pos, int(tapID), func(wid fyne.Tappable, ev *fyne.PointEvent) {
 	w.canvas.tapUp(pos, int(tapID), func(wid fyne.Tappable, ev *fyne.PointEvent) {
@@ -453,6 +463,8 @@ var keyCodeMap = map[key.Code]fyne.KeyName{
 	key.CodeBackslash:          fyne.KeyBackslash,
 	key.CodeBackslash:          fyne.KeyBackslash,
 	key.CodeRightSquareBracket: fyne.KeyRightBracket,
 	key.CodeRightSquareBracket: fyne.KeyRightBracket,
 	key.CodeGraveAccent:        fyne.KeyBackTick,
 	key.CodeGraveAccent:        fyne.KeyBackTick,
+
+	key.CodeBackButton: mobile.KeyBack,
 }
 }
 
 
 func keyToName(code key.Code) fyne.KeyName {
 func keyToName(code key.Code) fyne.KeyName {
@@ -503,8 +515,12 @@ func (d *mobileDriver) typeDownCanvas(canvas *mobileCanvas, r rune, code key.Cod
 			canvas.Focused().TypedRune(r)
 			canvas.Focused().TypedRune(r)
 		}
 		}
 	} else {
 	} else {
-		if keyName != "" && canvas.onTypedKey != nil {
-			canvas.onTypedKey(keyEvent)
+		if keyName != "" {
+			if canvas.onTypedKey != nil {
+				canvas.onTypedKey(keyEvent)
+			} else if keyName == mobile.KeyBack {
+				d.GoBack()
+			}
 		}
 		}
 		if r > 0 && canvas.onTypedRune != nil {
 		if r > 0 && canvas.onTypedRune != nil {
 			canvas.onTypedRune(r)
 			canvas.onTypedRune(r)
@@ -530,9 +546,10 @@ func (d *mobileDriver) SetOnConfigurationChanged(f func(*Configuration)) {
 // NewGoMobileDriver sets up a new Driver instance implemented using the Go
 // NewGoMobileDriver sets up a new Driver instance implemented using the Go
 // Mobile extension and OpenGL bindings.
 // Mobile extension and OpenGL bindings.
 func NewGoMobileDriver() fyne.Driver {
 func NewGoMobileDriver() fyne.Driver {
-	d := new(mobileDriver)
-	d.theme = fyne.ThemeVariant(2) // unspecified
-	d.animation = &animation.Runner{}
+	d := &mobileDriver{
+		theme:     fyne.ThemeVariant(2), // unspecified
+		animation: &animation.Runner{},
+	}
 
 
 	registerRepository(d)
 	registerRepository(d)
 	return d
 	return d

+ 2 - 0
vendor/fyne.io/fyne/v2/internal/driver/mobile/event/key/key.go

@@ -221,6 +221,8 @@ const (
 	CodeRightAlt     Code = 230
 	CodeRightAlt     Code = 230
 	CodeRightGUI     Code = 231
 	CodeRightGUI     Code = 231
 
 
+	CodeBackButton Code = 301 // anything above 255 is not used in the USB spec
+
 	// The following codes are not part of the standard USB HID Usage IDs for
 	// The following codes are not part of the standard USB HID Usage IDs for
 	// keyboards. See http://www.usb.org/developers/hidpage/Hut1_12v2.pdf
 	// keyboards. See http://www.usb.org/developers/hidpage/Hut1_12v2.pdf
 	//
 	//

+ 0 - 2
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/consts.go

@@ -44,14 +44,12 @@ const (
 	DepthTest        = 0x0B71
 	DepthTest        = 0x0B71
 	Blend            = 0x0BE2
 	Blend            = 0x0BE2
 	ScissorTest      = 0x0C11
 	ScissorTest      = 0x0C11
-	UnpackAlignment  = 0x0CF5
 	Texture2D        = 0x0DE1
 	Texture2D        = 0x0DE1
 
 
 	UnsignedByte = 0x1401
 	UnsignedByte = 0x1401
 	Float        = 0x1406
 	Float        = 0x1406
 	RED          = 0x1903
 	RED          = 0x1903
 	RGBA         = 0x1908
 	RGBA         = 0x1908
-	LUMINANCE    = 0x1909
 
 
 	Nearest          = 0x2600
 	Nearest          = 0x2600
 	Linear           = 0x2601
 	Linear           = 0x2601

+ 8 - 9
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/dll_windows.go

@@ -10,7 +10,6 @@ import (
 	"debug/pe"
 	"debug/pe"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"io/ioutil"
 	"log"
 	"log"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
@@ -18,7 +17,7 @@ import (
 	"runtime"
 	"runtime"
 )
 )
 
 
-var debug = log.New(ioutil.Discard, "gl: ", log.LstdFlags)
+var debug = log.New(io.Discard, "gl: ", log.LstdFlags)
 
 
 func downloadDLLs() (path string, err error) {
 func downloadDLLs() (path string, err error) {
 	url := "https://dl.google.com/go/mobile/angle-bd3f8780b-" + runtime.GOARCH + ".tgz"
 	url := "https://dl.google.com/go/mobile/angle-bd3f8780b-" + runtime.GOARCH + ".tgz"
@@ -54,11 +53,11 @@ func downloadDLLs() (path string, err error) {
 		}
 		}
 		switch header.Name {
 		switch header.Name {
 		case "angle-" + runtime.GOARCH + "/libglesv2.dll":
 		case "angle-" + runtime.GOARCH + "/libglesv2.dll":
-			bytesGLESv2, err = ioutil.ReadAll(tr)
+			bytesGLESv2, err = io.ReadAll(tr)
 		case "angle-" + runtime.GOARCH + "/libegl.dll":
 		case "angle-" + runtime.GOARCH + "/libegl.dll":
-			bytesEGL, err = ioutil.ReadAll(tr)
+			bytesEGL, err = io.ReadAll(tr)
 		case "angle-" + runtime.GOARCH + "/d3dcompiler_47.dll":
 		case "angle-" + runtime.GOARCH + "/d3dcompiler_47.dll":
-			bytesD3DCompiler, err = ioutil.ReadAll(tr)
+			bytesD3DCompiler, err = io.ReadAll(tr)
 		default: // skip
 		default: // skip
 		}
 		}
 		if err != nil {
 		if err != nil {
@@ -70,13 +69,13 @@ func downloadDLLs() (path string, err error) {
 	}
 	}
 
 
 	writeDLLs := func(path string) error {
 	writeDLLs := func(path string) error {
-		if err := ioutil.WriteFile(filepath.Join(path, "libglesv2.dll"), bytesGLESv2, 0755); err != nil {
+		if err := os.WriteFile(filepath.Join(path, "libglesv2.dll"), bytesGLESv2, 0755); err != nil {
 			return fmt.Errorf("gl: cannot install ANGLE: %v", err)
 			return fmt.Errorf("gl: cannot install ANGLE: %v", err)
 		}
 		}
-		if err := ioutil.WriteFile(filepath.Join(path, "libegl.dll"), bytesEGL, 0755); err != nil {
+		if err := os.WriteFile(filepath.Join(path, "libegl.dll"), bytesEGL, 0755); err != nil {
 			return fmt.Errorf("gl: cannot install ANGLE: %v", err)
 			return fmt.Errorf("gl: cannot install ANGLE: %v", err)
 		}
 		}
-		if err := ioutil.WriteFile(filepath.Join(path, "d3dcompiler_47.dll"), bytesD3DCompiler, 0755); err != nil {
+		if err := os.WriteFile(filepath.Join(path, "d3dcompiler_47.dll"), bytesD3DCompiler, 0755); err != nil {
 			return fmt.Errorf("gl: cannot install ANGLE: %v", err)
 			return fmt.Errorf("gl: cannot install ANGLE: %v", err)
 		}
 		}
 		return nil
 		return nil
@@ -152,7 +151,7 @@ func chromePath() string {
 	}
 	}
 
 
 	for _, installdir := range installdirs {
 	for _, installdir := range installdirs {
-		versiondirs, err := ioutil.ReadDir(installdir)
+		versiondirs, err := os.ReadDir(installdir)
 		if err != nil {
 		if err != nil {
 			continue
 			continue
 		}
 		}

+ 1 - 1
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/fn.go

@@ -63,7 +63,6 @@ const (
 	glfnGetShaderiv
 	glfnGetShaderiv
 	glfnGetTexParameteriv
 	glfnGetTexParameteriv
 	glfnGetUniformLocation
 	glfnGetUniformLocation
-	glfnPixelStorei
 	glfnLinkProgram
 	glfnLinkProgram
 	glfnReadPixels
 	glfnReadPixels
 	glfnScissor
 	glfnScissor
@@ -71,6 +70,7 @@ const (
 	glfnTexImage2D
 	glfnTexImage2D
 	glfnTexParameteri
 	glfnTexParameteri
 	glfnUniform1f
 	glfnUniform1f
+	glfnUniform2f
 	glfnUniform4f
 	glfnUniform4f
 	glfnUniform4fv
 	glfnUniform4fv
 	glfnUseProgram
 	glfnUseProgram

+ 16 - 10
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/gl.go

@@ -45,6 +45,7 @@ func (ctx *context) BindBuffer(target Enum, b Buffer) {
 		},
 		},
 	})
 	})
 }
 }
+
 func (ctx *context) BindTexture(target Enum, t Texture) {
 func (ctx *context) BindTexture(target Enum, t Texture) {
 	ctx.enqueue(call{
 	ctx.enqueue(call{
 		args: fnargs{
 		args: fnargs{
@@ -181,6 +182,7 @@ func (ctx *context) CreateVertexArray() VertexArray {
 		blocking: true,
 		blocking: true,
 	}))}
 	}))}
 }
 }
+
 func (ctx *context) DeleteBuffer(v Buffer) {
 func (ctx *context) DeleteBuffer(v Buffer) {
 	ctx.enqueue(call{
 	ctx.enqueue(call{
 		args: fnargs{
 		args: fnargs{
@@ -218,6 +220,7 @@ func (ctx *context) DrawArrays(mode Enum, first, count int) {
 		},
 		},
 	})
 	})
 }
 }
+
 func (ctx *context) Enable(cap Enum) {
 func (ctx *context) Enable(cap Enum) {
 	ctx.enqueue(call{
 	ctx.enqueue(call{
 		args: fnargs{
 		args: fnargs{
@@ -382,16 +385,6 @@ func (ctx *context) LinkProgram(p Program) {
 	})
 	})
 }
 }
 
 
-func (ctx *context) PixelStorei(pname Enum, param int32) {
-	ctx.enqueue(call{
-		args: fnargs{
-			fn: glfnPixelStorei,
-			a0: pname.c(),
-			a1: uintptr(param),
-		},
-	})
-}
-
 func (ctx *context) ReadPixels(dst []byte, x, y, width, height int, format, ty Enum) {
 func (ctx *context) ReadPixels(dst []byte, x, y, width, height int, format, ty Enum) {
 	ctx.enqueue(call{
 	ctx.enqueue(call{
 		args: fnargs{
 		args: fnargs{
@@ -434,6 +427,7 @@ func (ctx *context) ShaderSource(s Shader, src string) {
 		blocking: true,
 		blocking: true,
 	})
 	})
 }
 }
+
 func (ctx *context) TexImage2D(target Enum, level int, internalFormat int, width, height int, format Enum, ty Enum, data []byte) {
 func (ctx *context) TexImage2D(target Enum, level int, internalFormat int, width, height int, format Enum, ty Enum, data []byte) {
 	// It is common to pass TexImage2D a nil data, indicating that a
 	// It is common to pass TexImage2D a nil data, indicating that a
 	// bound GL buffer is being used as the source. In that case, it
 	// bound GL buffer is being used as the source. In that case, it
@@ -480,6 +474,18 @@ func (ctx *context) Uniform1f(dst Uniform, v float32) {
 		},
 		},
 	})
 	})
 }
 }
+
+func (ctx *context) Uniform2f(dst Uniform, v0, v1 float32) {
+	ctx.enqueue(call{
+		args: fnargs{
+			fn: glfnUniform2f,
+			a0: dst.c(),
+			a1: uintptr(math.Float32bits(v0)),
+			a2: uintptr(math.Float32bits(v1)),
+		},
+	})
+}
+
 func (ctx *context) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
 func (ctx *context) Uniform4f(dst Uniform, v0, v1, v2, v3 float32) {
 	ctx.enqueue(call{
 	ctx.enqueue(call{
 		args: fnargs{
 		args: fnargs{

+ 6 - 5
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/interface.go

@@ -172,11 +172,6 @@ type Context interface {
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glLinkProgram.xhtml
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glLinkProgram.xhtml
 	LinkProgram(p Program)
 	LinkProgram(p Program)
 
 
-	// PixelStorei set pixel storage modes
-	//
-	// https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glPixelStorei.xhtml
-	PixelStorei(pname Enum, param int32)
-
 	// ReadPixels returns pixel data from a buffer.
 	// ReadPixels returns pixel data from a buffer.
 	//
 	//
 	// In GLES 3, the source buffer is controlled with ReadBuffer.
 	// In GLES 3, the source buffer is controlled with ReadBuffer.
@@ -208,6 +203,11 @@ type Context interface {
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
 	Uniform1f(dst Uniform, v float32)
 	Uniform1f(dst Uniform, v float32)
 
 
+	// Uniform2f writes a vec2 uniform variable.
+	//
+	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
+	Uniform2f(dst Uniform, v0, v1 float32)
+
 	// Uniform4f writes a vec4 uniform variable.
 	// Uniform4f writes a vec4 uniform variable.
 	//
 	//
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
@@ -217,6 +217,7 @@ type Context interface {
 	//
 	//
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
 	Uniform4fv(dst Uniform, src []float32)
 	Uniform4fv(dst Uniform, src []float32)
+
 	// UseProgram sets the active program.
 	// UseProgram sets the active program.
 	//
 	//
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUseProgram.xhtml
 	// http://www.khronos.org/opengles/sdk/docs/man3/html/glUseProgram.xhtml

+ 3 - 3
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work.c

@@ -125,9 +125,6 @@ uintptr_t processFn(struct fnargs* args, char* parg) {
 	case glfnLinkProgram:
 	case glfnLinkProgram:
 		glLinkProgram((GLint)args->a0);
 		glLinkProgram((GLint)args->a0);
 		break;
 		break;
-	case glfnPixelStorei:
-		glPixelStorei((GLenum)args->a0, (GLint)args->a1);
-		break;
 	case glfnReadPixels:
 	case glfnReadPixels:
 		glReadPixels((GLint)args->a0, (GLint)args->a1, (GLsizei)args->a2, (GLsizei)args->a3, (GLenum)args->a4, (GLenum)args->a5, (void*)parg);
 		glReadPixels((GLint)args->a0, (GLint)args->a1, (GLsizei)args->a2, (GLsizei)args->a3, (GLenum)args->a4, (GLenum)args->a5, (void*)parg);
 		break;
 		break;
@@ -159,6 +156,9 @@ uintptr_t processFn(struct fnargs* args, char* parg) {
 	case glfnUniform1f:
 	case glfnUniform1f:
 		glUniform1f((GLint)args->a0, *(GLfloat*)&args->a1);
 		glUniform1f((GLint)args->a0, *(GLfloat*)&args->a1);
 		break;
 		break;
+	case glfnUniform2f:
+		glUniform2f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2);
+		break;
 	case glfnUniform4f:
 	case glfnUniform4f:
 		glUniform4f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3, *(GLfloat*)&args->a4);
 		glUniform4f((GLint)args->a0, *(GLfloat*)&args->a1, *(GLfloat*)&args->a2, *(GLfloat*)&args->a3, *(GLfloat*)&args->a4);
 		break;
 		break;

+ 3 - 4
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work.go

@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
 
 
-//go:build (darwin || linux || openbsd || freebsd) && go1.15
+//go:build darwin || linux || openbsd || freebsd
 // +build darwin linux openbsd freebsd
 // +build darwin linux openbsd freebsd
-// +build go1.15
 
 
 package gl
 package gl
 
 
@@ -13,7 +12,7 @@ package gl
 #cgo darwin,!ios  LDFLAGS: -framework OpenGL
 #cgo darwin,!ios  LDFLAGS: -framework OpenGL
 #cgo linux        LDFLAGS: -lGLESv2
 #cgo linux        LDFLAGS: -lGLESv2
 #cgo openbsd      LDFLAGS: -L/usr/X11R6/lib/ -lGLESv2
 #cgo openbsd      LDFLAGS: -L/usr/X11R6/lib/ -lGLESv2
-#cgo freebsd      LDFLAGS: -L/usr/local/X11R6/lib/ -lGLESv2
+#cgo freebsd      LDFLAGS: -L/usr/local/lib/ -lGLESv2
 
 
 #cgo android      CFLAGS: -Dos_android
 #cgo android      CFLAGS: -Dos_android
 #cgo ios          CFLAGS: -Dos_ios
 #cgo ios          CFLAGS: -Dos_ios
@@ -23,7 +22,7 @@ package gl
 #cgo openbsd      CFLAGS: -Dos_openbsd
 #cgo openbsd      CFLAGS: -Dos_openbsd
 #cgo freebsd      CFLAGS: -Dos_freebsd
 #cgo freebsd      CFLAGS: -Dos_freebsd
 #cgo openbsd      CFLAGS: -I/usr/X11R6/include/
 #cgo openbsd      CFLAGS: -I/usr/X11R6/include/
-#cgo freebsd      CFLAGS: -I/usr/local/X11R6/include/
+#cgo freebsd      CFLAGS: -I/usr/local/include/
 
 
 #include <stdint.h>
 #include <stdint.h>
 #include "work.h"
 #include "work.h"

+ 1 - 1
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work.h

@@ -69,7 +69,6 @@ typedef enum {
 	glfnGetShaderiv,
 	glfnGetShaderiv,
 	glfnGetTexParameteriv,
 	glfnGetTexParameteriv,
 	glfnGetUniformLocation,
 	glfnGetUniformLocation,
-	glfnPixelStorei,
 	glfnLinkProgram,
 	glfnLinkProgram,
 	glfnReadPixels,
 	glfnReadPixels,
 	glfnScissor,
 	glfnScissor,
@@ -77,6 +76,7 @@ typedef enum {
 	glfnTexImage2D,
 	glfnTexImage2D,
 	glfnTexParameteri,
 	glfnTexParameteri,
 	glfnUniform1f,
 	glfnUniform1f,
+	glfnUniform2f,
 	glfnUniform4f,
 	glfnUniform4f,
 	glfnUniform4fv,
 	glfnUniform4fv,
 	glfnUseProgram,
 	glfnUseProgram,

+ 0 - 181
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work114.go

@@ -1,181 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build (darwin || linux || openbsd || freebsd) && !go1.15
-// +build darwin linux openbsd freebsd
-// +build !go1.15
-
-package gl
-
-/*
-#cgo ios                LDFLAGS: -framework OpenGLES
-#cgo darwin,amd64,!ios  LDFLAGS: -framework OpenGL
-#cgo darwin,arm         LDFLAGS: -framework OpenGLES
-#cgo darwin,arm64       LDFLAGS: -framework OpenGLES
-#cgo linux              LDFLAGS: -lGLESv2
-#cgo openbsd            LDFLAGS: -L/usr/X11R6/lib/ -lGLESv2
-#cgo freebsd            LDFLAGS: -L/usr/local/X11R6/lib/ -lGLESv2
-
-#cgo android            CFLAGS: -Dos_android
-#cgo ios                CFLAGS: -Dos_ios
-#cgo darwin,amd64,!ios  CFLAGS: -Dos_macos
-#cgo darwin,arm         CFLAGS: -Dos_ios
-#cgo darwin,arm64       CFLAGS: -Dos_ios
-#cgo darwin             CFLAGS: -DGL_SILENCE_DEPRECATION
-#cgo linux        CFLAGS: -Dos_linux
-#cgo openbsd      CFLAGS: -Dos_openbsd
-#cgo freebsd      CFLAGS: -Dos_freebsd
-#cgo openbsd      CFLAGS: -I/usr/X11R6/include/
-#cgo freebsd      CFLAGS: -I/usr/local/X11R6/include/
-
-#include <stdint.h>
-#include "work.h"
-
-uintptr_t process(struct fnargs* cargs, char* parg0, char* parg1, char* parg2, int count) {
-	uintptr_t ret;
-
-	ret = processFn(&cargs[0], parg0);
-	if (count > 1) {
-		ret = processFn(&cargs[1], parg1);
-	}
-	if (count > 2) {
-		ret = processFn(&cargs[2], parg2);
-	}
-
-	return ret;
-}
-*/
-import "C"
-
-import (
-	"unsafe"
-
-	"fyne.io/fyne/v2/internal/async"
-)
-
-const workbufLen = 3
-
-type context struct {
-	cptr  uintptr
-	debug int32
-
-	workAvailable *async.UnboundedStructChan
-
-	// work is a queue of calls to execute.
-	work chan call
-
-	// retvalue is sent a return value when blocking calls complete.
-	// It is safe to use a global unbuffered channel here as calls
-	// cannot currently be made concurrently.
-	//
-	// TODO: the comment above about concurrent calls isn't actually true: package
-	// app calls package gl, but it has to do so in a separate goroutine, which
-	// means that its gl calls (which may be blocking) can race with other gl calls
-	// in the main program. We should make it safe to issue blocking gl calls
-	// concurrently, or get the gl calls out of package app, or both.
-	retvalue chan C.uintptr_t
-
-	cargs [workbufLen]C.struct_fnargs
-	parg  [workbufLen]*C.char
-}
-
-func (ctx *context) WorkAvailable() <-chan struct{} { return ctx.workAvailable.Out() }
-
-type context3 struct {
-	*context
-}
-
-// NewContext creates a cgo OpenGL context.
-//
-// See the Worker interface for more details on how it is used.
-func NewContext() (Context, Worker) {
-	glctx := &context{
-		workAvailable: async.NewUnboundedStructChan(),
-		work:          make(chan call, workbufLen*4),
-		retvalue:      make(chan C.uintptr_t),
-	}
-	if C.GLES_VERSION == "GL_ES_2_0" {
-		return glctx, glctx
-	}
-	return context3{glctx}, glctx
-}
-
-// Version returns a GL ES version string, either "GL_ES_2_0" or "GL_ES_3_0".
-// Future versions of the gl package may return "GL_ES_3_1".
-func Version() string {
-	return C.GLES_VERSION
-}
-
-func (ctx *context) enqueue(c call) uintptr {
-	ctx.work <- c
-	ctx.workAvailable.In() <- struct{}{}
-
-	if c.blocking {
-		return uintptr(<-ctx.retvalue)
-	}
-	return 0
-}
-
-func (ctx *context) DoWork() {
-	queue := make([]call, 0, workbufLen)
-	for {
-		// Wait until at least one piece of work is ready.
-		// Accumulate work until a piece is marked as blocking.
-		select {
-		case w := <-ctx.work:
-			queue = append(queue, w)
-		default:
-			return
-		}
-		blocking := queue[len(queue)-1].blocking
-	enqueue:
-		for len(queue) < cap(queue) && !blocking {
-			select {
-			case w := <-ctx.work:
-				queue = append(queue, w)
-				blocking = queue[len(queue)-1].blocking
-			default:
-				break enqueue
-			}
-		}
-
-		// Process the queued GL functions.
-		for i, q := range queue {
-			ctx.cargs[i] = *(*C.struct_fnargs)(unsafe.Pointer(&q.args))
-			ctx.parg[i] = (*C.char)(q.parg)
-		}
-		ret := C.process(&ctx.cargs[0], ctx.parg[0], ctx.parg[1], ctx.parg[2], C.int(len(queue)))
-
-		// Cleanup and signal.
-		queue = queue[:0]
-		if blocking {
-			ctx.retvalue <- ret
-		}
-	}
-}
-
-func init() {
-	if unsafe.Sizeof(C.GLint(0)) != unsafe.Sizeof(int32(0)) {
-		panic("GLint is not an int32")
-	}
-}
-
-// cString creates C string off the Go heap.
-// ret is a *char.
-func (ctx *context) cString(str string) (uintptr, func()) {
-	ptr := unsafe.Pointer(C.CString(str))
-	return uintptr(ptr), func() { C.free(ptr) }
-}
-
-// cString creates a pointer to a C string off the Go heap.
-// ret is a **char.
-func (ctx *context) cStringPtr(str string) (uintptr, func()) {
-	s, free := ctx.cString(str)
-	ptr := C.malloc(C.size_t(unsafe.Sizeof((*int)(nil))))
-	*(*uintptr)(ptr) = s
-	return uintptr(ptr), func() {
-		free()
-		C.free(ptr)
-	}
-}

+ 5 - 4
vendor/fyne.io/fyne/v2/internal/driver/mobile/gl/work_windows.go

@@ -237,10 +237,6 @@ var glfnMap = map[glfn]func(c call) (ret uintptr){
 		ret, _, _ = syscall.Syscall(glGetUniformLocation.Addr(), 2, c.args.a0, c.args.a1, 0)
 		ret, _, _ = syscall.Syscall(glGetUniformLocation.Addr(), 2, c.args.a0, c.args.a1, 0)
 		return ret
 		return ret
 	},
 	},
-	glfnPixelStorei: func(c call) (ret uintptr) {
-		syscall.Syscall(glPixelStorei.Addr(), 2, c.args.a0, c.args.a1, 0)
-		return
-	},
 	glfnLinkProgram: func(c call) (ret uintptr) {
 	glfnLinkProgram: func(c call) (ret uintptr) {
 		syscall.Syscall(glLinkProgram.Addr(), 1, c.args.a0, 0, 0)
 		syscall.Syscall(glLinkProgram.Addr(), 1, c.args.a0, 0, 0)
 		return
 		return
@@ -269,6 +265,10 @@ var glfnMap = map[glfn]func(c call) (ret uintptr){
 		syscall.Syscall6(glUniform1f.Addr(), 2, c.args.a0, c.args.a1, c.args.a2, c.args.a3, c.args.a4, c.args.a5)
 		syscall.Syscall6(glUniform1f.Addr(), 2, c.args.a0, c.args.a1, c.args.a2, c.args.a3, c.args.a4, c.args.a5)
 		return
 		return
 	},
 	},
+	glfnUniform2f: func(c call) (ret uintptr) {
+		syscall.Syscall6(glUniform2f.Addr(), 3, c.args.a0, c.args.a1, c.args.a2, c.args.a3, c.args.a4, c.args.a5)
+		return
+	},
 	glfnUniform4f: func(c call) (ret uintptr) {
 	glfnUniform4f: func(c call) (ret uintptr) {
 		syscall.Syscall6(glUniform4f.Addr(), 5, c.args.a0, c.args.a1, c.args.a2, c.args.a3, c.args.a4, c.args.a5)
 		syscall.Syscall6(glUniform4f.Addr(), 5, c.args.a0, c.args.a1, c.args.a2, c.args.a3, c.args.a4, c.args.a5)
 		return
 		return
@@ -356,6 +356,7 @@ var (
 	glTexImage2D              = libGLESv2.NewProc("glTexImage2D")
 	glTexImage2D              = libGLESv2.NewProc("glTexImage2D")
 	glTexParameteri           = libGLESv2.NewProc("glTexParameteri")
 	glTexParameteri           = libGLESv2.NewProc("glTexParameteri")
 	glUniform1f               = libGLESv2.NewProc("glUniform1f")
 	glUniform1f               = libGLESv2.NewProc("glUniform1f")
+	glUniform2f               = libGLESv2.NewProc("glUniform2f")
 	glUniform4f               = libGLESv2.NewProc("glUniform4f")
 	glUniform4f               = libGLESv2.NewProc("glUniform4f")
 	glUniform4fv              = libGLESv2.NewProc("glUniform4fv")
 	glUniform4fv              = libGLESv2.NewProc("glUniform4fv")
 	glUseProgram              = libGLESv2.NewProc("glUseProgram")
 	glUseProgram              = libGLESv2.NewProc("glUseProgram")

+ 5 - 1
vendor/fyne.io/fyne/v2/internal/driver/mobile/window.go

@@ -101,6 +101,10 @@ func (w *window) SetCloseIntercept(callback func()) {
 	w.onCloseIntercepted = callback
 	w.onCloseIntercepted = callback
 }
 }
 
 
+func (w *window) SetOnDropped(dropped func(fyne.Position, []fyne.URI)) {
+	// not implemented yet
+}
+
 func (w *window) Show() {
 func (w *window) Show() {
 	menu := fyne.CurrentApp().Driver().(*mobileDriver).findMenu(w)
 	menu := fyne.CurrentApp().Driver().(*mobileDriver).findMenu(w)
 	menuButton := w.newMenuButton(menu)
 	menuButton := w.newMenuButton(menu)
@@ -161,7 +165,7 @@ func (w *window) Close() {
 		w.canvas.Painter().Free(obj)
 		w.canvas.Painter().Free(obj)
 	})
 	})
 
 
-	w.canvas.WalkTrees(nil, func(node *common.RenderCacheNode) {
+	w.canvas.WalkTrees(nil, func(node *common.RenderCacheNode, _ fyne.Position) {
 		if wid, ok := node.Obj().(fyne.Widget); ok {
 		if wid, ok := node.Obj().(fyne.Widget); ok {
 			cache.DestroyRenderer(wid)
 			cache.DestroyRenderer(wid)
 		}
 		}

+ 8 - 6
vendor/fyne.io/fyne/v2/internal/driver/util.go

@@ -92,7 +92,7 @@ func FindObjectAtPositionMatching(mouse fyne.Position, matches func(object fyne.
 func ReverseWalkVisibleObjectTree(
 func ReverseWalkVisibleObjectTree(
 	obj fyne.CanvasObject,
 	obj fyne.CanvasObject,
 	beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
 	beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
-	afterChildren func(fyne.CanvasObject, fyne.CanvasObject),
+	afterChildren func(fyne.CanvasObject, fyne.Position, fyne.CanvasObject),
 ) bool {
 ) bool {
 	clipSize := fyne.NewSize(math.MaxInt32, math.MaxInt32)
 	clipSize := fyne.NewSize(math.MaxInt32, math.MaxInt32)
 	return walkObjectTree(obj, true, nil, fyne.NewPos(0, 0), fyne.NewPos(0, 0), clipSize, beforeChildren, afterChildren, true)
 	return walkObjectTree(obj, true, nil, fyne.NewPos(0, 0), fyne.NewPos(0, 0), clipSize, beforeChildren, afterChildren, true)
@@ -110,7 +110,7 @@ func ReverseWalkVisibleObjectTree(
 func WalkCompleteObjectTree(
 func WalkCompleteObjectTree(
 	obj fyne.CanvasObject,
 	obj fyne.CanvasObject,
 	beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
 	beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
-	afterChildren func(fyne.CanvasObject, fyne.CanvasObject),
+	afterChildren func(fyne.CanvasObject, fyne.Position, fyne.CanvasObject),
 ) bool {
 ) bool {
 	clipSize := fyne.NewSize(math.MaxInt32, math.MaxInt32)
 	clipSize := fyne.NewSize(math.MaxInt32, math.MaxInt32)
 	return walkObjectTree(obj, false, nil, fyne.NewPos(0, 0), fyne.NewPos(0, 0), clipSize, beforeChildren, afterChildren, false)
 	return walkObjectTree(obj, false, nil, fyne.NewPos(0, 0), fyne.NewPos(0, 0), clipSize, beforeChildren, afterChildren, false)
@@ -128,7 +128,7 @@ func WalkCompleteObjectTree(
 func WalkVisibleObjectTree(
 func WalkVisibleObjectTree(
 	obj fyne.CanvasObject,
 	obj fyne.CanvasObject,
 	beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
 	beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
-	afterChildren func(fyne.CanvasObject, fyne.CanvasObject),
+	afterChildren func(fyne.CanvasObject, fyne.Position, fyne.CanvasObject),
 ) bool {
 ) bool {
 	clipSize := fyne.NewSize(math.MaxInt32, math.MaxInt32)
 	clipSize := fyne.NewSize(math.MaxInt32, math.MaxInt32)
 	return walkObjectTree(obj, false, nil, fyne.NewPos(0, 0), fyne.NewPos(0, 0), clipSize, beforeChildren, afterChildren, true)
 	return walkObjectTree(obj, false, nil, fyne.NewPos(0, 0), fyne.NewPos(0, 0), clipSize, beforeChildren, afterChildren, true)
@@ -141,7 +141,7 @@ func walkObjectTree(
 	offset, clipPos fyne.Position,
 	offset, clipPos fyne.Position,
 	clipSize fyne.Size,
 	clipSize fyne.Size,
 	beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
 	beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool,
-	afterChildren func(fyne.CanvasObject, fyne.CanvasObject),
+	afterChildren func(fyne.CanvasObject, fyne.Position, fyne.CanvasObject),
 	requireVisible bool,
 	requireVisible bool,
 ) bool {
 ) bool {
 	if obj == nil {
 	if obj == nil {
@@ -157,7 +157,9 @@ func walkObjectTree(
 	case *fyne.Container:
 	case *fyne.Container:
 		children = co.Objects
 		children = co.Objects
 	case fyne.Widget:
 	case fyne.Widget:
-		children = cache.Renderer(co).Objects()
+		if cache.IsRendered(co) || requireVisible {
+			children = cache.Renderer(co).Objects()
+		}
 	}
 	}
 
 
 	if _, ok := obj.(fyne.Scrollable); ok {
 	if _, ok := obj.(fyne.Scrollable); ok {
@@ -194,7 +196,7 @@ func walkObjectTree(
 	}
 	}
 
 
 	if afterChildren != nil {
 	if afterChildren != nil {
-		afterChildren(obj, parent)
+		afterChildren(obj, pos, parent)
 	}
 	}
 	return cancelled
 	return cancelled
 }
 }

+ 12 - 2
vendor/fyne.io/fyne/v2/internal/overlay_stack.go

@@ -75,13 +75,23 @@ func (s *OverlayStack) Remove(overlay fyne.CanvasObject) {
 	s.propertyLock.Lock()
 	s.propertyLock.Lock()
 	defer s.propertyLock.Unlock()
 	defer s.propertyLock.Unlock()
 
 
+	overlayIdx := -1
 	for i, o := range s.overlays {
 	for i, o := range s.overlays {
 		if o == overlay {
 		if o == overlay {
-			s.overlays = s.overlays[:i]
-			s.focusManagers = s.focusManagers[:i]
+			overlayIdx = i
 			break
 			break
 		}
 		}
 	}
 	}
+	if overlayIdx == -1 {
+		return
+	}
+	// set removed elements in backing array to nil to release memory references
+	for i := overlayIdx; i < len(s.overlays); i++ {
+		s.overlays[i] = nil
+		s.focusManagers[i] = nil
+	}
+	s.overlays = s.overlays[:overlayIdx]
+	s.focusManagers = s.focusManagers[:overlayIdx]
 }
 }
 
 
 // Top returns the top-most overlay of the stack.
 // Top returns the top-most overlay of the stack.

+ 28 - 5
vendor/fyne.io/fyne/v2/internal/painter/draw.go

@@ -10,6 +10,8 @@ import (
 	"golang.org/x/image/math/fixed"
 	"golang.org/x/image/math/fixed"
 )
 )
 
 
+const quarterCircleControl = 1 - 0.55228
+
 // DrawCircle rasterizes the given circle object into an image.
 // DrawCircle rasterizes the given circle object into an image.
 // The bounds of the output image will be increased by vectorPad to allow for stroke overflow at the edges.
 // The bounds of the output image will be increased by vectorPad to allow for stroke overflow at the edges.
 // The scale function is used to understand how many pixels are required per unit of size.
 // The scale function is used to understand how many pixels are required per unit of size.
@@ -98,18 +100,39 @@ func DrawRectangle(rect *canvas.Rectangle, vectorPad float32, scale func(float32
 	if rect.FillColor != nil {
 	if rect.FillColor != nil {
 		filler := rasterx.NewFiller(width, height, scanner)
 		filler := rasterx.NewFiller(width, height, scanner)
 		filler.SetColor(rect.FillColor)
 		filler.SetColor(rect.FillColor)
-		rasterx.AddRect(float64(p1x), float64(p1y), float64(p3x), float64(p3y), 0, filler)
+		if rect.CornerRadius == 0 {
+			rasterx.AddRect(float64(p1x), float64(p1y), float64(p3x), float64(p3y), 0, filler)
+		} else {
+			r := float64(scale(rect.CornerRadius))
+			rasterx.AddRoundRect(float64(p1x), float64(p1y), float64(p3x), float64(p3y), r, r, 0, rasterx.RoundGap, filler)
+		}
 		filler.Draw()
 		filler.Draw()
 	}
 	}
 
 
 	if rect.StrokeColor != nil && rect.StrokeWidth > 0 {
 	if rect.StrokeColor != nil && rect.StrokeWidth > 0 {
+		r := scale(rect.CornerRadius)
+		c := quarterCircleControl * r
 		dasher := rasterx.NewDasher(width, height, scanner)
 		dasher := rasterx.NewDasher(width, height, scanner)
 		dasher.SetColor(rect.StrokeColor)
 		dasher.SetColor(rect.StrokeColor)
 		dasher.SetStroke(fixed.Int26_6(float64(stroke)*64), 0, nil, nil, nil, 0, nil, 0)
 		dasher.SetStroke(fixed.Int26_6(float64(stroke)*64), 0, nil, nil, nil, 0, nil, 0)
-		dasher.Start(rasterx.ToFixedP(float64(p1x), float64(p1y)))
-		dasher.Line(rasterx.ToFixedP(float64(p2x), float64(p2y)))
-		dasher.Line(rasterx.ToFixedP(float64(p3x), float64(p3y)))
-		dasher.Line(rasterx.ToFixedP(float64(p4x), float64(p4y)))
+		if c != 0 {
+			dasher.Start(rasterx.ToFixedP(float64(p1x), float64(p1y+r)))
+			dasher.CubeBezier(rasterx.ToFixedP(float64(p1x), float64(p1y+c)), rasterx.ToFixedP(float64(p1x+c), float64(p1y)), rasterx.ToFixedP(float64(p1x+r), float64(p2y)))
+		} else {
+			dasher.Start(rasterx.ToFixedP(float64(p1x), float64(p1y)))
+		}
+		dasher.Line(rasterx.ToFixedP(float64(p2x-r), float64(p2y)))
+		if c != 0 {
+			dasher.CubeBezier(rasterx.ToFixedP(float64(p2x-c), float64(p2y)), rasterx.ToFixedP(float64(p2x), float64(p2y+c)), rasterx.ToFixedP(float64(p2x), float64(p2y+r)))
+		}
+		dasher.Line(rasterx.ToFixedP(float64(p3x), float64(p3y-r)))
+		if c != 0 {
+			dasher.CubeBezier(rasterx.ToFixedP(float64(p3x), float64(p3y-c)), rasterx.ToFixedP(float64(p3x-c), float64(p3y)), rasterx.ToFixedP(float64(p3x-r), float64(p3y)))
+		}
+		dasher.Line(rasterx.ToFixedP(float64(p4x+r), float64(p4y)))
+		if c != 0 {
+			dasher.CubeBezier(rasterx.ToFixedP(float64(p4x+c), float64(p4y)), rasterx.ToFixedP(float64(p4x), float64(p4y-c)), rasterx.ToFixedP(float64(p4x), float64(p4y-r)))
+		}
 		dasher.Stop(true)
 		dasher.Stop(true)
 		dasher.Draw()
 		dasher.Draw()
 	}
 	}

+ 121 - 268
vendor/fyne.io/fyne/v2/internal/painter/font.go

@@ -2,18 +2,16 @@ package painter
 
 
 import (
 import (
 	"bytes"
 	"bytes"
-	"image"
 	"image/color"
 	"image/color"
 	"image/draw"
 	"image/draw"
 	"math"
 	"math"
+	"strings"
 	"sync"
 	"sync"
 
 
+	"github.com/go-text/render"
 	"github.com/go-text/typesetting/di"
 	"github.com/go-text/typesetting/di"
-	gotext "github.com/go-text/typesetting/font"
+	"github.com/go-text/typesetting/font"
 	"github.com/go-text/typesetting/shaping"
 	"github.com/go-text/typesetting/shaping"
-	"github.com/goki/freetype"
-	"github.com/goki/freetype/truetype"
-	"golang.org/x/image/font"
 	"golang.org/x/image/math/fixed"
 	"golang.org/x/image/math/fixed"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
@@ -25,142 +23,91 @@ const (
 	// DefaultTabWidth is the default width in spaces
 	// DefaultTabWidth is the default width in spaces
 	DefaultTabWidth = 4
 	DefaultTabWidth = 4
 
 
-	// TextDPI is a global constant that determines how text scales to interface sizes
-	TextDPI = 78
-
 	fontTabSpaceSize = 10
 	fontTabSpaceSize = 10
 )
 )
 
 
-// CachedFontFace returns a font face held in memory. These are loaded from the current theme.
-func CachedFontFace(style fyne.TextStyle, fontDP float32, texScale float32) (font.Face, gotext.Face) {
-	key := faceCacheKey{float32ToFixed266(fontDP), float32ToFixed266(texScale)}
+// CachedFontFace returns a Font face held in memory. These are loaded from the current theme.
+func CachedFontFace(style fyne.TextStyle, fontDP float32, texScale float32) *FontCacheItem {
 	val, ok := fontCache.Load(style)
 	val, ok := fontCache.Load(style)
 	if !ok {
 	if !ok {
-		var f1, f2 *truetype.Font
+		var f1, f2 font.Face
 		switch {
 		switch {
 		case style.Monospace:
 		case style.Monospace:
-			f1 = loadFont(theme.TextMonospaceFont())
-			f2 = loadFont(theme.DefaultTextMonospaceFont())
+			f1 = loadMeasureFont(theme.TextMonospaceFont())
+			f2 = loadMeasureFont(theme.DefaultTextMonospaceFont())
 		case style.Bold:
 		case style.Bold:
 			if style.Italic {
 			if style.Italic {
-				f1 = loadFont(theme.TextBoldItalicFont())
-				f2 = loadFont(theme.DefaultTextBoldItalicFont())
+				f1 = loadMeasureFont(theme.TextBoldItalicFont())
+				f2 = loadMeasureFont(theme.DefaultTextBoldItalicFont())
 			} else {
 			} else {
-				f1 = loadFont(theme.TextBoldFont())
-				f2 = loadFont(theme.DefaultTextBoldFont())
+				f1 = loadMeasureFont(theme.TextBoldFont())
+				f2 = loadMeasureFont(theme.DefaultTextBoldFont())
 			}
 			}
 		case style.Italic:
 		case style.Italic:
-			f1 = loadFont(theme.TextItalicFont())
-			f2 = loadFont(theme.DefaultTextItalicFont())
+			f1 = loadMeasureFont(theme.TextItalicFont())
+			f2 = loadMeasureFont(theme.DefaultTextItalicFont())
 		case style.Symbol:
 		case style.Symbol:
-			f2 = loadFont(theme.DefaultSymbolFont())
+			f1 = loadMeasureFont(theme.SymbolFont())
+			f2 = loadMeasureFont(theme.DefaultSymbolFont())
 		default:
 		default:
-			f1 = loadFont(theme.TextFont())
-			f2 = loadFont(theme.DefaultTextFont())
+			f1 = loadMeasureFont(theme.TextFont())
+			f2 = loadMeasureFont(theme.DefaultTextFont())
 		}
 		}
 
 
 		if f1 == nil {
 		if f1 == nil {
 			f1 = f2
 			f1 = f2
 		}
 		}
-		val = &fontCacheItem{font: f1, fallback: f2, faces: make(map[faceCacheKey]font.Face),
-			measureFaces: make(map[faceCacheKey]gotext.Face)}
-		fontCache.Store(style, val)
-	}
-
-	comp := val.(*fontCacheItem)
-	comp.facesMutex.RLock()
-	face := comp.faces[key]
-	measureFace := comp.measureFaces[key]
-	comp.facesMutex.RUnlock()
-	if face == nil {
-		var opts truetype.Options
-		opts.Size = float64(fontDP)
-		opts.DPI = float64(TextDPI * texScale)
-
-		f1 := truetype.NewFace(comp.font, &opts)
-		f2 := truetype.NewFace(comp.fallback, &opts)
-		face = newFontWithFallback(f1, f2, comp.font, comp.fallback)
-
-		switch {
-		case style.Monospace:
-			measureFace = loadMeasureFont(theme.TextMonospaceFont())
-			if measureFace == nil {
-				measureFace = loadMeasureFont(theme.DefaultTextMonospaceFont())
-			}
-		case style.Bold:
-			if style.Italic {
-				measureFace = loadMeasureFont(theme.TextBoldItalicFont())
-				if measureFace == nil {
-					measureFace = loadMeasureFont(theme.DefaultTextBoldItalicFont())
-				}
-			} else {
-				measureFace = loadMeasureFont(theme.TextBoldFont())
-				if measureFace == nil {
-					measureFace = loadMeasureFont(theme.DefaultTextBoldFont())
-				}
-			}
-		case style.Italic:
-			measureFace = loadMeasureFont(theme.TextItalicFont())
-			if measureFace == nil {
-				measureFace = loadMeasureFont(theme.DefaultTextItalicFont())
-			}
-		case style.Symbol:
-			measureFace = loadMeasureFont(theme.DefaultSymbolFont())
-		default:
-			measureFace = loadMeasureFont(theme.TextFont())
-			if measureFace == nil {
-				measureFace = loadMeasureFont(theme.DefaultTextFont())
-			}
+		faces := []font.Face{f1, f2}
+		if emoji := theme.DefaultEmojiFont(); emoji != nil {
+			faces = append(faces, loadMeasureFont(emoji))
 		}
 		}
-
-		comp.facesMutex.Lock()
-		comp.faces[key] = face
-		comp.measureFaces[key] = measureFace
-		comp.facesMutex.Unlock()
+		val = &FontCacheItem{Fonts: faces}
+		fontCache.Store(style, val)
 	}
 	}
 
 
-	return face, measureFace
+	return val.(*FontCacheItem)
 }
 }
 
 
-// ClearFontCache is used to remove cached fonts in the case that we wish to re-load font faces
+// ClearFontCache is used to remove cached fonts in the case that we wish to re-load Font faces
 func ClearFontCache() {
 func ClearFontCache() {
-	fontCache.Range(func(_, val interface{}) bool {
-		item := val.(*fontCacheItem)
-		for _, face := range item.faces {
-			if face == nil {
-				continue
-			}
-			err := face.Close()
-
-			if err != nil {
-				fyne.LogError("failed to close font face", err)
-				return false
-			}
-		}
-		return true
-	})
 
 
 	fontCache = &sync.Map{}
 	fontCache = &sync.Map{}
 }
 }
 
 
 // DrawString draws a string into an image.
 // DrawString draws a string into an image.
-func DrawString(dst draw.Image, s string, color color.Color, f font.Face, face gotext.Face, fontSize, scale float32,
-	height int, tabWidth int) {
-	src := &image.Uniform{C: color}
-	dot := freetype.Pt(0, height-f.Metrics().Descent.Ceil())
-	walkString(face, s, float32ToFixed266(fontSize), tabWidth, &dot.X, scale, func(g gotext.GID) {
-		dr, mask, maskp, _, ok := f.(truetype.IndexableFace).GlyphAtIndex(dot, truetype.Index(g))
-		if !ok {
-			dr, mask, maskp, _, ok = f.Glyph(dot, 0xfffd)
-		}
-		if ok {
-			draw.DrawMask(dst, dr, src, image.Point{}, mask, maskp, draw.Over)
+func DrawString(dst draw.Image, s string, color color.Color, f []font.Face, fontSize, scale float32, tabWidth int) {
+	r := render.Renderer{
+		FontSize: fontSize,
+		PixScale: scale,
+		Color:    color,
+	}
+
+	// TODO avoid shaping twice!
+	sh := &shaping.HarfbuzzShaper{}
+	out := sh.Shape(shaping.Input{
+		Text:     []rune(s),
+		RunStart: 0,
+		RunEnd:   len(s),
+		Face:     f[0],
+		Size:     fixed.I(int(fontSize * r.PixScale)),
+	})
+
+	advance := float32(0)
+	y := int(math.Ceil(float64(fixed266ToFloat32(out.LineBounds.Ascent))))
+	walkString(f, s, float32ToFixed266(fontSize), tabWidth, &advance, scale, func(run shaping.Output, x float32) {
+		if len(run.Glyphs) == 1 {
+			if run.Glyphs[0].GlyphID == 0 {
+				r.DrawStringAt(string([]rune{0xfffd}), dst, int(x), y, f[0])
+				return
+			}
 		}
 		}
+
+		r.DrawShapedRunAt(run, dst, int(x), y)
 	})
 	})
 }
 }
 
 
-func loadMeasureFont(data fyne.Resource) gotext.Face {
-	loaded, err := gotext.ParseTTF(bytes.NewReader(data.Content()))
+func loadMeasureFont(data fyne.Resource) font.Face {
+	loaded, err := font.ParseTTF(bytes.NewReader(data.Content()))
 	if err != nil {
 	if err != nil {
 		fyne.LogError("font load error", err)
 		fyne.LogError("font load error", err)
 		return nil
 		return nil
@@ -171,8 +118,8 @@ func loadMeasureFont(data fyne.Resource) gotext.Face {
 
 
 // MeasureString returns how far dot would advance by drawing s with f.
 // MeasureString returns how far dot would advance by drawing s with f.
 // Tabs are translated into a dot location change.
 // Tabs are translated into a dot location change.
-func MeasureString(f gotext.Face, s string, textSize float32, tabWidth int) (size fyne.Size, advance fixed.Int26_6) {
-	return walkString(f, s, float32ToFixed266(textSize), tabWidth, &advance, 1, func(gotext.GID) {})
+func MeasureString(f []font.Face, s string, textSize float32, tabWidth int) (size fyne.Size, advance float32) {
+	return walkString(f, s, float32ToFixed266(textSize), tabWidth, &advance, 1, func(shaping.Output, float32) {})
 }
 }
 
 
 // RenderedTextSize looks up how big a string would be if drawn on screen.
 // RenderedTextSize looks up how big a string would be if drawn on screen.
@@ -196,206 +143,112 @@ func float32ToFixed266(f float32) fixed.Int26_6 {
 	return fixed.Int26_6(float64(f) * (1 << 6))
 	return fixed.Int26_6(float64(f) * (1 << 6))
 }
 }
 
 
-func loadFont(data fyne.Resource) *truetype.Font {
-	loaded, err := truetype.Parse(data.Content())
-	if err != nil {
-		fyne.LogError("font load error", err)
-	}
-
-	return loaded
-}
-
 func measureText(text string, fontSize float32, style fyne.TextStyle) (fyne.Size, float32) {
 func measureText(text string, fontSize float32, style fyne.TextStyle) (fyne.Size, float32) {
-	_, face := CachedFontFace(style, fontSize, 1)
-	size, base := MeasureString(face, text, fontSize, style.TabWidth)
-	return size, fixed266ToFloat32(base)
-}
-
-func newFontWithFallback(chosen, fallback font.Face, chosenFont, fallbackFont ttfFont) font.Face {
-	return &compositeFace{chosen: chosen, fallback: fallback, chosenFont: chosenFont, fallbackFont: fallbackFont}
+	face := CachedFontFace(style, fontSize, 1)
+	return MeasureString(face.Fonts, text, fontSize, style.TabWidth)
 }
 }
 
 
-func tabStop(spacew, x fixed.Int26_6, tabWidth int) fixed.Int26_6 {
+func tabStop(spacew, x float32, tabWidth int) float32 {
 	if tabWidth <= 0 {
 	if tabWidth <= 0 {
 		tabWidth = DefaultTabWidth
 		tabWidth = DefaultTabWidth
 	}
 	}
 
 
-	tabw := spacew * fixed.Int26_6(tabWidth)
+	tabw := spacew * float32(tabWidth)
 	tabs, _ := math.Modf(float64((x + tabw) / tabw))
 	tabs, _ := math.Modf(float64((x + tabw) / tabw))
-	return tabw * fixed.Int26_6(tabs)
+	return tabw * float32(tabs)
 }
 }
 
 
-func walkString(f gotext.Face, s string, textSize fixed.Int26_6, tabWidth int, advance *fixed.Int26_6, scale float32, cb func(g gotext.GID)) (size fyne.Size, base fixed.Int26_6) {
+func walkString(faces []font.Face, s string, textSize fixed.Int26_6, tabWidth int, advance *float32, scale float32,
+	cb func(run shaping.Output, x float32)) (size fyne.Size, base float32) {
+	s = strings.ReplaceAll(s, "\r", "")
+
 	runes := []rune(s)
 	runes := []rune(s)
 	in := shaping.Input{
 	in := shaping.Input{
 		Text:      []rune{' '},
 		Text:      []rune{' '},
 		RunStart:  0,
 		RunStart:  0,
 		RunEnd:    1,
 		RunEnd:    1,
 		Direction: di.DirectionLTR,
 		Direction: di.DirectionLTR,
-		Face:      f,
+		Face:      faces[0],
 		Size:      textSize,
 		Size:      textSize,
 	}
 	}
 	shaper := &shaping.HarfbuzzShaper{}
 	shaper := &shaping.HarfbuzzShaper{}
 	out := shaper.Shape(in)
 	out := shaper.Shape(in)
-	spacew := float32ToFixed266(scale) * fontTabSpaceSize
 
 
 	in.Text = runes
 	in.Text = runes
 	in.RunStart = 0
 	in.RunStart = 0
 	in.RunEnd = len(runes)
 	in.RunEnd = len(runes)
 
 
-	ins := shaping.SplitByFontGlyphs(in, []gotext.Face{f}) // TODO provide fallback...
+	x := float32(0)
+	spacew := scale * fontTabSpaceSize
+	ins := shaping.SplitByFontGlyphs(in, faces)
 	for _, in := range ins {
 	for _, in := range ins {
-		out = shaper.Shape(in)
-
-		var c rune
-		nextRuneIndex := 0
-		last := -1
-		for _, g := range out.Glyphs {
-			if g.ClusterIndex != last {
-				c = in.Text[nextRuneIndex]
-				nextRuneIndex += g.RuneCount
-				last = g.ClusterIndex
-			}
+		inEnd := in.RunEnd
+
+		pending := false
+		for i, r := range in.Text[in.RunStart:in.RunEnd] {
+			if r == '\t' {
+				if pending {
+					in.RunEnd = i
+					out = shaper.Shape(in)
+					x = shapeCallback(shaper, in, out, x, scale, cb)
+				}
+				x = tabStop(spacew, x, tabWidth)
 
 
-			if c == '\r' {
-				continue
-			}
-			if c == '\t' {
-				*advance = tabStop(spacew, *advance, tabWidth)
+				in.RunStart = i + 1
+				in.RunEnd = inEnd
+				pending = false
 			} else {
 			} else {
-				cb(g.GlyphID)
-				*advance += float32ToFixed266(fixed266ToFloat32(g.XAdvance) * scale)
+				pending = true
 			}
 			}
 		}
 		}
-	}
 
 
-	return fyne.NewSize(fixed266ToFloat32(*advance), fixed266ToFloat32(out.LineBounds.LineHeight())),
-		out.LineBounds.Ascent
-}
-
-var _ truetype.IndexableFace = (*compositeFace)(nil)
-
-type compositeFace struct {
-	sync.Mutex
-
-	chosen, fallback         font.Face
-	chosenFont, fallbackFont ttfFont
-}
-
-func (c *compositeFace) Close() (err error) {
-	c.Lock()
-	defer c.Unlock()
-
-	if c.chosen != nil {
-		err = c.chosen.Close()
-	}
-
-	err2 := c.fallback.Close()
-	if err2 != nil {
-		return err2
-	}
-
-	return
-}
-
-func (c *compositeFace) Glyph(dot fixed.Point26_6, r rune) (
-	dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
-	c.Lock()
-	defer c.Unlock()
-
-	if c.containsGlyph(c.chosenFont, r) {
-		return c.chosen.Glyph(dot, r)
-	}
-
-	if c.containsGlyph(c.fallbackFont, r) {
-		return c.fallback.Glyph(dot, r)
-	}
-
-	return
-}
-
-func (c *compositeFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
-	c.Lock()
-	defer c.Unlock()
-
-	if c.containsGlyph(c.chosenFont, r) {
-		return c.chosen.GlyphAdvance(r)
-	}
-
-	if c.containsGlyph(c.fallbackFont, r) {
-		return c.fallback.GlyphAdvance(r)
+		x = shapeCallback(shaper, in, out, x, scale, cb)
 	}
 	}
 
 
-	return
+	*advance = x
+	return fyne.NewSize(*advance, fixed266ToFloat32(out.LineBounds.LineHeight())),
+		fixed266ToFloat32(out.LineBounds.Ascent)
 }
 }
 
 
-func (c *compositeFace) GlyphAtIndex(dot fixed.Point26_6, g truetype.Index) (dr image.Rectangle, mask image.Image, maskp image.Point,
-	advance fixed.Int26_6, ok bool) {
-	if g == 0 {
-		return image.Rectangle{}, nil, image.Point{}, 0, false
-	}
-
-	c.Lock()
-	defer c.Unlock()
-
-	dr, mask, maskp, advance, ok = c.chosen.(truetype.IndexableFace).GlyphAtIndex(dot, g)
-	if ok {
-		return
-	}
-
-	return c.fallback.(truetype.IndexableFace).GlyphAtIndex(dot, g)
-}
+func shapeCallback(shaper shaping.Shaper, in shaping.Input, out shaping.Output, x, scale float32, cb func(shaping.Output, float32)) float32 {
+	out = shaper.Shape(in)
+	glyphs := out.Glyphs
+	start := 0
+	pending := false
+	adv := fixed.I(0)
+	for i, g := range out.Glyphs {
+		if g.GlyphID == 0 {
+			if pending {
+				out.Glyphs = glyphs[start:i]
+				cb(out, x)
+				x += fixed266ToFloat32(adv) * scale
+				adv = 0
+			}
 
 
-func (c *compositeFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
-	c.Lock()
-	defer c.Unlock()
+			out.Glyphs = glyphs[i : i+1]
+			cb(out, x)
+			x += fixed266ToFloat32(glyphs[i].XAdvance) * scale
+			adv = 0
 
 
-	if c.containsGlyph(c.chosenFont, r) {
-		return c.chosen.GlyphBounds(r)
+			start = i + 1
+			pending = false
+		} else {
+			pending = true
+		}
+		adv += g.XAdvance
 	}
 	}
 
 
-	if c.containsGlyph(c.fallbackFont, r) {
-		return c.fallback.GlyphBounds(r)
+	if pending {
+		out.Glyphs = glyphs[start:]
+		cb(out, x)
+		x += fixed266ToFloat32(adv) * scale
+		adv = 0
 	}
 	}
-
-	return
-}
-
-func (c *compositeFace) Kern(r0, r1 rune) fixed.Int26_6 {
-	c.Lock()
-	defer c.Unlock()
-
-	if c.containsGlyph(c.chosenFont, r0) && c.containsGlyph(c.chosenFont, r1) {
-		return c.chosen.Kern(r0, r1)
-	}
-
-	return c.fallback.Kern(r0, r1)
-}
-
-func (c *compositeFace) Metrics() font.Metrics {
-	c.Lock()
-	defer c.Unlock()
-
-	return c.chosen.Metrics()
-}
-
-func (c *compositeFace) containsGlyph(font ttfFont, r rune) bool {
-	return font != nil && font.Index(r) != 0
-}
-
-type ttfFont interface {
-	Index(rune) truetype.Index
-}
-
-type faceCacheKey struct {
-	size, scale fixed.Int26_6
+	return x + fixed266ToFloat32(adv)*scale
 }
 }
 
 
-type fontCacheItem struct {
-	font, fallback *truetype.Font
-	faces          map[faceCacheKey]font.Face
-	measureFaces   map[faceCacheKey]gotext.Face
-	facesMutex     sync.RWMutex
+type FontCacheItem struct {
+	Fonts []font.Face
 }
 }
 
 
-var fontCache = &sync.Map{} // map[fyne.TextStyle]*fontCacheItem
+var fontCache = &sync.Map{} // map[fyne.TextStyle]*FontCacheItem

+ 1 - 1
vendor/fyne.io/fyne/v2/internal/painter/gl/context.go

@@ -29,7 +29,6 @@ type context interface {
 	GetShaderInfoLog(shader Shader) string
 	GetShaderInfoLog(shader Shader) string
 	GetUniformLocation(program Program, name string) Uniform
 	GetUniformLocation(program Program, name string) Uniform
 	LinkProgram(program Program)
 	LinkProgram(program Program)
-	PixelStorei(pname uint32, param int32)
 	ReadBuffer(src uint32)
 	ReadBuffer(src uint32)
 	ReadPixels(x, y, width, height int, colorFormat, typ uint32, pixels []uint8)
 	ReadPixels(x, y, width, height int, colorFormat, typ uint32, pixels []uint8)
 	Scissor(x, y, w, h int32)
 	Scissor(x, y, w, h int32)
@@ -37,6 +36,7 @@ type context interface {
 	TexImage2D(target uint32, level, width, height int, colorFormat, typ uint32, data []uint8)
 	TexImage2D(target uint32, level, width, height int, colorFormat, typ uint32, data []uint8)
 	TexParameteri(target, param uint32, value int32)
 	TexParameteri(target, param uint32, value int32)
 	Uniform1f(uniform Uniform, v float32)
 	Uniform1f(uniform Uniform, v float32)
+	Uniform2f(uniform Uniform, v0, v1 float32)
 	Uniform4f(uniform Uniform, v0, v1, v2, v3 float32)
 	Uniform4f(uniform Uniform, v0, v1, v2, v3 float32)
 	UseProgram(program Program)
 	UseProgram(program Program)
 	VertexAttribPointerWithOffset(attribute Attribute, size int, typ uint32, normalized bool, stride, offset int)
 	VertexAttribPointerWithOffset(attribute Attribute, size int, typ uint32, normalized bool, stride, offset int)

+ 118 - 48
vendor/fyne.io/fyne/v2/internal/painter/gl/draw.go

@@ -7,7 +7,6 @@ import (
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/canvas"
 	paint "fyne.io/fyne/v2/internal/painter"
 	paint "fyne.io/fyne/v2/internal/painter"
-	"fyne.io/fyne/v2/theme"
 )
 )
 
 
 func (p *painter) createBuffer(points []float32) Buffer {
 func (p *painter) createBuffer(points []float32) Buffer {
@@ -44,7 +43,6 @@ func (p *painter) drawLine(line *canvas.Line, pos fyne.Position, frame fyne.Size
 	if line.StrokeColor == color.Transparent || line.StrokeColor == nil || line.StrokeWidth == 0 {
 	if line.StrokeColor == color.Transparent || line.StrokeColor == nil || line.StrokeWidth == 0 {
 		return
 		return
 	}
 	}
-
 	points, halfWidth, feather := p.lineCoords(pos, line.Position1, line.Position2, line.StrokeWidth, 0.5, frame)
 	points, halfWidth, feather := p.lineCoords(pos, line.Position1, line.Position2, line.StrokeWidth, 0.5, frame)
 	p.ctx.UseProgram(p.lineProgram)
 	p.ctx.UseProgram(p.lineProgram)
 	vbo := p.createBuffer(points)
 	vbo := p.createBuffer(points)
@@ -55,13 +53,9 @@ func (p *painter) drawLine(line *canvas.Line, pos fyne.Position, frame fyne.Size
 	p.logError()
 	p.logError()
 
 
 	colorUniform := p.ctx.GetUniformLocation(p.lineProgram, "color")
 	colorUniform := p.ctx.GetUniformLocation(p.lineProgram, "color")
-	r, g, b, a := line.StrokeColor.RGBA()
-	if a == 0 {
-		p.ctx.Uniform4f(colorUniform, 0, 0, 0, 0)
-	} else {
-		alpha := float32(a)
-		p.ctx.Uniform4f(colorUniform, float32(r)/alpha, float32(g)/alpha, float32(b)/alpha, alpha/0xffff)
-	}
+	r, g, b, a := getFragmentColor(line.StrokeColor)
+	p.ctx.Uniform4f(colorUniform, r, g, b, a)
+
 	lineWidthUniform := p.ctx.GetUniformLocation(p.lineProgram, "lineWidth")
 	lineWidthUniform := p.ctx.GetUniformLocation(p.lineProgram, "lineWidth")
 	p.ctx.Uniform1f(lineWidthUniform, halfWidth)
 	p.ctx.Uniform1f(lineWidthUniform, halfWidth)
 
 
@@ -102,8 +96,71 @@ func (p *painter) drawRectangle(rect *canvas.Rectangle, pos fyne.Position, frame
 	if (rect.FillColor == color.Transparent || rect.FillColor == nil) && (rect.StrokeColor == color.Transparent || rect.StrokeColor == nil || rect.StrokeWidth == 0) {
 	if (rect.FillColor == color.Transparent || rect.FillColor == nil) && (rect.StrokeColor == color.Transparent || rect.StrokeColor == nil || rect.StrokeWidth == 0) {
 		return
 		return
 	}
 	}
-	p.drawTextureWithDetails(rect, p.newGlRectTexture, pos, rect.Size(), frame, canvas.ImageFillStretch,
-		1.0, paint.VectorPad(rect))
+
+	roundedCorners := rect.CornerRadius != 0
+	var program Program
+	if roundedCorners {
+		program = p.roundRectangleProgram
+	} else {
+		program = p.rectangleProgram
+	}
+
+	// Vertex: BEG
+	bounds, points := p.vecRectCoords(pos, rect, frame)
+	p.ctx.UseProgram(program)
+	vbo := p.createBuffer(points)
+	p.defineVertexArray(program, "vert", 2, 4, 0)
+	p.defineVertexArray(program, "normal", 2, 4, 2)
+
+	p.ctx.BlendFunc(srcAlpha, oneMinusSrcAlpha)
+	p.logError()
+	// Vertex: END
+
+	// Fragment: BEG
+	frameSizeUniform := p.ctx.GetUniformLocation(program, "frame_size")
+	frameWidthScaled, frameHeightScaled := p.scaleFrameSize(frame)
+	p.ctx.Uniform2f(frameSizeUniform, frameWidthScaled, frameHeightScaled)
+
+	rectCoordsUniform := p.ctx.GetUniformLocation(program, "rect_coords")
+	x1Scaled, x2Scaled, y1Scaled, y2Scaled := p.scaleRectCoords(bounds[0], bounds[2], bounds[1], bounds[3])
+	p.ctx.Uniform4f(rectCoordsUniform, x1Scaled, x2Scaled, y1Scaled, y2Scaled)
+
+	strokeWidthScaled := roundToPixel(rect.StrokeWidth*p.pixScale, 1.0)
+	if roundedCorners {
+		strokeUniform := p.ctx.GetUniformLocation(program, "stroke_width_half")
+		p.ctx.Uniform1f(strokeUniform, strokeWidthScaled*0.5)
+
+		rectSizeUniform := p.ctx.GetUniformLocation(program, "rect_size_half")
+		rectSizeWidthScaled := x2Scaled - x1Scaled - strokeWidthScaled
+		rectSizeHeightScaled := y2Scaled - y1Scaled - strokeWidthScaled
+		p.ctx.Uniform2f(rectSizeUniform, rectSizeWidthScaled*0.5, rectSizeHeightScaled*0.5)
+
+		radiusUniform := p.ctx.GetUniformLocation(program, "radius")
+		radiusScaled := roundToPixel(rect.CornerRadius*p.pixScale, 1.0)
+		p.ctx.Uniform1f(radiusUniform, radiusScaled)
+	} else {
+		strokeUniform := p.ctx.GetUniformLocation(program, "stroke_width")
+		p.ctx.Uniform1f(strokeUniform, strokeWidthScaled)
+	}
+
+	var r, g, b, a float32
+	fillColorUniform := p.ctx.GetUniformLocation(program, "fill_color")
+	r, g, b, a = getFragmentColor(rect.FillColor)
+	p.ctx.Uniform4f(fillColorUniform, r, g, b, a)
+
+	strokeColorUniform := p.ctx.GetUniformLocation(program, "stroke_color")
+	strokeColor := rect.StrokeColor
+	if strokeColor == nil {
+		strokeColor = color.Transparent
+	}
+	r, g, b, a = getFragmentColor(strokeColor)
+	p.ctx.Uniform4f(strokeColorUniform, r, g, b, a)
+	p.logError()
+	// Fragment: END
+
+	p.ctx.DrawArrays(triangleStrip, 0, 4)
+	p.logError()
+	p.freeBuffer(vbo)
 }
 }
 
 
 func (p *painter) drawText(text *canvas.Text, pos fyne.Position, frame fyne.Size) {
 func (p *painter) drawText(text *canvas.Text, pos fyne.Position, frame fyne.Size) {
@@ -124,45 +181,10 @@ func (p *painter) drawText(text *canvas.Text, pos fyne.Position, frame fyne.Size
 		pos = fyne.NewPos(pos.X, pos.Y+(containerSize.Height-size.Height)/2)
 		pos = fyne.NewPos(pos.X, pos.Y+(containerSize.Height-size.Height)/2)
 	}
 	}
 
 
-	color := text.Color
-	if color == nil {
-		color = theme.ForegroundColor()
-	}
-
 	// text size is sensitive to position on screen
 	// text size is sensitive to position on screen
 	size, _ = roundToPixelCoords(size, text.Position(), p.pixScale)
 	size, _ = roundToPixelCoords(size, text.Position(), p.pixScale)
 	size.Width += roundToPixel(paint.VectorPad(text), p.pixScale)
 	size.Width += roundToPixel(paint.VectorPad(text), p.pixScale)
-	p.drawSingleChannelTexture(text, p.newGlTextTexture, pos, size, frame, color, 0)
-}
-
-func (p *painter) drawSingleChannelTexture(o fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture,
-	pos fyne.Position, size, frame fyne.Size, c color.Color, pad float32) {
-	texture, err := p.getTexture(o, creator)
-	if err != nil {
-		return
-	}
-
-	points := p.rectCoords(size, pos, frame, canvas.ImageFillStretch, 0, pad)
-	p.ctx.UseProgram(p.singleChannelProgram)
-	vbo := p.createBuffer(points)
-	p.defineVertexArray(p.singleChannelProgram, "vert", 3, 5, 0)
-	p.defineVertexArray(p.singleChannelProgram, "vertTexCoord", 2, 5, 3)
-
-	p.ctx.BlendFunc(srcAlpha, oneMinusSrcAlpha)
-	p.logError()
-
-	shaderColor := p.ctx.GetUniformLocation(p.singleChannelProgram, "color")
-	r, g, b, a := getFragmentColor(c)
-	p.ctx.Uniform4f(shaderColor, r, g, b, a)
-
-	p.ctx.ActiveTexture(texture0)
-	p.ctx.BindTexture(texture2D, texture)
-	p.logError()
-
-	p.ctx.DrawArrays(triangleStrip, 0, 4)
-	p.logError()
-	p.freeBuffer(vbo)
-
+	p.drawTextureWithDetails(text, p.newGlTextTexture, pos, size, frame, canvas.ImageFillStretch, 1.0, 0)
 }
 }
 
 
 func (p *painter) drawTextureWithDetails(o fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture,
 func (p *painter) drawTextureWithDetails(o fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture,
@@ -175,7 +197,7 @@ func (p *painter) drawTextureWithDetails(o fyne.CanvasObject, creator func(canva
 
 
 	aspect := float32(0)
 	aspect := float32(0)
 	if img, ok := o.(*canvas.Image); ok {
 	if img, ok := o.(*canvas.Image); ok {
-		aspect = paint.GetAspect(img)
+		aspect = img.Aspect()
 		if aspect == 0 {
 		if aspect == 0 {
 			aspect = 1 // fallback, should not occur - normally an image load error
 			aspect = 1 // fallback, should not occur - normally an image load error
 		}
 		}
@@ -315,6 +337,36 @@ func rectInnerCoords(size fyne.Size, pos fyne.Position, fill canvas.ImageFill, a
 	return size, pos
 	return size, pos
 }
 }
 
 
+func (p *painter) vecRectCoords(pos fyne.Position, rect *canvas.Rectangle, frame fyne.Size) ([4]float32, []float32) {
+	size := rect.Size()
+	pos1 := rect.Position()
+
+	xPosDiff := pos.X - pos1.X
+	yPosDiff := pos.Y - pos1.Y
+	pos1.X = roundToPixel(pos1.X+xPosDiff, p.pixScale)
+	pos1.Y = roundToPixel(pos1.Y+yPosDiff, p.pixScale)
+	size.Width = roundToPixel(size.Width, p.pixScale)
+	size.Height = roundToPixel(size.Height, p.pixScale)
+
+	x1Pos := pos1.X
+	x1Norm := -1 + x1Pos*2/frame.Width
+	x2Pos := pos1.X + size.Width
+	x2Norm := -1 + x2Pos*2/frame.Width
+	y1Pos := pos1.Y
+	y1Norm := 1 - y1Pos*2/frame.Height
+	y2Pos := pos1.Y + size.Height
+	y2Norm := 1 - y2Pos*2/frame.Height
+
+	// output a norm for the fill and the vert is unused, but we pass 0 to avoid optimisation issues
+	coords := []float32{
+		0, 0, x1Norm, y1Norm, // first triangle
+		0, 0, x2Norm, y1Norm, // second triangle
+		0, 0, x1Norm, y2Norm,
+		0, 0, x2Norm, y2Norm}
+
+	return [4]float32{x1Pos, y1Pos, x2Pos, y2Pos}, coords
+}
+
 func roundToPixel(v float32, pixScale float32) float32 {
 func roundToPixel(v float32, pixScale float32) float32 {
 	if pixScale == 1.0 {
 	if pixScale == 1.0 {
 		return float32(math.Round(float64(v)))
 		return float32(math.Round(float64(v)))
@@ -337,6 +389,9 @@ func roundToPixelCoords(size fyne.Size, pos fyne.Position, pixScale float32) (fy
 
 
 // Returns FragmentColor(red,green,blue,alpha) from fyne.Color
 // Returns FragmentColor(red,green,blue,alpha) from fyne.Color
 func getFragmentColor(col color.Color) (float32, float32, float32, float32) {
 func getFragmentColor(col color.Color) (float32, float32, float32, float32) {
+	if col == nil {
+		return 0, 0, 0, 0
+	}
 	r, g, b, a := col.RGBA()
 	r, g, b, a := col.RGBA()
 	if a == 0 {
 	if a == 0 {
 		return 0, 0, 0, 0
 		return 0, 0, 0, 0
@@ -344,3 +399,18 @@ func getFragmentColor(col color.Color) (float32, float32, float32, float32) {
 	alpha := float32(a)
 	alpha := float32(a)
 	return float32(r) / alpha, float32(g) / alpha, float32(b) / alpha, alpha / 0xffff
 	return float32(r) / alpha, float32(g) / alpha, float32(b) / alpha, alpha / 0xffff
 }
 }
+
+func (p *painter) scaleFrameSize(frame fyne.Size) (float32, float32) {
+	frameWidthScaled := roundToPixel(frame.Width*p.pixScale, 1.0)
+	frameHeightScaled := roundToPixel(frame.Height*p.pixScale, 1.0)
+	return frameWidthScaled, frameHeightScaled
+}
+
+// Returns scaled RectCoords(x1,x2,y1,y2) in same order
+func (p *painter) scaleRectCoords(x1, x2, y1, y2 float32) (float32, float32, float32, float32) {
+	x1Scaled := roundToPixel(x1*p.pixScale, 1.0)
+	x2Scaled := roundToPixel(x2*p.pixScale, 1.0)
+	y1Scaled := roundToPixel(y1*p.pixScale, 1.0)
+	y2Scaled := roundToPixel(y2*p.pixScale, 1.0)
+	return x1Scaled, x2Scaled, y1Scaled, y2Scaled
+}

+ 6 - 1
vendor/fyne.io/fyne/v2/internal/painter/gl/gl.go

@@ -12,11 +12,16 @@ import (
 const floatSize = 4
 const floatSize = 4
 const max16bit = float32(255 * 255)
 const max16bit = float32(255 * 255)
 
 
-func logGLError(err uint32) {
+// logGLError logs error in the GL renderer.
+//
+// Receives a function as parameter, to lazily get the error code only when
+// needed, avoiding unneeded overhead.
+func logGLError(getError func() uint32) {
 	if fyne.CurrentApp().Settings().BuildType() != fyne.BuildDebug {
 	if fyne.CurrentApp().Settings().BuildType() != fyne.BuildDebug {
 		return
 		return
 	}
 	}
 
 
+	err := getError()
 	if err == 0 {
 	if err == 0 {
 		return
 		return
 	}
 	}

+ 0 - 9
vendor/fyne.io/fyne/v2/internal/painter/gl/gl_const_darwin.go

@@ -1,9 +0,0 @@
-package gl
-
-import (
-	"fyne.io/fyne/v2/internal/driver/mobile/gl"
-)
-
-const (
-	singleChannelColorFormat = gl.RED
-)

+ 0 - 17
vendor/fyne.io/fyne/v2/internal/painter/gl/gl_const_mobile.go

@@ -1,17 +0,0 @@
-//go:build !darwin && !js && !wasm && (android || ios || mobile)
-// +build !darwin
-// +build !js
-// +build !wasm
-// +build android ios mobile
-
-package gl
-
-import (
-	"fyne.io/fyne/v2/internal/driver/mobile/gl"
-)
-
-const (
-	singleChannelColorFormat = gl.LUMINANCE
-)
-
-var _ = singleChannelColorFormat

+ 6 - 7
vendor/fyne.io/fyne/v2/internal/painter/gl/gl_core.go

@@ -17,7 +17,6 @@ const (
 	bitDepthBuffer        = gl.DEPTH_BUFFER_BIT
 	bitDepthBuffer        = gl.DEPTH_BUFFER_BIT
 	clampToEdge           = gl.CLAMP_TO_EDGE
 	clampToEdge           = gl.CLAMP_TO_EDGE
 	colorFormatRGBA       = gl.RGBA
 	colorFormatRGBA       = gl.RGBA
-	colorFormatR          = gl.RED
 	compileStatus         = gl.COMPILE_STATUS
 	compileStatus         = gl.COMPILE_STATUS
 	constantAlpha         = gl.CONSTANT_ALPHA
 	constantAlpha         = gl.CONSTANT_ALPHA
 	float                 = gl.FLOAT
 	float                 = gl.FLOAT
@@ -39,7 +38,6 @@ const (
 	textureWrapT          = gl.TEXTURE_WRAP_T
 	textureWrapT          = gl.TEXTURE_WRAP_T
 	triangles             = gl.TRIANGLES
 	triangles             = gl.TRIANGLES
 	triangleStrip         = gl.TRIANGLE_STRIP
 	triangleStrip         = gl.TRIANGLE_STRIP
-	unpackAlignment       = gl.UNPACK_ALIGNMENT
 	unsignedByte          = gl.UNSIGNED_BYTE
 	unsignedByte          = gl.UNSIGNED_BYTE
 	vertexShader          = gl.VERTEX_SHADER
 	vertexShader          = gl.VERTEX_SHADER
 )
 )
@@ -74,8 +72,9 @@ func (p *painter) Init() {
 	gl.Enable(gl.BLEND)
 	gl.Enable(gl.BLEND)
 	p.logError()
 	p.logError()
 	p.program = p.createProgram("simple")
 	p.program = p.createProgram("simple")
-	p.singleChannelProgram = p.createProgram("single_channel")
 	p.lineProgram = p.createProgram("line")
 	p.lineProgram = p.createProgram("line")
+	p.rectangleProgram = p.createProgram("rectangle")
+	p.roundRectangleProgram = p.createProgram("round_rectangle")
 }
 }
 
 
 type coreContext struct{}
 type coreContext struct{}
@@ -211,10 +210,6 @@ func (c *coreContext) LinkProgram(program Program) {
 	gl.LinkProgram(uint32(program))
 	gl.LinkProgram(uint32(program))
 }
 }
 
 
-func (c *coreContext) PixelStorei(pname uint32, param int32) {
-	gl.PixelStorei(pname, param)
-}
-
 func (c *coreContext) ReadBuffer(src uint32) {
 func (c *coreContext) ReadBuffer(src uint32) {
 	gl.ReadBuffer(src)
 	gl.ReadBuffer(src)
 }
 }
@@ -255,6 +250,10 @@ func (c *coreContext) Uniform1f(uniform Uniform, v float32) {
 	gl.Uniform1f(int32(uniform), v)
 	gl.Uniform1f(int32(uniform), v)
 }
 }
 
 
+func (c *coreContext) Uniform2f(uniform Uniform, v0, v1 float32) {
+	gl.Uniform2f(int32(uniform), v0, v1)
+}
+
 func (c *coreContext) Uniform4f(uniform Uniform, v0, v1, v2, v3 float32) {
 func (c *coreContext) Uniform4f(uniform Uniform, v0, v1, v2, v3 float32) {
 	gl.Uniform4f(int32(uniform), v0, v1, v2, v3)
 	gl.Uniform4f(int32(uniform), v0, v1, v2, v3)
 }
 }

+ 6 - 7
vendor/fyne.io/fyne/v2/internal/painter/gl/gl_es.go

@@ -24,7 +24,6 @@ const (
 	bitDepthBuffer        = gl.DEPTH_BUFFER_BIT
 	bitDepthBuffer        = gl.DEPTH_BUFFER_BIT
 	clampToEdge           = gl.CLAMP_TO_EDGE
 	clampToEdge           = gl.CLAMP_TO_EDGE
 	colorFormatRGBA       = gl.RGBA
 	colorFormatRGBA       = gl.RGBA
-	colorFormatR          = gl.LUMINANCE
 	compileStatus         = gl.COMPILE_STATUS
 	compileStatus         = gl.COMPILE_STATUS
 	constantAlpha         = gl.CONSTANT_ALPHA
 	constantAlpha         = gl.CONSTANT_ALPHA
 	float                 = gl.FLOAT
 	float                 = gl.FLOAT
@@ -46,7 +45,6 @@ const (
 	textureWrapT          = gl.TEXTURE_WRAP_T
 	textureWrapT          = gl.TEXTURE_WRAP_T
 	triangles             = gl.TRIANGLES
 	triangles             = gl.TRIANGLES
 	triangleStrip         = gl.TRIANGLE_STRIP
 	triangleStrip         = gl.TRIANGLE_STRIP
-	unpackAlignment       = gl.UNPACK_ALIGNMENT
 	unsignedByte          = gl.UNSIGNED_BYTE
 	unsignedByte          = gl.UNSIGNED_BYTE
 	vertexShader          = gl.VERTEX_SHADER
 	vertexShader          = gl.VERTEX_SHADER
 )
 )
@@ -81,8 +79,9 @@ func (p *painter) Init() {
 	gl.Enable(gl.BLEND)
 	gl.Enable(gl.BLEND)
 	p.logError()
 	p.logError()
 	p.program = p.createProgram("simple_es")
 	p.program = p.createProgram("simple_es")
-	p.singleChannelProgram = p.createProgram("single_channel_es")
 	p.lineProgram = p.createProgram("line_es")
 	p.lineProgram = p.createProgram("line_es")
+	p.rectangleProgram = p.createProgram("rectangle_es")
+	p.roundRectangleProgram = p.createProgram("round_rectangle_es")
 }
 }
 
 
 type esContext struct{}
 type esContext struct{}
@@ -218,10 +217,6 @@ func (c *esContext) LinkProgram(program Program) {
 	gl.LinkProgram(uint32(program))
 	gl.LinkProgram(uint32(program))
 }
 }
 
 
-func (c *esContext) PixelStorei(pname uint32, param int32) {
-	gl.PixelStorei(pname, param)
-}
-
 func (c *esContext) ReadBuffer(src uint32) {
 func (c *esContext) ReadBuffer(src uint32) {
 	gl.ReadBuffer(src)
 	gl.ReadBuffer(src)
 }
 }
@@ -262,6 +257,10 @@ func (c *esContext) Uniform1f(uniform Uniform, v float32) {
 	gl.Uniform1f(int32(uniform), v)
 	gl.Uniform1f(int32(uniform), v)
 }
 }
 
 
+func (c *esContext) Uniform2f(uniform Uniform, v0, v1 float32) {
+	gl.Uniform2f(int32(uniform), v0, v1)
+}
+
 func (c *esContext) Uniform4f(uniform Uniform, v0, v1, v2, v3 float32) {
 func (c *esContext) Uniform4f(uniform Uniform, v0, v1, v2, v3 float32) {
 	gl.Uniform4f(int32(uniform), v0, v1, v2, v3)
 	gl.Uniform4f(int32(uniform), v0, v1, v2, v3)
 }
 }

+ 16 - 9
vendor/fyne.io/fyne/v2/internal/painter/gl/gl_gomobile.go

@@ -18,7 +18,6 @@ const (
 	bitDepthBuffer        = gl.DepthBufferBit
 	bitDepthBuffer        = gl.DepthBufferBit
 	clampToEdge           = gl.ClampToEdge
 	clampToEdge           = gl.ClampToEdge
 	colorFormatRGBA       = gl.RGBA
 	colorFormatRGBA       = gl.RGBA
-	colorFormatR          = singleChannelColorFormat
 	compileStatus         = gl.CompileStatus
 	compileStatus         = gl.CompileStatus
 	constantAlpha         = gl.ConstantAlpha
 	constantAlpha         = gl.ConstantAlpha
 	float                 = gl.Float
 	float                 = gl.Float
@@ -40,7 +39,6 @@ const (
 	textureWrapT          = gl.TextureWrapT
 	textureWrapT          = gl.TextureWrapT
 	triangles             = gl.Triangles
 	triangles             = gl.Triangles
 	triangleStrip         = gl.TriangleStrip
 	triangleStrip         = gl.TriangleStrip
-	unpackAlignment       = gl.UnpackAlignment
 	unsignedByte          = gl.UnsignedByte
 	unsignedByte          = gl.UnsignedByte
 	vertexShader          = gl.VertexShader
 	vertexShader          = gl.VertexShader
 )
 )
@@ -58,6 +56,7 @@ type (
 	Uniform gl.Uniform
 	Uniform gl.Uniform
 )
 )
 
 
+var compiled []Program // avoid multiple compilations with the re-used mobile GUI context
 var noBuffer = Buffer{}
 var noBuffer = Buffer{}
 var noShader = Shader{}
 var noShader = Shader{}
 var textureFilterToGL = []int32{gl.Linear, gl.Nearest}
 var textureFilterToGL = []int32{gl.Linear, gl.Nearest}
@@ -70,9 +69,17 @@ func (p *painter) Init() {
 	p.ctx = &mobileContext{glContext: p.contextProvider.Context().(gl.Context)}
 	p.ctx = &mobileContext{glContext: p.contextProvider.Context().(gl.Context)}
 	p.glctx().Disable(gl.DepthTest)
 	p.glctx().Disable(gl.DepthTest)
 	p.glctx().Enable(gl.Blend)
 	p.glctx().Enable(gl.Blend)
-	p.program = p.createProgram("simple_es")
-	p.singleChannelProgram = p.createProgram("single_channel_es")
-	p.lineProgram = p.createProgram("line_es")
+	if compiled == nil {
+		compiled = []Program{
+			p.createProgram("simple_es"),
+			p.createProgram("line_es"),
+			p.createProgram("rectangle_es"),
+			p.createProgram("round_rectangle_es")}
+	}
+	p.program = compiled[0]
+	p.lineProgram = compiled[1]
+	p.rectangleProgram = compiled[2]
+	p.roundRectangleProgram = compiled[3]
 }
 }
 
 
 // f32Bytes returns the byte representation of float32 values in the given byte
 // f32Bytes returns the byte representation of float32 values in the given byte
@@ -224,10 +231,6 @@ func (c *mobileContext) LinkProgram(program Program) {
 	c.glContext.LinkProgram(gl.Program(program))
 	c.glContext.LinkProgram(gl.Program(program))
 }
 }
 
 
-func (c *mobileContext) PixelStorei(pname uint32, param int32) {
-	c.glContext.PixelStorei(gl.Enum(pname), param)
-}
-
 func (c *mobileContext) ReadBuffer(_ uint32) {
 func (c *mobileContext) ReadBuffer(_ uint32) {
 }
 }
 
 
@@ -264,6 +267,10 @@ func (c *mobileContext) Uniform1f(uniform Uniform, v float32) {
 	c.glContext.Uniform1f(gl.Uniform(uniform), v)
 	c.glContext.Uniform1f(gl.Uniform(uniform), v)
 }
 }
 
 
+func (c *mobileContext) Uniform2f(uniform Uniform, v0, v1 float32) {
+	c.glContext.Uniform2f(gl.Uniform(uniform), v0, v1)
+}
+
 func (c *mobileContext) Uniform4f(uniform Uniform, v0, v1, v2, v3 float32) {
 func (c *mobileContext) Uniform4f(uniform Uniform, v0, v1, v2, v3 float32) {
 	c.glContext.Uniform4f(gl.Uniform(uniform), v0, v1, v2, v3)
 	c.glContext.Uniform4f(gl.Uniform(uniform), v0, v1, v2, v3)
 }
 }

+ 6 - 7
vendor/fyne.io/fyne/v2/internal/painter/gl/gl_goxjs.go

@@ -16,7 +16,6 @@ const (
 	bitDepthBuffer        = gl.DEPTH_BUFFER_BIT
 	bitDepthBuffer        = gl.DEPTH_BUFFER_BIT
 	clampToEdge           = gl.CLAMP_TO_EDGE
 	clampToEdge           = gl.CLAMP_TO_EDGE
 	colorFormatRGBA       = gl.RGBA
 	colorFormatRGBA       = gl.RGBA
-	colorFormatR          = gl.LUMINANCE
 	compileStatus         = gl.COMPILE_STATUS
 	compileStatus         = gl.COMPILE_STATUS
 	constantAlpha         = gl.CONSTANT_ALPHA
 	constantAlpha         = gl.CONSTANT_ALPHA
 	float                 = gl.FLOAT
 	float                 = gl.FLOAT
@@ -38,7 +37,6 @@ const (
 	textureWrapT          = gl.TEXTURE_WRAP_T
 	textureWrapT          = gl.TEXTURE_WRAP_T
 	triangles             = gl.TRIANGLES
 	triangles             = gl.TRIANGLES
 	triangleStrip         = gl.TRIANGLE_STRIP
 	triangleStrip         = gl.TRIANGLE_STRIP
-	unpackAlignment       = gl.UNPACK_ALIGNMENT
 	unsignedByte          = gl.UNSIGNED_BYTE
 	unsignedByte          = gl.UNSIGNED_BYTE
 	vertexShader          = gl.VERTEX_SHADER
 	vertexShader          = gl.VERTEX_SHADER
 )
 )
@@ -66,8 +64,9 @@ func (p *painter) Init() {
 	gl.Enable(gl.BLEND)
 	gl.Enable(gl.BLEND)
 	p.logError()
 	p.logError()
 	p.program = p.createProgram("simple_es")
 	p.program = p.createProgram("simple_es")
-	p.singleChannelProgram = p.createProgram("single_channel_es")
 	p.lineProgram = p.createProgram("line_es")
 	p.lineProgram = p.createProgram("line_es")
+	p.rectangleProgram = p.createProgram("rectangle_es")
+	p.roundRectangleProgram = p.createProgram("round_rectangle_es")
 }
 }
 
 
 type xjsContext struct{}
 type xjsContext struct{}
@@ -186,10 +185,6 @@ func (c *xjsContext) LinkProgram(program Program) {
 	gl.LinkProgram(gl.Program(program))
 	gl.LinkProgram(gl.Program(program))
 }
 }
 
 
-func (c *xjsContext) PixelStorei(pname uint32, param int32) {
-	gl.PixelStorei(gl.Enum(pname), param)
-}
-
 func (c *xjsContext) ReadBuffer(_ uint32) {
 func (c *xjsContext) ReadBuffer(_ uint32) {
 }
 }
 
 
@@ -225,6 +220,10 @@ func (c *xjsContext) Uniform1f(uniform Uniform, v float32) {
 	gl.Uniform1f(gl.Uniform(uniform), v)
 	gl.Uniform1f(gl.Uniform(uniform), v)
 }
 }
 
 
+func (c *xjsContext) Uniform2f(uniform Uniform, v0, v1 float32) {
+	gl.Uniform2f(gl.Uniform(uniform), v0, v1)
+}
+
 func (c *xjsContext) Uniform4f(uniform Uniform, v0, v1, v2, v3 float32) {
 func (c *xjsContext) Uniform4f(uniform Uniform, v0, v1, v2, v3 float32) {
 	gl.Uniform4f(gl.Uniform(uniform), v0, v1, v2, v3)
 	gl.Uniform4f(gl.Uniform(uniform), v0, v1, v2, v3)
 }
 }

+ 18 - 14
vendor/fyne.io/fyne/v2/internal/painter/gl/painter.go

@@ -20,12 +20,15 @@ func shaderSourceNamed(name string) ([]byte, []byte) {
 		return shaderSimpleVert.StaticContent, shaderSimpleFrag.StaticContent
 		return shaderSimpleVert.StaticContent, shaderSimpleFrag.StaticContent
 	case "simple_es":
 	case "simple_es":
 		return shaderSimpleesVert.StaticContent, shaderSimpleesFrag.StaticContent
 		return shaderSimpleesVert.StaticContent, shaderSimpleesFrag.StaticContent
-	case "single_channel":
-		return shaderSimpleVert.StaticContent, shaderSinglechannelFrag.StaticContent
-	case "single_channel_es":
-		return shaderSimpleesVert.StaticContent, shaderSinglechannelesFrag.StaticContent
+	case "rectangle":
+		return shaderRectangleVert.StaticContent, shaderRectangleFrag.StaticContent
+	case "round_rectangle":
+		return shaderRectangleVert.StaticContent, shaderRoundrectangleFrag.StaticContent
+	case "rectangle_es":
+		return shaderRectangleesVert.StaticContent, shaderRectangleesFrag.StaticContent
+	case "round_rectangle_es":
+		return shaderRectangleesVert.StaticContent, shaderRoundrectangleesFrag.StaticContent
 	}
 	}
-
 	return nil, nil
 	return nil, nil
 }
 }
 
 
@@ -60,14 +63,15 @@ func NewPainter(c fyne.Canvas, ctx driver.WithContext) Painter {
 }
 }
 
 
 type painter struct {
 type painter struct {
-	canvas               fyne.Canvas
-	ctx                  context
-	contextProvider      driver.WithContext
-	program              Program
-	singleChannelProgram Program
-	lineProgram          Program
-	texScale             float32
-	pixScale             float32 // pre-calculate scale*texScale for each draw
+	canvas                fyne.Canvas
+	ctx                   context
+	contextProvider       driver.WithContext
+	program               Program
+	lineProgram           Program
+	rectangleProgram      Program
+	roundRectangleProgram Program
+	texScale              float32
+	pixScale              float32 // pre-calculate scale*texScale for each draw
 }
 }
 
 
 // Declare conformity to Painter interface
 // Declare conformity to Painter interface
@@ -179,5 +183,5 @@ func (p *painter) createProgram(shaderFilename string) Program {
 }
 }
 
 
 func (p *painter) logError() {
 func (p *painter) logError() {
-	logGLError(p.ctx.GetError())
+	logGLError(p.ctx.GetError)
 }
 }

+ 30 - 10
vendor/fyne.io/fyne/v2/internal/painter/gl/shaders.go

@@ -25,6 +25,36 @@ var shaderLineesVert = &fyne.StaticResource{
 	StaticContent: []byte(
 	StaticContent: []byte(
 		"#version 100\n\n#ifdef GL_ES\n# ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n# else\nprecision mediump float;\n#endif\nprecision mediump int;\nprecision lowp sampler2D;\n#endif\n\nattribute vec2 vert;\nattribute vec2 normal;\n    \nuniform float lineWidth;\n\nvarying vec2 delta;\n\nvoid main() {\n    delta = normal * lineWidth;\n\n    gl_Position = vec4(vert + delta, 0, 1);\n}\n"),
 		"#version 100\n\n#ifdef GL_ES\n# ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n# else\nprecision mediump float;\n#endif\nprecision mediump int;\nprecision lowp sampler2D;\n#endif\n\nattribute vec2 vert;\nattribute vec2 normal;\n    \nuniform float lineWidth;\n\nvarying vec2 delta;\n\nvoid main() {\n    delta = normal * lineWidth;\n\n    gl_Position = vec4(vert + delta, 0, 1);\n}\n"),
 }
 }
+var shaderRectangleFrag = &fyne.StaticResource{
+	StaticName: "rectangle.frag",
+	StaticContent: []byte(
+		"#version 110\n\n/* scaled params */\nuniform vec2 frame_size;\nuniform vec4 rect_coords; //x1 [0], x2 [1], y1 [2], y2 [3]; coords of the rect_frame\nuniform float stroke_width;\n/* colors params*/\nuniform vec4 fill_color;\nuniform vec4 stroke_color;\n\n\nvoid main() {\n\n    vec4 color = fill_color;\n    \n    if (gl_FragCoord.x >= rect_coords[1] - stroke_width ){\n        color = stroke_color;\n    } else if (gl_FragCoord.x <= rect_coords[0] + stroke_width){\n        color = stroke_color;\n    } else if (gl_FragCoord.y <= frame_size.y - rect_coords[3] + stroke_width ){\n        color = stroke_color;\n    } else if (gl_FragCoord.y >= frame_size.y - rect_coords[2] - stroke_width ){\n        color = stroke_color;\n    }\n\n    gl_FragColor = color;\n}\n"),
+}
+var shaderRectangleVert = &fyne.StaticResource{
+	StaticName: "rectangle.vert",
+	StaticContent: []byte(
+		"#version 110\n\nattribute vec2 vert;\nattribute vec2 normal;\n\nvoid main() {\n    gl_Position = vec4(vert+normal, 0, 1);\n}\n"),
+}
+var shaderRectangleesFrag = &fyne.StaticResource{
+	StaticName: "rectangle_es.frag",
+	StaticContent: []byte(
+		"#version 100\n\n#ifdef GL_ES\n# ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n# else\nprecision mediump float;\n#endif\nprecision mediump int;\nprecision lowp sampler2D;\n#endif\n\n/* scaled params */\nuniform vec2 frame_size;\nuniform vec4 rect_coords; //x1 [0], x2 [1], y1 [2], y2 [3]; coords of the rect_frame\nuniform float stroke_width;\n/* colors params*/\nuniform vec4 fill_color;\nuniform vec4 stroke_color;\n\n\nvoid main() {\n\n    vec4 color = fill_color;\n    \n    if (gl_FragCoord.x >= rect_coords[1] - stroke_width ){\n        color = stroke_color;\n    } else if (gl_FragCoord.x <= rect_coords[0] + stroke_width){\n        color = stroke_color;\n    } else if (gl_FragCoord.y <= frame_size.y - rect_coords[3] + stroke_width ){\n        color = stroke_color;\n    } else if (gl_FragCoord.y >= frame_size.y - rect_coords[2] - stroke_width ){\n        color = stroke_color;\n    }\n\n    gl_FragColor = color;\n}\n"),
+}
+var shaderRectangleesVert = &fyne.StaticResource{
+	StaticName: "rectangle_es.vert",
+	StaticContent: []byte(
+		"#version 100\n\n#ifdef GL_ES\n# ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n# else\nprecision mediump float;\n#endif\nprecision mediump int;\nprecision lowp sampler2D;\n#endif\n\nattribute vec2 vert;\nattribute vec2 normal;\n\nvoid main() {\n    gl_Position = vec4(vert+normal, 0, 1);\n}\n"),
+}
+var shaderRoundrectangleFrag = &fyne.StaticResource{
+	StaticName: "round_rectangle.frag",
+	StaticContent: []byte(
+		"#version 110\n\n/* scaled params */\nuniform vec2 frame_size;\nuniform vec4 rect_coords; //x1 [0], x2 [1], y1 [2], y2 [3]; coords of the rect_frame\nuniform float stroke_width_half;\nuniform vec2 rect_size_half;\nuniform float radius;\n/* colors params*/\nuniform vec4 fill_color;\nuniform vec4 stroke_color;\n\nfloat calc_distance(vec2 p, vec2 b, float r)\n{\n    vec2 d = abs(p) - b + vec2(r);\n\treturn min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;   \n}\n\nvoid main() {\n\n    vec4 frag_rect_coords = vec4(rect_coords[0], rect_coords[1], frame_size.y - rect_coords[3], frame_size.y - rect_coords[2]);\n    vec2 vec_centered_pos = (gl_FragCoord.xy - vec2(frag_rect_coords[0] + frag_rect_coords[1], frag_rect_coords[2] + frag_rect_coords[3]) * 0.5);\n\n    float distance = calc_distance(vec_centered_pos, rect_size_half, radius - stroke_width_half);\n\n    vec4 from_color = stroke_color; //Always the border color. If no border, this still should be set\n    vec4 to_color = stroke_color; //Outside color\n\n    if (stroke_width_half == 0.0)\n    {\n        from_color = fill_color;\n        to_color = fill_color;\n    }\n    to_color[3] = 0.0; // blend the fill colour to alpha\n\n    if (distance < 0.0)\n    {\n        to_color = fill_color;\n    } \n\n    distance = abs(distance) - stroke_width_half;\n\n    float blend_amount = smoothstep(-1.0, 1.0, distance);\n\n    // final color\n    gl_FragColor = mix(from_color, to_color, blend_amount);\n}\n"),
+}
+var shaderRoundrectangleesFrag = &fyne.StaticResource{
+	StaticName: "round_rectangle_es.frag",
+	StaticContent: []byte(
+		"#version 100\n\n#ifdef GL_ES\n# ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n# else\nprecision mediump float;\n#endif\nprecision mediump int;\nprecision lowp sampler2D;\n#endif\n\n/* scaled params */\nuniform vec2 frame_size;\nuniform vec4 rect_coords; //x1 [0], x2 [1], y1 [2], y2 [3]; coords of the rect_frame\nuniform float stroke_width_half;\nuniform vec2 rect_size_half;\nuniform float radius;\n/* colors params*/\nuniform vec4 fill_color;\nuniform vec4 stroke_color;\n\nfloat calc_distance(vec2 p, vec2 b, float r)\n{\n    vec2 d = abs(p) - b + vec2(r);\n\treturn min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;   \n}\n\nvoid main() {\n\n    vec4 frag_rect_coords = vec4(rect_coords[0], rect_coords[1], frame_size.y - rect_coords[3], frame_size.y - rect_coords[2]);\n    vec2 vec_centered_pos = (gl_FragCoord.xy - vec2(frag_rect_coords[0] + frag_rect_coords[1], frag_rect_coords[2] + frag_rect_coords[3]) * 0.5);\n\n    float distance = calc_distance(vec_centered_pos, rect_size_half, radius - stroke_width_half);\n\n    vec4 from_color = stroke_color; //Always the border color. If no border, this still should be set\n    vec4 to_color = stroke_color; //Outside color\n\n    if (stroke_width_half == 0.0)\n    {\n        from_color = fill_color;\n        to_color = fill_color;\n    }\n    to_color[3] = 0.0; // blend the fill colour to alpha\n\n    if (distance < 0.0)\n    {\n        to_color = fill_color;\n    } \n\n    distance = abs(distance) - stroke_width_half;\n\n    float blend_amount = smoothstep(-1.0, 1.0, distance);\n\n    // final color\n    gl_FragColor = mix(from_color, to_color, blend_amount);\n}\n"),
+}
 var shaderSimpleFrag = &fyne.StaticResource{
 var shaderSimpleFrag = &fyne.StaticResource{
 	StaticName: "simple.frag",
 	StaticName: "simple.frag",
 	StaticContent: []byte(
 	StaticContent: []byte(
@@ -45,13 +75,3 @@ var shaderSimpleesVert = &fyne.StaticResource{
 	StaticContent: []byte(
 	StaticContent: []byte(
 		"#version 100\n\n#ifdef GL_ES\n# ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n# else\nprecision mediump float;\n#endif\nprecision mediump int;\nprecision lowp sampler2D;\n#endif\n\nattribute vec3 vert;\nattribute vec2 vertTexCoord;\nvarying vec2 fragTexCoord;\n\nvoid main() {\n    fragTexCoord = vertTexCoord;\n\n    gl_Position = vec4(vert, 1);\n}"),
 		"#version 100\n\n#ifdef GL_ES\n# ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n# else\nprecision mediump float;\n#endif\nprecision mediump int;\nprecision lowp sampler2D;\n#endif\n\nattribute vec3 vert;\nattribute vec2 vertTexCoord;\nvarying vec2 fragTexCoord;\n\nvoid main() {\n    fragTexCoord = vertTexCoord;\n\n    gl_Position = vec4(vert, 1);\n}"),
 }
 }
-var shaderSinglechannelFrag = &fyne.StaticResource{
-	StaticName: "single_channel.frag",
-	StaticContent: []byte(
-		"#version 110\n\nuniform vec4 color;\n\nuniform sampler2D tex;\nvarying vec2 fragTexCoord;\n\nvoid main()\n{\n    gl_FragColor = vec4(color.r, color.g, color.b, texture2D(tex, fragTexCoord).r*color.a);\n}\n"),
-}
-var shaderSinglechannelesFrag = &fyne.StaticResource{
-	StaticName: "single_channel_es.frag",
-	StaticContent: []byte(
-		"#version 100\n\n#ifdef GL_ES\n# ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n# else\nprecision mediump float;\n#endif\nprecision mediump int;\nprecision lowp sampler2D;\n#endif\n\nuniform vec4 color;\n\nuniform sampler2D tex;\nvarying vec2 fragTexCoord;\n\nvoid main()\n{\n    gl_FragColor = vec4(color.r, color.g, color.b, texture2D(tex, fragTexCoord).r*color.a);\n}\n"),
-}

+ 8 - 41
vendor/fyne.io/fyne/v2/internal/painter/gl/texture.go

@@ -3,7 +3,6 @@ package gl
 import (
 import (
 	"fmt"
 	"fmt"
 	"image"
 	"image"
-	"image/color"
 	"image/draw"
 	"image/draw"
 	"math"
 	"math"
 
 
@@ -11,6 +10,7 @@ import (
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/internal/cache"
 	"fyne.io/fyne/v2/internal/cache"
 	paint "fyne.io/fyne/v2/internal/painter"
 	paint "fyne.io/fyne/v2/internal/painter"
+	"fyne.io/fyne/v2/theme"
 )
 )
 
 
 var noTexture = Texture(cache.NoTexture)
 var noTexture = Texture(cache.NoTexture)
@@ -77,25 +77,6 @@ func (p *painter) imgToTexture(img image.Image, textureFilter canvas.ImageScale)
 		)
 		)
 		p.logError()
 		p.logError()
 		return texture
 		return texture
-	case *image.Gray:
-		if len(i.Pix) == 0 { // image is empty
-			return noTexture
-		}
-
-		p.ctx.PixelStorei(unpackAlignment, 1) // OpenGL expects 4 byte alignment for images which is not guaranteed for image.Gray
-		texture := p.newTexture(textureFilter)
-		p.ctx.TexImage2D(
-			texture2D,
-			0,
-			i.Bounds().Dx(),
-			i.Bounds().Dy(),
-			colorFormatR,
-			unsignedByte,
-			i.Pix,
-		)
-		p.ctx.PixelStorei(unpackAlignment, 4) // Reset to default for performance reasons
-		p.logError()
-		return texture
 	default:
 	default:
 		rgba := image.NewRGBA(image.Rect(0, 0, img.Bounds().Dx(), img.Bounds().Dy()))
 		rgba := image.NewRGBA(image.Rect(0, 0, img.Bounds().Dx(), img.Bounds().Dy()))
 		draw.Draw(rgba, rgba.Rect, img, image.Point{}, draw.Over)
 		draw.Draw(rgba, rgba.Rect, img, image.Point{}, draw.Over)
@@ -159,34 +140,20 @@ func (p *painter) newGlRasterTexture(obj fyne.CanvasObject) Texture {
 	return p.imgToTexture(rast.Generator(int(width), int(height)), rast.ScaleMode)
 	return p.imgToTexture(rast.Generator(int(width), int(height)), rast.ScaleMode)
 }
 }
 
 
-func (p *painter) newGlRectTexture(obj fyne.CanvasObject) Texture {
-	rect := obj.(*canvas.Rectangle)
-	if rect.StrokeColor != nil && rect.StrokeWidth > 0 {
-		return p.newGlStrokedRectTexture(rect)
-	}
-	if rect.FillColor == nil {
-		return noTexture
-	}
-	return p.imgToTexture(image.NewUniform(rect.FillColor), canvas.ImageScaleSmooth)
-}
-
-func (p *painter) newGlStrokedRectTexture(obj fyne.CanvasObject) Texture {
-	rect := obj.(*canvas.Rectangle)
-	raw := paint.DrawRectangle(rect, paint.VectorPad(rect), p.textureScale)
-
-	return p.imgToTexture(raw, canvas.ImageScaleSmooth)
-}
-
 func (p *painter) newGlTextTexture(obj fyne.CanvasObject) Texture {
 func (p *painter) newGlTextTexture(obj fyne.CanvasObject) Texture {
 	text := obj.(*canvas.Text)
 	text := obj.(*canvas.Text)
+	color := text.Color
+	if color == nil {
+		color = theme.ForegroundColor()
+	}
 
 
 	bounds := text.MinSize()
 	bounds := text.MinSize()
 	width := int(math.Ceil(float64(p.textureScale(bounds.Width) + paint.VectorPad(text)))) // potentially italic overspill
 	width := int(math.Ceil(float64(p.textureScale(bounds.Width) + paint.VectorPad(text)))) // potentially italic overspill
 	height := int(math.Ceil(float64(p.textureScale(bounds.Height))))
 	height := int(math.Ceil(float64(p.textureScale(bounds.Height))))
-	img := image.NewGray(image.Rect(0, 0, width, height))
+	img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
 
-	face, measureFace := paint.CachedFontFace(text.TextStyle, text.TextSize*p.canvas.Scale(), p.texScale)
-	paint.DrawString(img, text.Text, color.White, face, measureFace, text.TextSize, p.pixScale, height, text.TextStyle.TabWidth)
+	face := paint.CachedFontFace(text.TextStyle, text.TextSize*p.canvas.Scale(), p.texScale)
+	paint.DrawString(img, text.Text, color, face.Fonts, text.TextSize, p.pixScale, text.TextStyle.TabWidth)
 	return p.imgToTexture(img, canvas.ImageScaleSmooth)
 	return p.imgToTexture(img, canvas.ImageScaleSmooth)
 }
 }
 
 

+ 11 - 136
vendor/fyne.io/fyne/v2/internal/painter/image.go

@@ -1,149 +1,45 @@
 package painter
 package painter
 
 
 import (
 import (
-	"bytes"
-	"fmt"
 	"image"
 	"image"
 	_ "image/jpeg" // avoid users having to import when using image widget
 	_ "image/jpeg" // avoid users having to import when using image widget
 	_ "image/png"  // avoid the same for PNG images
 	_ "image/png"  // avoid the same for PNG images
-	"io"
-	"os"
-	"path/filepath"
-	"strings"
 
 
 	"golang.org/x/image/draw"
 	"golang.org/x/image/draw"
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/canvas"
-	"fyne.io/fyne/v2/internal"
-	"fyne.io/fyne/v2/internal/cache"
-	"fyne.io/fyne/v2/internal/svg"
 )
 )
 
 
-var aspects = make(map[interface{}]float32, 16)
-
-// GetAspect looks up an aspect ratio of an image
-func GetAspect(img *canvas.Image) float32 {
-	aspect := float32(0.0)
-	if img.Resource != nil {
-		aspect = aspects[img.Resource.Name()]
-	} else if img.File != "" {
-		aspect = aspects[img.File]
-	} else if img.Image != nil {
-		// HOTFIX until Fyne 2.4 proper fix:
-		// we are not storing the aspect ratio in the map for the image.Image case
-		size := img.Image.Bounds().Size()
-		return float32(size.X) / float32(size.Y)
-	}
-
-	if aspect == 0 {
-		aspect = aspects[img]
-	}
-
-	return aspect
-}
-
 // PaintImage renders a given fyne Image to a Go standard image
 // PaintImage renders a given fyne Image to a Go standard image
 // If a fyne.Canvas is given and the image’s fill mode is “fill original” the image’s min size has
 // If a fyne.Canvas is given and the image’s fill mode is “fill original” the image’s min size has
 // to fit its original size. If it doesn’t, PaintImage does not paint the image but adjusts its min size.
 // to fit its original size. If it doesn’t, PaintImage does not paint the image but adjusts its min size.
 // The image will then be painted on the next frame because of the min size change.
 // The image will then be painted on the next frame because of the min size change.
 func PaintImage(img *canvas.Image, c fyne.Canvas, width, height int) image.Image {
 func PaintImage(img *canvas.Image, c fyne.Canvas, width, height int) image.Image {
-	var wantOrigW, wantOrigH int
-	wantOrigSize := false
-	if img.FillMode == canvas.ImageFillOriginal && c != nil {
-		wantOrigW = internal.ScaleInt(c, img.MinSize().Width)
-		wantOrigH = internal.ScaleInt(c, img.MinSize().Height)
-		wantOrigSize = true
+	if img.Size().IsZero() && c == nil { // an image without size or canvas won't get rendered unless we setup
+		img.Resize(fyne.NewSize(float32(width), float32(height)))
 	}
 	}
-
-	dst, origW, origH, err := paintImage(img, width, height, wantOrigSize, wantOrigW, wantOrigH)
+	dst, err := paintImage(img, width, height)
 	if err != nil {
 	if err != nil {
 		fyne.LogError("failed to paint image", err)
 		fyne.LogError("failed to paint image", err)
-		return nil
 	}
 	}
 
 
-	if wantOrigSize && dst == nil {
-		dpSize := fyne.NewSize(internal.UnscaleInt(c, origW), internal.UnscaleInt(c, origH))
-		img.SetMinSize(dpSize)
-		canvas.Refresh(img) // force the initial size to be respected
-	}
 	return dst
 	return dst
 }
 }
 
 
-func paintImage(img *canvas.Image, width, height int, wantOrigSize bool, wantOrigW, wantOrigH int) (dst image.Image, origW, origH int, err error) {
-	if (width <= 0 || height <= 0) && !wantOrigSize {
+func paintImage(img *canvas.Image, width, height int) (dst image.Image, err error) {
+	if width <= 0 || height <= 0 {
 		return
 		return
 	}
 	}
 
 
-	var aspectCacheKey interface{} = img
-	checkSize := func(origW, origH int) bool {
-		aspect := float32(origW) / float32(origH)
-		// this is used by our render code, so let's set it to the file aspect
-		aspects[aspectCacheKey] = aspect
-		return !wantOrigSize || (wantOrigW == origW && wantOrigH == origH)
+	dst = img.Image
+	if dst == nil {
+		dst = image.NewNRGBA(image.Rect(0, 0, width, height))
 	}
 	}
 
 
-	switch {
-	case img.File != "" || img.Resource != nil:
-		var (
-			file  io.Reader
-			name  string
-			isSVG bool
-		)
-		if img.Resource != nil {
-			name = img.Resource.Name()
-			file = bytes.NewReader(img.Resource.Content())
-			isSVG = IsResourceSVG(img.Resource)
-		} else {
-			name = img.File
-			var handle *os.File
-			handle, err = os.Open(img.File)
-			if err != nil {
-				err = fmt.Errorf("image load error: %w", err)
-				return
-			}
-			defer handle.Close()
-			file = handle
-			isSVG = isFileSVG(img.File)
-		}
-		aspectCacheKey = name
-
-		if isSVG {
-			tex := cache.GetSvg(name, width, height)
-			if tex == nil {
-				// Not in cache, so load the item and add to cache
-				tex, err = svg.ToImage(file, width, height, checkSize)
-				if err != nil {
-					return
-				}
-
-				cache.SetSvg(name, tex, width, height)
-			}
-			dst = tex
-		} else {
-			var pixels image.Image
-			pixels, _, err = image.Decode(file)
-			if err != nil {
-				err = fmt.Errorf("failed to decode image: %w", err)
-				return
-			}
-
-			origSize := pixels.Bounds().Size()
-			origW, origH = origSize.X, origSize.Y
-			if checkSize(origSize.X, origSize.Y) {
-				dst = scaleImage(pixels, width, height, img.ScaleMode)
-			}
-		}
-	case img.Image != nil:
-		origSize := img.Image.Bounds().Size()
-		origW, origH = origSize.X, origSize.Y
-		// HOTFIX until Fyne 2.4: don't store aspect ratio in map, as checkSize(x, y) does.
-		// Doing so leaks a reference to the image.Image data
-		if !wantOrigSize || (wantOrigW == origW && wantOrigH == origH) {
-			dst = scaleImage(img.Image, width, height, img.ScaleMode)
-		}
-	default:
-		dst = image.NewNRGBA(image.Rect(0, 0, 1, 1))
+	size := dst.Bounds().Size()
+	if width != size.X || height != size.Y {
+		dst = scaleImage(dst, width, height, img.ScaleMode)
 	}
 	}
 	return
 	return
 }
 }
@@ -169,24 +65,3 @@ func scaleImage(pixels image.Image, scaledW, scaledH int, scale canvas.ImageScal
 	}
 	}
 	return tex
 	return tex
 }
 }
-
-func isFileSVG(path string) bool {
-	return strings.ToLower(filepath.Ext(path)) == ".svg"
-}
-
-// IsResourceSVG checks if the resource is an SVG or not.
-func IsResourceSVG(res fyne.Resource) bool {
-	if strings.ToLower(filepath.Ext(res.Name())) == ".svg" {
-		return true
-	}
-
-	if len(res.Content()) < 5 {
-		return false
-	}
-
-	switch strings.ToLower(string(res.Content()[:5])) {
-	case "<!doc", "<?xml", "<svg ":
-		return true
-	}
-	return false
-}

+ 30 - 30
vendor/fyne.io/fyne/v2/internal/painter/software/draw.go

@@ -7,8 +7,8 @@ import (
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/canvas"
-	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal/painter"
 	"fyne.io/fyne/v2/internal/painter"
+	"fyne.io/fyne/v2/internal/scale"
 	"fyne.io/fyne/v2/theme"
 	"fyne.io/fyne/v2/theme"
 
 
 	"golang.org/x/image/draw"
 	"golang.org/x/image/draw"
@@ -21,9 +21,9 @@ type gradient interface {
 
 
 func drawCircle(c fyne.Canvas, circle *canvas.Circle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 func drawCircle(c fyne.Canvas, circle *canvas.Circle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 	pad := painter.VectorPad(circle)
 	pad := painter.VectorPad(circle)
-	scaledWidth := internal.ScaleInt(c, circle.Size().Width+pad*2)
-	scaledHeight := internal.ScaleInt(c, circle.Size().Height+pad*2)
-	scaledX, scaledY := internal.ScaleInt(c, pos.X-pad), internal.ScaleInt(c, pos.Y-pad)
+	scaledWidth := scale.ToScreenCoordinate(c, circle.Size().Width+pad*2)
+	scaledHeight := scale.ToScreenCoordinate(c, circle.Size().Height+pad*2)
+	scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
 	bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
 	bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
 
 
 	raw := painter.DrawCircle(circle, pad, func(in float32) float32 {
 	raw := painter.DrawCircle(circle, pad, func(in float32) float32 {
@@ -43,10 +43,10 @@ func drawCircle(c fyne.Canvas, circle *canvas.Circle, pos fyne.Position, base *i
 
 
 func drawGradient(c fyne.Canvas, g gradient, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 func drawGradient(c fyne.Canvas, g gradient, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 	bounds := g.Size()
 	bounds := g.Size()
-	width := internal.ScaleInt(c, bounds.Width)
-	height := internal.ScaleInt(c, bounds.Height)
+	width := scale.ToScreenCoordinate(c, bounds.Width)
+	height := scale.ToScreenCoordinate(c, bounds.Height)
 	tex := g.Generate(width, height)
 	tex := g.Generate(width, height)
-	drawTex(internal.ScaleInt(c, pos.X), internal.ScaleInt(c, pos.Y), width, height, base, tex, clip)
+	drawTex(scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y), width, height, base, tex, clip)
 }
 }
 
 
 func drawImage(c fyne.Canvas, img *canvas.Image, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 func drawImage(c fyne.Canvas, img *canvas.Image, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
@@ -54,14 +54,14 @@ func drawImage(c fyne.Canvas, img *canvas.Image, pos fyne.Position, base *image.
 	if bounds.IsZero() {
 	if bounds.IsZero() {
 		return
 		return
 	}
 	}
-	width := internal.ScaleInt(c, bounds.Width)
-	height := internal.ScaleInt(c, bounds.Height)
-	scaledX, scaledY := internal.ScaleInt(c, pos.X), internal.ScaleInt(c, pos.Y)
+	width := scale.ToScreenCoordinate(c, bounds.Width)
+	height := scale.ToScreenCoordinate(c, bounds.Height)
+	scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
 
 
 	origImg := painter.PaintImage(img, c, width, height)
 	origImg := painter.PaintImage(img, c, width, height)
 
 
 	if img.FillMode == canvas.ImageFillContain {
 	if img.FillMode == canvas.ImageFillContain {
-		imgAspect := painter.GetAspect(img)
+		imgAspect := img.Aspect()
 		objAspect := float32(width) / float32(height)
 		objAspect := float32(width) / float32(height)
 
 
 		if objAspect > imgAspect {
 		if objAspect > imgAspect {
@@ -104,9 +104,9 @@ func drawPixels(x, y, width, height int, mode canvas.ImageScale, base *image.NRG
 
 
 func drawLine(c fyne.Canvas, line *canvas.Line, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 func drawLine(c fyne.Canvas, line *canvas.Line, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 	pad := painter.VectorPad(line)
 	pad := painter.VectorPad(line)
-	scaledWidth := internal.ScaleInt(c, line.Size().Width+pad*2)
-	scaledHeight := internal.ScaleInt(c, line.Size().Height+pad*2)
-	scaledX, scaledY := internal.ScaleInt(c, pos.X-pad), internal.ScaleInt(c, pos.Y-pad)
+	scaledWidth := scale.ToScreenCoordinate(c, line.Size().Width+pad*2)
+	scaledHeight := scale.ToScreenCoordinate(c, line.Size().Height+pad*2)
+	scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
 	bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
 	bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
 
 
 	raw := painter.DrawLine(line, pad, func(in float32) float32 {
 	raw := painter.DrawLine(line, pad, func(in float32) float32 {
@@ -133,8 +133,8 @@ func drawTex(x, y, width, height int, base *image.NRGBA, tex image.Image, clip i
 
 
 func drawText(c fyne.Canvas, text *canvas.Text, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 func drawText(c fyne.Canvas, text *canvas.Text, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 	bounds := text.MinSize()
 	bounds := text.MinSize()
-	width := internal.ScaleInt(c, bounds.Width+painter.VectorPad(text))
-	height := internal.ScaleInt(c, bounds.Height)
+	width := scale.ToScreenCoordinate(c, bounds.Width+painter.VectorPad(text))
+	height := scale.ToScreenCoordinate(c, bounds.Height)
 	txtImg := image.NewRGBA(image.Rect(0, 0, width, height))
 	txtImg := image.NewRGBA(image.Rect(0, 0, width, height))
 
 
 	color := text.Color
 	color := text.Color
@@ -142,8 +142,8 @@ func drawText(c fyne.Canvas, text *canvas.Text, pos fyne.Position, base *image.N
 		color = theme.ForegroundColor()
 		color = theme.ForegroundColor()
 	}
 	}
 
 
-	face, measureFace := painter.CachedFontFace(text.TextStyle, text.TextSize*c.Scale(), 1)
-	painter.DrawString(txtImg, text.Text, color, face, measureFace, text.TextSize, c.Scale(), height, text.TextStyle.TabWidth)
+	face := painter.CachedFontFace(text.TextStyle, text.TextSize*c.Scale(), 1)
+	painter.DrawString(txtImg, text.Text, color, face.Fonts, text.TextSize, c.Scale(), text.TextStyle.TabWidth)
 
 
 	size := text.Size()
 	size := text.Size()
 	offsetX := float32(0)
 	offsetX := float32(0)
@@ -157,8 +157,8 @@ func drawText(c fyne.Canvas, text *canvas.Text, pos fyne.Position, base *image.N
 	if size.Height > bounds.Height {
 	if size.Height > bounds.Height {
 		offsetY = (size.Height - bounds.Height) / 2
 		offsetY = (size.Height - bounds.Height) / 2
 	}
 	}
-	scaledX := internal.ScaleInt(c, pos.X+offsetX)
-	scaledY := internal.ScaleInt(c, pos.Y+offsetY)
+	scaledX := scale.ToScreenCoordinate(c, pos.X+offsetX)
+	scaledY := scale.ToScreenCoordinate(c, pos.Y+offsetY)
 	imgBounds := image.Rect(scaledX, scaledY, scaledX+width, scaledY+height)
 	imgBounds := image.Rect(scaledX, scaledY, scaledX+width, scaledY+height)
 	clippedBounds := clip.Intersect(imgBounds)
 	clippedBounds := clip.Intersect(imgBounds)
 	srcPt := image.Point{X: clippedBounds.Min.X - imgBounds.Min.X, Y: clippedBounds.Min.Y - imgBounds.Min.Y}
 	srcPt := image.Point{X: clippedBounds.Min.X - imgBounds.Min.X, Y: clippedBounds.Min.Y - imgBounds.Min.Y}
@@ -170,9 +170,9 @@ func drawRaster(c fyne.Canvas, rast *canvas.Raster, pos fyne.Position, base *ima
 	if bounds.IsZero() {
 	if bounds.IsZero() {
 		return
 		return
 	}
 	}
-	width := internal.ScaleInt(c, bounds.Width)
-	height := internal.ScaleInt(c, bounds.Height)
-	scaledX, scaledY := internal.ScaleInt(c, pos.X), internal.ScaleInt(c, pos.Y)
+	width := scale.ToScreenCoordinate(c, bounds.Width)
+	height := scale.ToScreenCoordinate(c, bounds.Height)
+	scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
 
 
 	pix := rast.Generator(width, height)
 	pix := rast.Generator(width, height)
 	if pix.Bounds().Bounds().Dx() != width || pix.Bounds().Dy() != height {
 	if pix.Bounds().Bounds().Dx() != width || pix.Bounds().Dy() != height {
@@ -184,9 +184,9 @@ func drawRaster(c fyne.Canvas, rast *canvas.Raster, pos fyne.Position, base *ima
 
 
 func drawRectangleStroke(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 func drawRectangleStroke(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 	pad := painter.VectorPad(rect)
 	pad := painter.VectorPad(rect)
-	scaledWidth := internal.ScaleInt(c, rect.Size().Width+pad*2)
-	scaledHeight := internal.ScaleInt(c, rect.Size().Height+pad*2)
-	scaledX, scaledY := internal.ScaleInt(c, pos.X-pad), internal.ScaleInt(c, pos.Y-pad)
+	scaledWidth := scale.ToScreenCoordinate(c, rect.Size().Width+pad*2)
+	scaledHeight := scale.ToScreenCoordinate(c, rect.Size().Height+pad*2)
+	scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X-pad), scale.ToScreenCoordinate(c, pos.Y-pad)
 	bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
 	bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
 
 
 	raw := painter.DrawRectangle(rect, pad, func(in float32) float32 {
 	raw := painter.DrawRectangle(rect, pad, func(in float32) float32 {
@@ -205,14 +205,14 @@ func drawRectangleStroke(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Positio
 }
 }
 
 
 func drawRectangle(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
 func drawRectangle(c fyne.Canvas, rect *canvas.Rectangle, pos fyne.Position, base *image.NRGBA, clip image.Rectangle) {
-	if rect.StrokeColor != nil && rect.StrokeWidth > 0 { // use a rasterizer if there is a stroke
+	if (rect.StrokeColor != nil && rect.StrokeWidth > 0) || rect.CornerRadius != 0 { // use a rasterizer if there is a stroke or radius
 		drawRectangleStroke(c, rect, pos, base, clip)
 		drawRectangleStroke(c, rect, pos, base, clip)
 		return
 		return
 	}
 	}
 
 
-	scaledWidth := internal.ScaleInt(c, rect.Size().Width)
-	scaledHeight := internal.ScaleInt(c, rect.Size().Height)
-	scaledX, scaledY := internal.ScaleInt(c, pos.X), internal.ScaleInt(c, pos.Y)
+	scaledWidth := scale.ToScreenCoordinate(c, rect.Size().Width)
+	scaledHeight := scale.ToScreenCoordinate(c, rect.Size().Height)
+	scaledX, scaledY := scale.ToScreenCoordinate(c, pos.X), scale.ToScreenCoordinate(c, pos.Y)
 	bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
 	bounds := clip.Intersect(image.Rect(scaledX, scaledY, scaledX+scaledWidth, scaledY+scaledHeight))
 	draw.Draw(base, bounds, image.NewUniform(rect.FillColor), image.Point{}, draw.Over)
 	draw.Draw(base, bounds, image.NewUniform(rect.FillColor), image.Point{}, draw.Over)
 }
 }

+ 6 - 6
vendor/fyne.io/fyne/v2/internal/painter/software/painter.go

@@ -5,8 +5,8 @@ import (
 
 
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/canvas"
 	"fyne.io/fyne/v2/canvas"
-	"fyne.io/fyne/v2/internal"
 	"fyne.io/fyne/v2/internal/driver"
 	"fyne.io/fyne/v2/internal/driver"
+	"fyne.io/fyne/v2/internal/scale"
 )
 )
 
 
 // Painter is a simple software painter that can paint a canvas in memory.
 // Painter is a simple software painter that can paint a canvas in memory.
@@ -22,17 +22,17 @@ func NewPainter() *Painter {
 // The canvas to be drawn is passed in as a parameter and the return is an
 // The canvas to be drawn is passed in as a parameter and the return is an
 // image containing the result of rendering.
 // image containing the result of rendering.
 func (*Painter) Paint(c fyne.Canvas) image.Image {
 func (*Painter) Paint(c fyne.Canvas) image.Image {
-	bounds := image.Rect(0, 0, internal.ScaleInt(c, c.Size().Width), internal.ScaleInt(c, c.Size().Height))
+	bounds := image.Rect(0, 0, scale.ToScreenCoordinate(c, c.Size().Width), scale.ToScreenCoordinate(c, c.Size().Height))
 	base := image.NewNRGBA(bounds)
 	base := image.NewNRGBA(bounds)
 
 
 	paint := func(obj fyne.CanvasObject, pos, clipPos fyne.Position, clipSize fyne.Size) bool {
 	paint := func(obj fyne.CanvasObject, pos, clipPos fyne.Position, clipSize fyne.Size) bool {
 		w := fyne.Min(clipPos.X+clipSize.Width, c.Size().Width)
 		w := fyne.Min(clipPos.X+clipSize.Width, c.Size().Width)
 		h := fyne.Min(clipPos.Y+clipSize.Height, c.Size().Height)
 		h := fyne.Min(clipPos.Y+clipSize.Height, c.Size().Height)
 		clip := image.Rect(
 		clip := image.Rect(
-			internal.ScaleInt(c, clipPos.X),
-			internal.ScaleInt(c, clipPos.Y),
-			internal.ScaleInt(c, w),
-			internal.ScaleInt(c, h),
+			scale.ToScreenCoordinate(c, clipPos.X),
+			scale.ToScreenCoordinate(c, clipPos.Y),
+			scale.ToScreenCoordinate(c, w),
+			scale.ToScreenCoordinate(c, h),
 		)
 		)
 		switch o := obj.(type) {
 		switch o := obj.(type) {
 		case *canvas.Image:
 		case *canvas.Image:

+ 0 - 1
vendor/fyne.io/fyne/v2/internal/painter/vector.go

@@ -27,7 +27,6 @@ func VectorPad(obj fyne.CanvasObject) float32 {
 		if co.TextStyle.Italic {
 		if co.TextStyle.Italic {
 			return co.TextSize / 5 // make sure that even a 20% lean does not overflow
 			return co.TextSize / 5 // make sure that even a 20% lean does not overflow
 		}
 		}
-		return co.TextSize / 5 // TODO remove after we get our new text rendering all sorted in 2.4 - #3500
 	}
 	}
 
 
 	return 0
 	return 0

Some files were not shown because too many files changed in this diff