testcanvas.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. package test
  2. import (
  3. "image"
  4. "image/draw"
  5. "sync"
  6. "fyne.io/fyne/v2"
  7. "fyne.io/fyne/v2/driver/desktop"
  8. "fyne.io/fyne/v2/internal"
  9. "fyne.io/fyne/v2/internal/app"
  10. "fyne.io/fyne/v2/internal/cache"
  11. "fyne.io/fyne/v2/internal/scale"
  12. "fyne.io/fyne/v2/theme"
  13. )
  14. var (
  15. dummyCanvas fyne.Canvas
  16. )
  17. // WindowlessCanvas provides functionality for a canvas to operate without a window
  18. type WindowlessCanvas interface {
  19. fyne.Canvas
  20. Padded() bool
  21. Resize(fyne.Size)
  22. SetPadded(bool)
  23. SetScale(float32)
  24. }
  25. type testCanvas struct {
  26. size fyne.Size
  27. scale float32
  28. content fyne.CanvasObject
  29. overlays *internal.OverlayStack
  30. focusMgr *app.FocusManager
  31. hovered desktop.Hoverable
  32. padded bool
  33. transparent bool
  34. onTypedRune func(rune)
  35. onTypedKey func(*fyne.KeyEvent)
  36. fyne.ShortcutHandler
  37. painter SoftwarePainter
  38. propertyLock sync.RWMutex
  39. }
  40. // Canvas returns a reusable in-memory canvas used for testing
  41. func Canvas() fyne.Canvas {
  42. if dummyCanvas == nil {
  43. dummyCanvas = NewCanvas()
  44. }
  45. return dummyCanvas
  46. }
  47. // NewCanvas returns a single use in-memory canvas used for testing.
  48. // This canvas has no painter so calls to Capture() will return a blank image.
  49. func NewCanvas() WindowlessCanvas {
  50. c := &testCanvas{
  51. focusMgr: app.NewFocusManager(nil),
  52. padded: true,
  53. scale: 1.0,
  54. size: fyne.NewSize(10, 10),
  55. }
  56. c.overlays = &internal.OverlayStack{Canvas: c}
  57. return c
  58. }
  59. // NewCanvasWithPainter allows creation of an in-memory canvas with a specific painter.
  60. // The painter will be used to render in the Capture() call.
  61. func NewCanvasWithPainter(painter SoftwarePainter) WindowlessCanvas {
  62. canvas := NewCanvas().(*testCanvas)
  63. canvas.painter = painter
  64. return canvas
  65. }
  66. // NewTransparentCanvasWithPainter allows creation of an in-memory canvas with a specific painter without a background color.
  67. // The painter will be used to render in the Capture() call.
  68. //
  69. // Since: 2.2
  70. func NewTransparentCanvasWithPainter(painter SoftwarePainter) WindowlessCanvas {
  71. canvas := NewCanvasWithPainter(painter).(*testCanvas)
  72. canvas.transparent = true
  73. return canvas
  74. }
  75. func (c *testCanvas) Capture() image.Image {
  76. cache.Clean(true)
  77. bounds := image.Rect(0, 0, scale.ToScreenCoordinate(c, c.Size().Width), scale.ToScreenCoordinate(c, c.Size().Height))
  78. img := image.NewNRGBA(bounds)
  79. if !c.transparent {
  80. draw.Draw(img, bounds, image.NewUniform(theme.BackgroundColor()), image.Point{}, draw.Src)
  81. }
  82. if c.painter != nil {
  83. draw.Draw(img, bounds, c.painter.Paint(c), image.Point{}, draw.Over)
  84. }
  85. return img
  86. }
  87. func (c *testCanvas) Content() fyne.CanvasObject {
  88. c.propertyLock.RLock()
  89. defer c.propertyLock.RUnlock()
  90. return c.content
  91. }
  92. func (c *testCanvas) Focus(obj fyne.Focusable) {
  93. c.focusManager().Focus(obj)
  94. }
  95. func (c *testCanvas) FocusNext() {
  96. c.focusManager().FocusNext()
  97. }
  98. func (c *testCanvas) FocusPrevious() {
  99. c.focusManager().FocusPrevious()
  100. }
  101. func (c *testCanvas) Focused() fyne.Focusable {
  102. return c.focusManager().Focused()
  103. }
  104. func (c *testCanvas) InteractiveArea() (fyne.Position, fyne.Size) {
  105. return fyne.Position{}, c.Size()
  106. }
  107. func (c *testCanvas) OnTypedKey() func(*fyne.KeyEvent) {
  108. c.propertyLock.RLock()
  109. defer c.propertyLock.RUnlock()
  110. return c.onTypedKey
  111. }
  112. func (c *testCanvas) OnTypedRune() func(rune) {
  113. c.propertyLock.RLock()
  114. defer c.propertyLock.RUnlock()
  115. return c.onTypedRune
  116. }
  117. func (c *testCanvas) Overlays() fyne.OverlayStack {
  118. c.propertyLock.Lock()
  119. defer c.propertyLock.Unlock()
  120. return c.overlays
  121. }
  122. func (c *testCanvas) Padded() bool {
  123. c.propertyLock.RLock()
  124. defer c.propertyLock.RUnlock()
  125. return c.padded
  126. }
  127. func (c *testCanvas) PixelCoordinateForPosition(pos fyne.Position) (int, int) {
  128. return int(float32(pos.X) * c.scale), int(float32(pos.Y) * c.scale)
  129. }
  130. func (c *testCanvas) Refresh(fyne.CanvasObject) {
  131. }
  132. func (c *testCanvas) Resize(size fyne.Size) {
  133. c.propertyLock.Lock()
  134. content := c.content
  135. overlays := c.overlays
  136. padded := c.padded
  137. c.size = size
  138. c.propertyLock.Unlock()
  139. if content == nil {
  140. return
  141. }
  142. // Ensure testcanvas mimics real canvas.Resize behavior
  143. for _, overlay := range overlays.List() {
  144. type popupWidget interface {
  145. fyne.CanvasObject
  146. ShowAtPosition(fyne.Position)
  147. }
  148. if p, ok := overlay.(popupWidget); ok {
  149. // TODO: remove this when #707 is being addressed.
  150. // “Notifies” the PopUp of the canvas size change.
  151. p.Refresh()
  152. } else {
  153. overlay.Resize(size)
  154. }
  155. }
  156. if padded {
  157. content.Resize(size.Subtract(fyne.NewSize(theme.Padding()*2, theme.Padding()*2)))
  158. content.Move(fyne.NewPos(theme.Padding(), theme.Padding()))
  159. } else {
  160. content.Resize(size)
  161. content.Move(fyne.NewPos(0, 0))
  162. }
  163. }
  164. func (c *testCanvas) Scale() float32 {
  165. c.propertyLock.RLock()
  166. defer c.propertyLock.RUnlock()
  167. return c.scale
  168. }
  169. func (c *testCanvas) SetContent(content fyne.CanvasObject) {
  170. c.propertyLock.Lock()
  171. c.content = content
  172. c.focusMgr = app.NewFocusManager(c.content)
  173. c.propertyLock.Unlock()
  174. if content == nil {
  175. return
  176. }
  177. padding := fyne.NewSize(0, 0)
  178. if c.padded {
  179. padding = fyne.NewSize(theme.Padding()*2, theme.Padding()*2)
  180. }
  181. c.Resize(content.MinSize().Add(padding))
  182. }
  183. func (c *testCanvas) SetOnTypedKey(handler func(*fyne.KeyEvent)) {
  184. c.propertyLock.Lock()
  185. defer c.propertyLock.Unlock()
  186. c.onTypedKey = handler
  187. }
  188. func (c *testCanvas) SetOnTypedRune(handler func(rune)) {
  189. c.propertyLock.Lock()
  190. defer c.propertyLock.Unlock()
  191. c.onTypedRune = handler
  192. }
  193. func (c *testCanvas) SetPadded(padded bool) {
  194. c.propertyLock.Lock()
  195. c.padded = padded
  196. c.propertyLock.Unlock()
  197. c.Resize(c.Size())
  198. }
  199. func (c *testCanvas) SetScale(scale float32) {
  200. c.propertyLock.Lock()
  201. defer c.propertyLock.Unlock()
  202. c.scale = scale
  203. }
  204. func (c *testCanvas) Size() fyne.Size {
  205. c.propertyLock.RLock()
  206. defer c.propertyLock.RUnlock()
  207. return c.size
  208. }
  209. func (c *testCanvas) Unfocus() {
  210. c.focusManager().Focus(nil)
  211. }
  212. func (c *testCanvas) focusManager() *app.FocusManager {
  213. c.propertyLock.RLock()
  214. defer c.propertyLock.RUnlock()
  215. if focusMgr := c.overlays.TopFocusManager(); focusMgr != nil {
  216. return focusMgr
  217. }
  218. return c.focusMgr
  219. }
  220. func (c *testCanvas) objectTrees() []fyne.CanvasObject {
  221. trees := make([]fyne.CanvasObject, 0, len(c.Overlays().List())+1)
  222. if c.content != nil {
  223. trees = append(trees, c.content)
  224. }
  225. trees = append(trees, c.Overlays().List()...)
  226. return trees
  227. }
  228. func layoutAndCollect(objects []fyne.CanvasObject, o fyne.CanvasObject, size fyne.Size) []fyne.CanvasObject {
  229. objects = append(objects, o)
  230. switch c := o.(type) {
  231. case fyne.Widget:
  232. r := c.CreateRenderer()
  233. r.Layout(size)
  234. for _, child := range r.Objects() {
  235. objects = layoutAndCollect(objects, child, child.Size())
  236. }
  237. case *fyne.Container:
  238. if c.Layout != nil {
  239. c.Layout.Layout(c.Objects, size)
  240. }
  241. for _, child := range c.Objects {
  242. objects = layoutAndCollect(objects, child, child.Size())
  243. }
  244. }
  245. return objects
  246. }