| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537 |
- package common
- import (
- "sync"
- "sync/atomic"
- "fyne.io/fyne/v2"
- "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)
- }
- // EnsureMinSize ensure canvas min size.
- //
- // This function uses lock.
- func (c *Canvas) EnsureMinSize() bool {
- if c.impl.Content() == nil {
- return false
- }
- var lastParent fyne.CanvasObject
- windowNeedsMinSizeUpdate := false
- csize := c.impl.Size()
- min := c.impl.MinSize()
- ensureMinSize := func(node *RenderCacheNode) {
- obj := node.obj
- cache.SetCanvasForObject(obj, c.impl)
- if !obj.Visible() {
- return
- }
- minSize := obj.MinSize()
- minSizeChanged := node.minSize != minSize
- if minSizeChanged {
- objToLayout := obj
- node.minSize = minSize
- if node.parent != nil {
- objToLayout = node.parent.obj
- } else {
- windowNeedsMinSizeUpdate = true
- size := obj.Size()
- expectedSize := minSize.Max(size)
- if expectedSize != size && size != csize {
- objToLayout = nil
- obj.Resize(expectedSize)
- }
- }
- if objToLayout != lastParent {
- updateLayout(lastParent)
- lastParent = objToLayout
- }
- }
- }
- c.WalkTrees(nil, ensureMinSize)
- shouldResize := windowNeedsMinSizeUpdate && (csize.Width < min.Width || csize.Height < min.Height)
- if shouldResize {
- c.impl.Resize(csize.Max(min))
- }
- if lastParent != nil {
- c.RLock()
- updateLayout(lastParent)
- c.RUnlock()
- }
- 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 {
- if c.painter != nil {
- c.painter.Free(obj)
- }
- return false
- }
- 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)
- }
- }
- }
- }
- cache.RangeExpiredTexturesFor(c.impl, func(obj fyne.CanvasObject) {
- if c.painter != nil {
- c.painter.Free(obj)
- }
- })
- 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) {
- 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),
- ) {
- 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),
- ) {
- 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, _ fyne.CanvasObject) {
- node = parent
- parent = node.parent
- if prev != nil && prev.parent != parent {
- prev.nextSibling = nil
- }
- if afterChildren != nil {
- afterChildren(node)
- }
- 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 = o.renderCaches[:overlayCount]
- }
- type renderCacheTree struct {
- sync.RWMutex
- root *RenderCacheNode
- }
- func updateLayout(objToLayout fyne.CanvasObject) {
- switch cont := objToLayout.(type) {
- case *fyne.Container:
- if cont.Layout != nil {
- cont.Layout.Layout(cont.Objects, cont.Size())
- }
- case fyne.Widget:
- cache.Renderer(cont).Layout(cont.Size())
- }
- }
|