| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- package test
- import (
- "image"
- "image/draw"
- "sync"
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/driver/desktop"
- "fyne.io/fyne/v2/internal"
- "fyne.io/fyne/v2/internal/app"
- "fyne.io/fyne/v2/internal/cache"
- "fyne.io/fyne/v2/internal/scale"
- "fyne.io/fyne/v2/theme"
- )
- var (
- dummyCanvas fyne.Canvas
- )
- // WindowlessCanvas provides functionality for a canvas to operate without a window
- type WindowlessCanvas interface {
- fyne.Canvas
- Padded() bool
- Resize(fyne.Size)
- SetPadded(bool)
- SetScale(float32)
- }
- type testCanvas struct {
- size fyne.Size
- scale float32
- content fyne.CanvasObject
- overlays *internal.OverlayStack
- focusMgr *app.FocusManager
- hovered desktop.Hoverable
- padded bool
- transparent bool
- onTypedRune func(rune)
- onTypedKey func(*fyne.KeyEvent)
- fyne.ShortcutHandler
- painter SoftwarePainter
- propertyLock sync.RWMutex
- }
- // Canvas returns a reusable in-memory canvas used for testing
- func Canvas() fyne.Canvas {
- if dummyCanvas == nil {
- dummyCanvas = NewCanvas()
- }
- return dummyCanvas
- }
- // NewCanvas returns a single use in-memory canvas used for testing.
- // This canvas has no painter so calls to Capture() will return a blank image.
- func NewCanvas() WindowlessCanvas {
- c := &testCanvas{
- focusMgr: app.NewFocusManager(nil),
- padded: true,
- scale: 1.0,
- size: fyne.NewSize(10, 10),
- }
- c.overlays = &internal.OverlayStack{Canvas: c}
- return c
- }
- // NewCanvasWithPainter allows creation of an in-memory canvas with a specific painter.
- // The painter will be used to render in the Capture() call.
- func NewCanvasWithPainter(painter SoftwarePainter) WindowlessCanvas {
- canvas := NewCanvas().(*testCanvas)
- canvas.painter = painter
- return canvas
- }
- // NewTransparentCanvasWithPainter allows creation of an in-memory canvas with a specific painter without a background color.
- // The painter will be used to render in the Capture() call.
- //
- // Since: 2.2
- func NewTransparentCanvasWithPainter(painter SoftwarePainter) WindowlessCanvas {
- canvas := NewCanvasWithPainter(painter).(*testCanvas)
- canvas.transparent = true
- return canvas
- }
- func (c *testCanvas) Capture() image.Image {
- cache.Clean(true)
- bounds := image.Rect(0, 0, scale.ToScreenCoordinate(c, c.Size().Width), scale.ToScreenCoordinate(c, c.Size().Height))
- img := image.NewNRGBA(bounds)
- if !c.transparent {
- draw.Draw(img, bounds, image.NewUniform(theme.BackgroundColor()), image.Point{}, draw.Src)
- }
- if c.painter != nil {
- draw.Draw(img, bounds, c.painter.Paint(c), image.Point{}, draw.Over)
- }
- return img
- }
- func (c *testCanvas) Content() fyne.CanvasObject {
- c.propertyLock.RLock()
- defer c.propertyLock.RUnlock()
- return c.content
- }
- func (c *testCanvas) Focus(obj fyne.Focusable) {
- c.focusManager().Focus(obj)
- }
- func (c *testCanvas) FocusNext() {
- c.focusManager().FocusNext()
- }
- func (c *testCanvas) FocusPrevious() {
- c.focusManager().FocusPrevious()
- }
- func (c *testCanvas) Focused() fyne.Focusable {
- return c.focusManager().Focused()
- }
- func (c *testCanvas) InteractiveArea() (fyne.Position, fyne.Size) {
- return fyne.Position{}, c.Size()
- }
- func (c *testCanvas) OnTypedKey() func(*fyne.KeyEvent) {
- c.propertyLock.RLock()
- defer c.propertyLock.RUnlock()
- return c.onTypedKey
- }
- func (c *testCanvas) OnTypedRune() func(rune) {
- c.propertyLock.RLock()
- defer c.propertyLock.RUnlock()
- return c.onTypedRune
- }
- func (c *testCanvas) Overlays() fyne.OverlayStack {
- c.propertyLock.Lock()
- defer c.propertyLock.Unlock()
- return c.overlays
- }
- func (c *testCanvas) Padded() bool {
- c.propertyLock.RLock()
- defer c.propertyLock.RUnlock()
- return c.padded
- }
- func (c *testCanvas) PixelCoordinateForPosition(pos fyne.Position) (int, int) {
- return int(float32(pos.X) * c.scale), int(float32(pos.Y) * c.scale)
- }
- func (c *testCanvas) Refresh(fyne.CanvasObject) {
- }
- func (c *testCanvas) Resize(size fyne.Size) {
- c.propertyLock.Lock()
- content := c.content
- overlays := c.overlays
- padded := c.padded
- c.size = size
- c.propertyLock.Unlock()
- if content == nil {
- return
- }
- // Ensure testcanvas mimics real canvas.Resize behavior
- for _, overlay := range overlays.List() {
- type popupWidget interface {
- fyne.CanvasObject
- ShowAtPosition(fyne.Position)
- }
- if p, ok := overlay.(popupWidget); ok {
- // TODO: remove this when #707 is being addressed.
- // “Notifies” the PopUp of the canvas size change.
- p.Refresh()
- } else {
- overlay.Resize(size)
- }
- }
- if padded {
- content.Resize(size.Subtract(fyne.NewSize(theme.Padding()*2, theme.Padding()*2)))
- content.Move(fyne.NewPos(theme.Padding(), theme.Padding()))
- } else {
- content.Resize(size)
- content.Move(fyne.NewPos(0, 0))
- }
- }
- func (c *testCanvas) Scale() float32 {
- c.propertyLock.RLock()
- defer c.propertyLock.RUnlock()
- return c.scale
- }
- func (c *testCanvas) SetContent(content fyne.CanvasObject) {
- c.propertyLock.Lock()
- c.content = content
- c.focusMgr = app.NewFocusManager(c.content)
- c.propertyLock.Unlock()
- if content == nil {
- return
- }
- padding := fyne.NewSize(0, 0)
- if c.padded {
- padding = fyne.NewSize(theme.Padding()*2, theme.Padding()*2)
- }
- c.Resize(content.MinSize().Add(padding))
- }
- func (c *testCanvas) SetOnTypedKey(handler func(*fyne.KeyEvent)) {
- c.propertyLock.Lock()
- defer c.propertyLock.Unlock()
- c.onTypedKey = handler
- }
- func (c *testCanvas) SetOnTypedRune(handler func(rune)) {
- c.propertyLock.Lock()
- defer c.propertyLock.Unlock()
- c.onTypedRune = handler
- }
- func (c *testCanvas) SetPadded(padded bool) {
- c.propertyLock.Lock()
- c.padded = padded
- c.propertyLock.Unlock()
- c.Resize(c.Size())
- }
- func (c *testCanvas) SetScale(scale float32) {
- c.propertyLock.Lock()
- defer c.propertyLock.Unlock()
- c.scale = scale
- }
- func (c *testCanvas) Size() fyne.Size {
- c.propertyLock.RLock()
- defer c.propertyLock.RUnlock()
- return c.size
- }
- func (c *testCanvas) Unfocus() {
- c.focusManager().Focus(nil)
- }
- func (c *testCanvas) focusManager() *app.FocusManager {
- c.propertyLock.RLock()
- defer c.propertyLock.RUnlock()
- if focusMgr := c.overlays.TopFocusManager(); focusMgr != nil {
- return focusMgr
- }
- return c.focusMgr
- }
- func (c *testCanvas) objectTrees() []fyne.CanvasObject {
- trees := make([]fyne.CanvasObject, 0, len(c.Overlays().List())+1)
- if c.content != nil {
- trees = append(trees, c.content)
- }
- trees = append(trees, c.Overlays().List()...)
- return trees
- }
- func layoutAndCollect(objects []fyne.CanvasObject, o fyne.CanvasObject, size fyne.Size) []fyne.CanvasObject {
- objects = append(objects, o)
- switch c := o.(type) {
- case fyne.Widget:
- r := c.CreateRenderer()
- r.Layout(size)
- for _, child := range r.Objects() {
- objects = layoutAndCollect(objects, child, child.Size())
- }
- case *fyne.Container:
- if c.Layout != nil {
- c.Layout.Layout(c.Objects, size)
- }
- for _, child := range c.Objects {
- objects = layoutAndCollect(objects, child, child.Size())
- }
- }
- return objects
- }
|