VideoTools/vendor/fyne.io/fyne/v2/test/canvas.go
Stu Leak 68df790d27 Fix player frame generation and video playback
Major improvements to UnifiedPlayer:

1. GetFrameImage() now works when paused for responsive UI updates
2. Play() method properly starts FFmpeg process
3. Frame display loop runs continuously for smooth video display
4. Disabled audio temporarily to fix video playback fundamentals
5. Simplified FFmpeg command to focus on video stream only

Player now:
- Generates video frames correctly
- Shows video when paused
- Has responsive progress tracking
- Starts playback properly

Next steps: Re-enable audio playback once video is stable
2026-01-07 22:20:00 -05:00

322 lines
6.8 KiB
Go

package test
import (
"image"
"image/draw"
"sync"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/internal"
intapp "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 WindowlessCanvas
// 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 canvas struct {
size fyne.Size
resized bool
scale float32
content fyne.CanvasObject
overlays internal.OverlayStack
focusMgr *intapp.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 := &canvas{
focusMgr: intapp.NewFocusManager(nil),
padded: true,
scale: 1.0,
size: fyne.NewSize(100, 100),
}
c.overlays.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 {
c := NewCanvas().(*canvas)
c.painter = painter
return c
}
// 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 {
c := NewCanvasWithPainter(painter).(*canvas)
c.transparent = true
return c
}
func (c *canvas) Capture() image.Image {
cache.Clean(true)
size := c.Size()
bounds := image.Rect(0, 0, scale.ToScreenCoordinate(c, size.Width), scale.ToScreenCoordinate(c, size.Height))
img := image.NewNRGBA(bounds)
if !c.transparent {
draw.Draw(img, bounds, image.NewUniform(theme.Color(theme.ColorNameBackground)), image.Point{}, draw.Src)
}
if c.painter != nil {
draw.Draw(img, bounds, c.painter.Paint(c), image.Point{}, draw.Over)
}
return img
}
func (c *canvas) Content() fyne.CanvasObject {
c.propertyLock.RLock()
defer c.propertyLock.RUnlock()
return c.content
}
func (c *canvas) Focus(obj fyne.Focusable) {
c.focusManager().Focus(obj)
}
func (c *canvas) FocusNext() {
c.focusManager().FocusNext()
}
func (c *canvas) FocusPrevious() {
c.focusManager().FocusPrevious()
}
func (c *canvas) Focused() fyne.Focusable {
return c.focusManager().Focused()
}
func (c *canvas) InteractiveArea() (fyne.Position, fyne.Size) {
return fyne.NewPos(2, 3), c.Size().SubtractWidthHeight(4, 5)
}
func (c *canvas) OnTypedKey() func(*fyne.KeyEvent) {
c.propertyLock.RLock()
defer c.propertyLock.RUnlock()
return c.onTypedKey
}
func (c *canvas) OnTypedRune() func(rune) {
c.propertyLock.RLock()
defer c.propertyLock.RUnlock()
return c.onTypedRune
}
func (c *canvas) Overlays() fyne.OverlayStack {
c.propertyLock.Lock()
defer c.propertyLock.Unlock()
return &c.overlays
}
func (c *canvas) Padded() bool {
c.propertyLock.RLock()
defer c.propertyLock.RUnlock()
return c.padded
}
func (c *canvas) PixelCoordinateForPosition(pos fyne.Position) (int, int) {
return int(pos.X * c.scale), int(pos.Y * c.scale)
}
func (c *canvas) Refresh(fyne.CanvasObject) {
}
func (c *canvas) Resize(size fyne.Size) {
c.propertyLock.Lock()
c.resized = true
c.propertyLock.Unlock()
c.doResize(size)
}
func (c *canvas) doResize(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 {
padding := theme.Padding()
content.Resize(size.Subtract(fyne.NewSquareSize(padding * 2)))
content.Move(fyne.NewSquareOffsetPos(padding))
} else {
content.Resize(size)
content.Move(fyne.NewPos(0, 0))
}
}
func (c *canvas) Scale() float32 {
c.propertyLock.RLock()
defer c.propertyLock.RUnlock()
return c.scale
}
func (c *canvas) SetContent(content fyne.CanvasObject) {
c.propertyLock.Lock()
c.content = content
c.focusMgr = intapp.NewFocusManager(c.content)
resized := c.resized
c.propertyLock.Unlock()
if content == nil {
return
}
minSize := content.MinSize()
if c.padded {
minSize = minSize.Add(fyne.NewSquareSize(theme.Padding() * 2))
}
if resized {
c.doResize(c.Size().Max(minSize))
} else {
c.doResize(minSize)
}
}
func (c *canvas) SetOnTypedKey(handler func(*fyne.KeyEvent)) {
c.propertyLock.Lock()
defer c.propertyLock.Unlock()
c.onTypedKey = handler
}
func (c *canvas) SetOnTypedRune(handler func(rune)) {
c.propertyLock.Lock()
defer c.propertyLock.Unlock()
c.onTypedRune = handler
}
func (c *canvas) SetPadded(padded bool) {
c.propertyLock.Lock()
c.padded = padded
c.propertyLock.Unlock()
c.doResize(c.Size())
}
func (c *canvas) SetScale(scale float32) {
c.propertyLock.Lock()
defer c.propertyLock.Unlock()
c.scale = scale
}
func (c *canvas) Size() fyne.Size {
c.propertyLock.RLock()
defer c.propertyLock.RUnlock()
return c.size
}
func (c *canvas) Unfocus() {
c.focusManager().Focus(nil)
}
func (c *canvas) focusManager() *intapp.FocusManager {
c.propertyLock.RLock()
defer c.propertyLock.RUnlock()
if focusMgr := c.overlays.TopFocusManager(); focusMgr != nil {
return focusMgr
}
return c.focusMgr
}
func (c *canvas) objectTrees() []fyne.CanvasObject {
overlays := c.Overlays().List()
trees := make([]fyne.CanvasObject, 0, len(overlays)+1)
if c.content != nil {
trees = append(trees, c.content)
}
trees = append(trees, overlays...)
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
}