testcanvas.go 6.7 KB

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