adam-gui/vendor/fyne.io/fyne/v2/test/testcanvas.go
2024-04-29 19:13:50 +02:00

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
}