306 lines
6.7 KiB
Go
306 lines
6.7 KiB
Go
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
|
|
}
|