611 lines
15 KiB
Go
611 lines
15 KiB
Go
|
package common
|
|||
|
|
|||
|
import (
|
|||
|
"image/color"
|
|||
|
"reflect"
|
|||
|
"sync"
|
|||
|
"sync/atomic"
|
|||
|
|
|||
|
"fyne.io/fyne/v2"
|
|||
|
"fyne.io/fyne/v2/canvas"
|
|||
|
"fyne.io/fyne/v2/internal"
|
|||
|
"fyne.io/fyne/v2/internal/app"
|
|||
|
"fyne.io/fyne/v2/internal/async"
|
|||
|
"fyne.io/fyne/v2/internal/cache"
|
|||
|
"fyne.io/fyne/v2/internal/driver"
|
|||
|
"fyne.io/fyne/v2/internal/painter/gl"
|
|||
|
)
|
|||
|
|
|||
|
// SizeableCanvas defines a canvas with size related functions.
|
|||
|
type SizeableCanvas interface {
|
|||
|
fyne.Canvas
|
|||
|
Resize(fyne.Size)
|
|||
|
MinSize() fyne.Size
|
|||
|
}
|
|||
|
|
|||
|
// Canvas defines common canvas implementation.
|
|||
|
type Canvas struct {
|
|||
|
sync.RWMutex
|
|||
|
|
|||
|
OnFocus func(obj fyne.Focusable)
|
|||
|
OnUnfocus func()
|
|||
|
|
|||
|
impl SizeableCanvas
|
|||
|
|
|||
|
contentFocusMgr *app.FocusManager
|
|||
|
menuFocusMgr *app.FocusManager
|
|||
|
overlays *overlayStack
|
|||
|
|
|||
|
shortcut fyne.ShortcutHandler
|
|||
|
|
|||
|
painter gl.Painter
|
|||
|
|
|||
|
// Any object that requestes to enter to the refresh queue should
|
|||
|
// not be omitted as it is always a rendering task's decision
|
|||
|
// for skipping frames or drawing calls.
|
|||
|
//
|
|||
|
// If an object failed to ender the refresh queue, the object may
|
|||
|
// disappear or blink from the view at any frames. As of this reason,
|
|||
|
// the refreshQueue is an unbounded queue which is bale to cache
|
|||
|
// arbitrary number of fyne.CanvasObject for the rendering.
|
|||
|
refreshQueue *async.CanvasObjectQueue
|
|||
|
dirty uint32 // atomic
|
|||
|
|
|||
|
mWindowHeadTree, contentTree, menuTree *renderCacheTree
|
|||
|
}
|
|||
|
|
|||
|
// AddShortcut adds a shortcut to the canvas.
|
|||
|
func (c *Canvas) AddShortcut(shortcut fyne.Shortcut, handler func(shortcut fyne.Shortcut)) {
|
|||
|
c.shortcut.AddShortcut(shortcut, handler)
|
|||
|
}
|
|||
|
|
|||
|
func (c *Canvas) DrawDebugOverlay(obj fyne.CanvasObject, pos fyne.Position, size fyne.Size) {
|
|||
|
switch obj.(type) {
|
|||
|
case fyne.Widget:
|
|||
|
r := canvas.NewRectangle(color.Transparent)
|
|||
|
r.StrokeColor = color.NRGBA{R: 0xcc, G: 0x33, B: 0x33, A: 0xff}
|
|||
|
r.StrokeWidth = 1
|
|||
|
r.Resize(obj.Size())
|
|||
|
c.Painter().Paint(r, pos, size)
|
|||
|
|
|||
|
t := canvas.NewText(reflect.ValueOf(obj).Elem().Type().Name(), r.StrokeColor)
|
|||
|
t.TextSize = 10
|
|||
|
c.Painter().Paint(t, pos.AddXY(2, 2), size)
|
|||
|
case *fyne.Container:
|
|||
|
r := canvas.NewRectangle(color.Transparent)
|
|||
|
r.StrokeColor = color.NRGBA{R: 0x33, G: 0x33, B: 0xcc, A: 0xff}
|
|||
|
r.StrokeWidth = 1
|
|||
|
r.Resize(obj.Size())
|
|||
|
c.Painter().Paint(r, pos, size)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// EnsureMinSize ensure canvas min size.
|
|||
|
//
|
|||
|
// This function uses lock.
|
|||
|
func (c *Canvas) EnsureMinSize() bool {
|
|||
|
if c.impl.Content() == nil {
|
|||
|
return false
|
|||
|
}
|
|||
|
windowNeedsMinSizeUpdate := false
|
|||
|
csize := c.impl.Size()
|
|||
|
min := c.impl.MinSize()
|
|||
|
|
|||
|
c.RLock()
|
|||
|
defer c.RUnlock()
|
|||
|
|
|||
|
var parentNeedingUpdate *RenderCacheNode
|
|||
|
|
|||
|
ensureMinSize := func(node *RenderCacheNode, pos fyne.Position) {
|
|||
|
obj := node.obj
|
|||
|
cache.SetCanvasForObject(obj, c.impl, func() {
|
|||
|
if img, ok := obj.(*canvas.Image); ok {
|
|||
|
c.RUnlock()
|
|||
|
img.Refresh() // this may now have a different texScale
|
|||
|
c.RLock()
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
if parentNeedingUpdate == node {
|
|||
|
c.updateLayout(obj)
|
|||
|
parentNeedingUpdate = nil
|
|||
|
}
|
|||
|
|
|||
|
c.RUnlock()
|
|||
|
if !obj.Visible() {
|
|||
|
c.RLock()
|
|||
|
return
|
|||
|
}
|
|||
|
minSize := obj.MinSize()
|
|||
|
c.RLock()
|
|||
|
|
|||
|
minSizeChanged := node.minSize != minSize
|
|||
|
if minSizeChanged {
|
|||
|
node.minSize = minSize
|
|||
|
if node.parent != nil {
|
|||
|
parentNeedingUpdate = node.parent
|
|||
|
} else {
|
|||
|
windowNeedsMinSizeUpdate = true
|
|||
|
c.RUnlock()
|
|||
|
size := obj.Size()
|
|||
|
c.RLock()
|
|||
|
expectedSize := minSize.Max(size)
|
|||
|
if expectedSize != size && size != csize {
|
|||
|
c.RUnlock()
|
|||
|
obj.Resize(expectedSize)
|
|||
|
c.RLock()
|
|||
|
} else {
|
|||
|
c.updateLayout(obj)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
c.WalkTrees(nil, ensureMinSize)
|
|||
|
|
|||
|
shouldResize := windowNeedsMinSizeUpdate && (csize.Width < min.Width || csize.Height < min.Height)
|
|||
|
if shouldResize {
|
|||
|
c.RUnlock()
|
|||
|
c.impl.Resize(csize.Max(min))
|
|||
|
c.RLock()
|
|||
|
}
|
|||
|
return windowNeedsMinSizeUpdate
|
|||
|
}
|
|||
|
|
|||
|
// Focus makes the provided item focused.
|
|||
|
func (c *Canvas) Focus(obj fyne.Focusable) {
|
|||
|
focusMgr := c.focusManager()
|
|||
|
if focusMgr != nil && focusMgr.Focus(obj) { // fast path – probably >99.9% of all cases
|
|||
|
if c.OnFocus != nil {
|
|||
|
c.OnFocus(obj)
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
c.RLock()
|
|||
|
focusMgrs := append([]*app.FocusManager{c.contentFocusMgr, c.menuFocusMgr}, c.overlays.ListFocusManagers()...)
|
|||
|
c.RUnlock()
|
|||
|
|
|||
|
for _, mgr := range focusMgrs {
|
|||
|
if mgr == nil {
|
|||
|
continue
|
|||
|
}
|
|||
|
if focusMgr != mgr {
|
|||
|
if mgr.Focus(obj) {
|
|||
|
if c.OnFocus != nil {
|
|||
|
c.OnFocus(obj)
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
fyne.LogError("Failed to focus object which is not part of the canvas’ content, menu or overlays.", nil)
|
|||
|
}
|
|||
|
|
|||
|
// Focused returns the current focused object.
|
|||
|
func (c *Canvas) Focused() fyne.Focusable {
|
|||
|
mgr := c.focusManager()
|
|||
|
if mgr == nil {
|
|||
|
return nil
|
|||
|
}
|
|||
|
return mgr.Focused()
|
|||
|
}
|
|||
|
|
|||
|
// FocusGained signals to the manager that its content got focus.
|
|||
|
// Valid only on Desktop.
|
|||
|
func (c *Canvas) FocusGained() {
|
|||
|
mgr := c.focusManager()
|
|||
|
if mgr == nil {
|
|||
|
return
|
|||
|
}
|
|||
|
mgr.FocusGained()
|
|||
|
}
|
|||
|
|
|||
|
// FocusLost signals to the manager that its content lost focus.
|
|||
|
// Valid only on Desktop.
|
|||
|
func (c *Canvas) FocusLost() {
|
|||
|
mgr := c.focusManager()
|
|||
|
if mgr == nil {
|
|||
|
return
|
|||
|
}
|
|||
|
mgr.FocusLost()
|
|||
|
}
|
|||
|
|
|||
|
// FocusNext focuses the next focusable item.
|
|||
|
func (c *Canvas) FocusNext() {
|
|||
|
mgr := c.focusManager()
|
|||
|
if mgr == nil {
|
|||
|
return
|
|||
|
}
|
|||
|
mgr.FocusNext()
|
|||
|
}
|
|||
|
|
|||
|
// FocusPrevious focuses the previous focusable item.
|
|||
|
func (c *Canvas) FocusPrevious() {
|
|||
|
mgr := c.focusManager()
|
|||
|
if mgr == nil {
|
|||
|
return
|
|||
|
}
|
|||
|
mgr.FocusPrevious()
|
|||
|
}
|
|||
|
|
|||
|
// FreeDirtyTextures frees dirty textures and returns the number of freed textures.
|
|||
|
func (c *Canvas) FreeDirtyTextures() (freed uint64) {
|
|||
|
freeObject := func(object fyne.CanvasObject) {
|
|||
|
freeWalked := func(obj fyne.CanvasObject, _ fyne.Position, _ fyne.Position, _ fyne.Size) bool {
|
|||
|
// No image refresh while recursing to avoid double texture upload.
|
|||
|
if _, ok := obj.(*canvas.Image); ok {
|
|||
|
return false
|
|||
|
}
|
|||
|
if c.painter != nil {
|
|||
|
c.painter.Free(obj)
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
// Image.Refresh will trigger a refresh specific to the object, while recursing on parent widget would just lead to
|
|||
|
// a double texture upload.
|
|||
|
if img, ok := object.(*canvas.Image); ok {
|
|||
|
if c.painter != nil {
|
|||
|
c.painter.Free(img)
|
|||
|
}
|
|||
|
} else {
|
|||
|
driver.WalkCompleteObjectTree(object, freeWalked, nil)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Within a frame, refresh tasks are requested from the Refresh method,
|
|||
|
// and we desire to clear out all requested operations within a frame.
|
|||
|
// See https://github.com/fyne-io/fyne/issues/2548.
|
|||
|
tasksToDo := c.refreshQueue.Len()
|
|||
|
|
|||
|
shouldFilterDuplicates := (tasksToDo > 200) // filtering has overhead, not worth enabling for few tasks
|
|||
|
var refreshSet map[fyne.CanvasObject]struct{}
|
|||
|
if shouldFilterDuplicates {
|
|||
|
refreshSet = make(map[fyne.CanvasObject]struct{})
|
|||
|
}
|
|||
|
|
|||
|
for c.refreshQueue.Len() > 0 {
|
|||
|
object := c.refreshQueue.Out()
|
|||
|
if !shouldFilterDuplicates {
|
|||
|
freed++
|
|||
|
freeObject(object)
|
|||
|
} else {
|
|||
|
refreshSet[object] = struct{}{}
|
|||
|
tasksToDo--
|
|||
|
if tasksToDo == 0 {
|
|||
|
shouldFilterDuplicates = false // stop collecting messages to avoid starvation
|
|||
|
for object := range refreshSet {
|
|||
|
freed++
|
|||
|
freeObject(object)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if c.painter != nil {
|
|||
|
cache.RangeExpiredTexturesFor(c.impl, c.painter.Free)
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// Initialize initializes the canvas.
|
|||
|
func (c *Canvas) Initialize(impl SizeableCanvas, onOverlayChanged func()) {
|
|||
|
c.impl = impl
|
|||
|
c.refreshQueue = async.NewCanvasObjectQueue()
|
|||
|
c.overlays = &overlayStack{
|
|||
|
OverlayStack: internal.OverlayStack{
|
|||
|
OnChange: onOverlayChanged,
|
|||
|
Canvas: impl,
|
|||
|
},
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ObjectTrees return canvas object trees.
|
|||
|
//
|
|||
|
// This function uses lock.
|
|||
|
func (c *Canvas) ObjectTrees() []fyne.CanvasObject {
|
|||
|
c.RLock()
|
|||
|
var content, menu fyne.CanvasObject
|
|||
|
if c.contentTree != nil && c.contentTree.root != nil {
|
|||
|
content = c.contentTree.root.obj
|
|||
|
}
|
|||
|
if c.menuTree != nil && c.menuTree.root != nil {
|
|||
|
menu = c.menuTree.root.obj
|
|||
|
}
|
|||
|
c.RUnlock()
|
|||
|
trees := make([]fyne.CanvasObject, 0, len(c.Overlays().List())+2)
|
|||
|
trees = append(trees, content)
|
|||
|
if menu != nil {
|
|||
|
trees = append(trees, menu)
|
|||
|
}
|
|||
|
trees = append(trees, c.Overlays().List()...)
|
|||
|
return trees
|
|||
|
}
|
|||
|
|
|||
|
// Overlays returns the overlay stack.
|
|||
|
func (c *Canvas) Overlays() fyne.OverlayStack {
|
|||
|
// we don't need to lock here, because overlays never changes
|
|||
|
return c.overlays
|
|||
|
}
|
|||
|
|
|||
|
// Painter returns the canvas painter.
|
|||
|
func (c *Canvas) Painter() gl.Painter {
|
|||
|
return c.painter
|
|||
|
}
|
|||
|
|
|||
|
// Refresh refreshes a canvas object.
|
|||
|
func (c *Canvas) Refresh(obj fyne.CanvasObject) {
|
|||
|
walkNeeded := false
|
|||
|
switch obj.(type) {
|
|||
|
case *fyne.Container:
|
|||
|
walkNeeded = true
|
|||
|
case fyne.Widget:
|
|||
|
walkNeeded = true
|
|||
|
}
|
|||
|
|
|||
|
if walkNeeded {
|
|||
|
driver.WalkCompleteObjectTree(obj, func(co fyne.CanvasObject, p1, p2 fyne.Position, s fyne.Size) bool {
|
|||
|
if i, ok := co.(*canvas.Image); ok {
|
|||
|
i.Refresh()
|
|||
|
}
|
|||
|
return false
|
|||
|
}, nil)
|
|||
|
}
|
|||
|
|
|||
|
c.refreshQueue.In(obj)
|
|||
|
c.SetDirty()
|
|||
|
}
|
|||
|
|
|||
|
// RemoveShortcut removes a shortcut from the canvas.
|
|||
|
func (c *Canvas) RemoveShortcut(shortcut fyne.Shortcut) {
|
|||
|
c.shortcut.RemoveShortcut(shortcut)
|
|||
|
}
|
|||
|
|
|||
|
// SetContentTreeAndFocusMgr sets content tree and focus manager.
|
|||
|
//
|
|||
|
// This function does not use the canvas lock.
|
|||
|
func (c *Canvas) SetContentTreeAndFocusMgr(content fyne.CanvasObject) {
|
|||
|
c.contentTree = &renderCacheTree{root: &RenderCacheNode{obj: content}}
|
|||
|
var focused fyne.Focusable
|
|||
|
if c.contentFocusMgr != nil {
|
|||
|
focused = c.contentFocusMgr.Focused() // keep old focus if possible
|
|||
|
}
|
|||
|
c.contentFocusMgr = app.NewFocusManager(content)
|
|||
|
if focused != nil {
|
|||
|
c.contentFocusMgr.Focus(focused)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// CheckDirtyAndClear returns true if the canvas is dirty and
|
|||
|
// clears the dirty state atomically.
|
|||
|
func (c *Canvas) CheckDirtyAndClear() bool {
|
|||
|
return atomic.SwapUint32(&c.dirty, 0) != 0
|
|||
|
}
|
|||
|
|
|||
|
// SetDirty sets canvas dirty flag atomically.
|
|||
|
func (c *Canvas) SetDirty() {
|
|||
|
atomic.AddUint32(&c.dirty, 1)
|
|||
|
}
|
|||
|
|
|||
|
// SetMenuTreeAndFocusMgr sets menu tree and focus manager.
|
|||
|
//
|
|||
|
// This function does not use the canvas lock.
|
|||
|
func (c *Canvas) SetMenuTreeAndFocusMgr(menu fyne.CanvasObject) {
|
|||
|
c.menuTree = &renderCacheTree{root: &RenderCacheNode{obj: menu}}
|
|||
|
if menu != nil {
|
|||
|
c.menuFocusMgr = app.NewFocusManager(menu)
|
|||
|
} else {
|
|||
|
c.menuFocusMgr = nil
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// SetMobileWindowHeadTree sets window head tree.
|
|||
|
//
|
|||
|
// This function does not use the canvas lock.
|
|||
|
func (c *Canvas) SetMobileWindowHeadTree(head fyne.CanvasObject) {
|
|||
|
c.mWindowHeadTree = &renderCacheTree{root: &RenderCacheNode{obj: head}}
|
|||
|
}
|
|||
|
|
|||
|
// SetPainter sets the canvas painter.
|
|||
|
func (c *Canvas) SetPainter(p gl.Painter) {
|
|||
|
c.painter = p
|
|||
|
}
|
|||
|
|
|||
|
// TypedShortcut handle the registered shortcut.
|
|||
|
func (c *Canvas) TypedShortcut(shortcut fyne.Shortcut) {
|
|||
|
c.shortcut.TypedShortcut(shortcut)
|
|||
|
}
|
|||
|
|
|||
|
// Unfocus unfocuses all the objects in the canvas.
|
|||
|
func (c *Canvas) Unfocus() {
|
|||
|
mgr := c.focusManager()
|
|||
|
if mgr == nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if mgr.Focus(nil) && c.OnUnfocus != nil {
|
|||
|
c.OnUnfocus()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// WalkTrees walks over the trees.
|
|||
|
func (c *Canvas) WalkTrees(
|
|||
|
beforeChildren func(*RenderCacheNode, fyne.Position),
|
|||
|
afterChildren func(*RenderCacheNode, fyne.Position),
|
|||
|
) {
|
|||
|
c.walkTree(c.contentTree, beforeChildren, afterChildren)
|
|||
|
if c.mWindowHeadTree != nil && c.mWindowHeadTree.root.obj != nil {
|
|||
|
c.walkTree(c.mWindowHeadTree, beforeChildren, afterChildren)
|
|||
|
}
|
|||
|
if c.menuTree != nil && c.menuTree.root.obj != nil {
|
|||
|
c.walkTree(c.menuTree, beforeChildren, afterChildren)
|
|||
|
}
|
|||
|
for _, tree := range c.overlays.renderCaches {
|
|||
|
if tree != nil {
|
|||
|
c.walkTree(tree, beforeChildren, afterChildren)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func (c *Canvas) focusManager() *app.FocusManager {
|
|||
|
if focusMgr := c.overlays.TopFocusManager(); focusMgr != nil {
|
|||
|
return focusMgr
|
|||
|
}
|
|||
|
c.RLock()
|
|||
|
defer c.RUnlock()
|
|||
|
if c.isMenuActive() {
|
|||
|
return c.menuFocusMgr
|
|||
|
}
|
|||
|
return c.contentFocusMgr
|
|||
|
}
|
|||
|
|
|||
|
func (c *Canvas) isMenuActive() bool {
|
|||
|
if c.menuTree == nil || c.menuTree.root == nil || c.menuTree.root.obj == nil {
|
|||
|
return false
|
|||
|
}
|
|||
|
menu := c.menuTree.root.obj
|
|||
|
if am, ok := menu.(activatableMenu); ok {
|
|||
|
return am.IsActive()
|
|||
|
}
|
|||
|
return true
|
|||
|
}
|
|||
|
|
|||
|
func (c *Canvas) walkTree(
|
|||
|
tree *renderCacheTree,
|
|||
|
beforeChildren func(*RenderCacheNode, fyne.Position),
|
|||
|
afterChildren func(*RenderCacheNode, fyne.Position),
|
|||
|
) {
|
|||
|
tree.Lock()
|
|||
|
defer tree.Unlock()
|
|||
|
var node, parent, prev *RenderCacheNode
|
|||
|
node = tree.root
|
|||
|
|
|||
|
bc := func(obj fyne.CanvasObject, pos fyne.Position, _ fyne.Position, _ fyne.Size) bool {
|
|||
|
if node != nil && node.obj != obj {
|
|||
|
if parent.firstChild == node {
|
|||
|
parent.firstChild = nil
|
|||
|
}
|
|||
|
node = nil
|
|||
|
}
|
|||
|
if node == nil {
|
|||
|
node = &RenderCacheNode{parent: parent, obj: obj}
|
|||
|
if parent.firstChild == nil {
|
|||
|
parent.firstChild = node
|
|||
|
} else {
|
|||
|
prev.nextSibling = node
|
|||
|
}
|
|||
|
}
|
|||
|
if prev != nil && prev.parent != parent {
|
|||
|
prev = nil
|
|||
|
}
|
|||
|
|
|||
|
if beforeChildren != nil {
|
|||
|
beforeChildren(node, pos)
|
|||
|
}
|
|||
|
|
|||
|
parent = node
|
|||
|
node = parent.firstChild
|
|||
|
return false
|
|||
|
}
|
|||
|
ac := func(obj fyne.CanvasObject, pos fyne.Position, _ fyne.CanvasObject) {
|
|||
|
node = parent
|
|||
|
parent = node.parent
|
|||
|
if prev != nil && prev.parent != parent {
|
|||
|
prev.nextSibling = nil
|
|||
|
}
|
|||
|
|
|||
|
if afterChildren != nil {
|
|||
|
afterChildren(node, pos)
|
|||
|
}
|
|||
|
|
|||
|
prev = node
|
|||
|
node = node.nextSibling
|
|||
|
}
|
|||
|
driver.WalkVisibleObjectTree(tree.root.obj, bc, ac)
|
|||
|
}
|
|||
|
|
|||
|
// RenderCacheNode represents a node in a render cache tree.
|
|||
|
type RenderCacheNode struct {
|
|||
|
// structural data
|
|||
|
firstChild *RenderCacheNode
|
|||
|
nextSibling *RenderCacheNode
|
|||
|
obj fyne.CanvasObject
|
|||
|
parent *RenderCacheNode
|
|||
|
// cache data
|
|||
|
minSize fyne.Size
|
|||
|
// painterData is some data from the painter associated with the drawed node
|
|||
|
// it may for instance point to a GL texture
|
|||
|
// it should free all associated resources when released
|
|||
|
// i.e. it should not simply be a texture reference integer
|
|||
|
painterData interface{}
|
|||
|
}
|
|||
|
|
|||
|
// Obj returns the node object.
|
|||
|
func (r *RenderCacheNode) Obj() fyne.CanvasObject {
|
|||
|
return r.obj
|
|||
|
}
|
|||
|
|
|||
|
type activatableMenu interface {
|
|||
|
IsActive() bool
|
|||
|
}
|
|||
|
|
|||
|
type overlayStack struct {
|
|||
|
internal.OverlayStack
|
|||
|
|
|||
|
propertyLock sync.RWMutex
|
|||
|
renderCaches []*renderCacheTree
|
|||
|
}
|
|||
|
|
|||
|
func (o *overlayStack) Add(overlay fyne.CanvasObject) {
|
|||
|
if overlay == nil {
|
|||
|
return
|
|||
|
}
|
|||
|
o.propertyLock.Lock()
|
|||
|
defer o.propertyLock.Unlock()
|
|||
|
o.add(overlay)
|
|||
|
}
|
|||
|
|
|||
|
func (o *overlayStack) Remove(overlay fyne.CanvasObject) {
|
|||
|
if overlay == nil || len(o.List()) == 0 {
|
|||
|
return
|
|||
|
}
|
|||
|
o.propertyLock.Lock()
|
|||
|
defer o.propertyLock.Unlock()
|
|||
|
o.remove(overlay)
|
|||
|
}
|
|||
|
|
|||
|
func (o *overlayStack) add(overlay fyne.CanvasObject) {
|
|||
|
o.renderCaches = append(o.renderCaches, &renderCacheTree{root: &RenderCacheNode{obj: overlay}})
|
|||
|
o.OverlayStack.Add(overlay)
|
|||
|
}
|
|||
|
|
|||
|
func (o *overlayStack) remove(overlay fyne.CanvasObject) {
|
|||
|
o.OverlayStack.Remove(overlay)
|
|||
|
overlayCount := len(o.List())
|
|||
|
o.renderCaches[overlayCount] = nil // release memory reference to removed element
|
|||
|
o.renderCaches = o.renderCaches[:overlayCount]
|
|||
|
}
|
|||
|
|
|||
|
type renderCacheTree struct {
|
|||
|
sync.RWMutex
|
|||
|
root *RenderCacheNode
|
|||
|
}
|
|||
|
|
|||
|
func (c *Canvas) updateLayout(objToLayout fyne.CanvasObject) {
|
|||
|
switch cont := objToLayout.(type) {
|
|||
|
case *fyne.Container:
|
|||
|
if cont.Layout != nil {
|
|||
|
layout := cont.Layout
|
|||
|
objects := cont.Objects
|
|||
|
c.RUnlock()
|
|||
|
layout.Layout(objects, cont.Size())
|
|||
|
c.RLock()
|
|||
|
}
|
|||
|
case fyne.Widget:
|
|||
|
renderer := cache.Renderer(cont)
|
|||
|
c.RUnlock()
|
|||
|
renderer.Layout(cont.Size())
|
|||
|
c.RLock()
|
|||
|
}
|
|||
|
}
|