242 lines
5.7 KiB
Go
242 lines
5.7 KiB
Go
package glfw
|
|
|
|
import (
|
|
"runtime"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/internal/app"
|
|
"fyne.io/fyne/v2/internal/async"
|
|
"fyne.io/fyne/v2/internal/cache"
|
|
"fyne.io/fyne/v2/internal/driver/common"
|
|
"fyne.io/fyne/v2/internal/painter"
|
|
"fyne.io/fyne/v2/internal/scale"
|
|
)
|
|
|
|
type funcData struct {
|
|
f func()
|
|
done chan struct{} // Zero allocation signalling channel
|
|
}
|
|
|
|
// channel for queuing functions on the main thread
|
|
var funcQueue = async.NewUnboundedChan[funcData]()
|
|
var running, drained atomic.Bool
|
|
|
|
// Arrange that main.main runs on main thread.
|
|
func init() {
|
|
runtime.LockOSThread()
|
|
async.SetMainGoroutine()
|
|
}
|
|
|
|
// force a function f to run on the main thread
|
|
func runOnMain(f func()) {
|
|
runOnMainWithWait(f, true)
|
|
}
|
|
|
|
// force a function f to run on the main thread and specify if we should wait for it to return
|
|
func runOnMainWithWait(f func(), wait bool) {
|
|
// If we are on main before app run just execute - otherwise add it to the main queue and wait.
|
|
// We also need to run it as-is if the app is in the process of shutting down as the queue will be stopped.
|
|
if (!running.Load() && async.IsMainGoroutine()) || drained.Load() {
|
|
f()
|
|
return
|
|
}
|
|
|
|
if wait {
|
|
done := common.DonePool.Get()
|
|
defer common.DonePool.Put(done)
|
|
|
|
funcQueue.In() <- funcData{f: f, done: done}
|
|
<-done
|
|
} else {
|
|
funcQueue.In() <- funcData{f: f}
|
|
}
|
|
}
|
|
|
|
func (d *gLDriver) drawSingleFrame() {
|
|
refreshed := false
|
|
for _, win := range d.windowList() {
|
|
w := win.(*window)
|
|
if w.closing {
|
|
continue
|
|
}
|
|
|
|
// CheckDirtyAndClear must be checked after visibility,
|
|
// because when a window becomes visible, it could be
|
|
// showing old content without a dirty flag set to true.
|
|
// Do the clear if and only if the window is visible.
|
|
if !w.visible || !w.canvas.CheckDirtyAndClear() {
|
|
// Window hidden or not being redrawn, mark canvasForObject
|
|
// cache alive if it hasn't been done recently
|
|
// n.b. we need to make sure threshold is a bit *after*
|
|
// time.Now() - CacheDuration()
|
|
threshold := time.Now().Add(10*time.Second - cache.ValidDuration)
|
|
if w.lastWalkedTime.Before(threshold) {
|
|
w.canvas.WalkTrees(nil, func(node *common.RenderCacheNode, _ fyne.Position) {
|
|
// marks canvas for object cache entry alive
|
|
_ = cache.GetCanvasForObject(node.Obj())
|
|
// marks renderer cache entry alive
|
|
if wid, ok := node.Obj().(fyne.Widget); ok {
|
|
_, _ = cache.CachedRenderer(wid)
|
|
}
|
|
})
|
|
w.lastWalkedTime = time.Now()
|
|
}
|
|
continue
|
|
}
|
|
|
|
w.RunWithContext(func() {
|
|
if w.driver.repaintWindow(w) {
|
|
refreshed = true
|
|
}
|
|
})
|
|
}
|
|
cache.Clean(refreshed)
|
|
}
|
|
|
|
func (d *gLDriver) runGL() {
|
|
if !running.CompareAndSwap(false, true) {
|
|
return // Run was called twice.
|
|
}
|
|
|
|
d.init()
|
|
if d.trayStart != nil {
|
|
d.trayStart()
|
|
}
|
|
|
|
fyne.CurrentApp().Settings().AddListener(func(set fyne.Settings) {
|
|
painter.ClearFontCache()
|
|
cache.ResetThemeCaches()
|
|
app.ApplySettingsWithCallback(set, fyne.CurrentApp(), func(w fyne.Window) {
|
|
c, ok := w.Canvas().(*glCanvas)
|
|
if !ok {
|
|
return
|
|
}
|
|
c.applyThemeOutOfTreeObjects()
|
|
c.reloadScale()
|
|
})
|
|
})
|
|
|
|
if f := fyne.CurrentApp().Lifecycle().(*app.Lifecycle).OnStarted(); f != nil {
|
|
f()
|
|
}
|
|
|
|
eventTick := time.NewTicker(time.Second / 60)
|
|
for {
|
|
select {
|
|
case <-d.done:
|
|
eventTick.Stop()
|
|
d.Terminate()
|
|
l := fyne.CurrentApp().Lifecycle().(*app.Lifecycle)
|
|
if f := l.OnStopped(); f != nil {
|
|
l.QueueEvent(f)
|
|
}
|
|
|
|
// as we are shutting down make sure we drain the pending funcQueue and close it out.
|
|
for len(funcQueue.Out()) > 0 {
|
|
f := <-funcQueue.Out()
|
|
if f.done != nil {
|
|
f.done <- struct{}{}
|
|
}
|
|
}
|
|
drained.Store(true)
|
|
funcQueue.Close()
|
|
return
|
|
case f := <-funcQueue.Out():
|
|
f.f()
|
|
if f.done != nil {
|
|
f.done <- struct{}{}
|
|
}
|
|
case <-eventTick.C:
|
|
d.pollEvents()
|
|
for i := 0; i < len(d.windows); i++ {
|
|
w := d.windows[i].(*window)
|
|
if w.viewport == nil {
|
|
continue
|
|
}
|
|
|
|
if w.viewport.ShouldClose() {
|
|
d.destroyWindow(w, i)
|
|
i-- // Trailing windows are moved forward one step.
|
|
continue
|
|
}
|
|
|
|
expand := w.shouldExpand
|
|
fullScreen := w.fullScreen
|
|
|
|
if expand && !fullScreen {
|
|
w.fitContent()
|
|
shouldExpand := w.shouldExpand
|
|
w.shouldExpand = false
|
|
view := w.viewport
|
|
|
|
if shouldExpand && runtime.GOOS != "js" {
|
|
view.SetSize(w.shouldWidth, w.shouldHeight)
|
|
}
|
|
}
|
|
}
|
|
|
|
d.animation.TickAnimations()
|
|
d.drawSingleFrame()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *gLDriver) destroyWindow(w *window, index int) {
|
|
w.visible = false
|
|
w.viewport.Destroy()
|
|
w.destroy(d)
|
|
|
|
if index < len(d.windows)-1 {
|
|
copy(d.windows[index:], d.windows[index+1:])
|
|
}
|
|
d.windows[len(d.windows)-1] = nil
|
|
d.windows = d.windows[:len(d.windows)-1]
|
|
|
|
if len(d.windows) == 0 {
|
|
d.Quit()
|
|
}
|
|
}
|
|
|
|
func (d *gLDriver) repaintWindow(w *window) bool {
|
|
canvas := w.canvas
|
|
freed := false
|
|
if canvas.EnsureMinSize() {
|
|
w.shouldExpand = true
|
|
}
|
|
freed = canvas.FreeDirtyTextures() > 0
|
|
|
|
updateGLContext(w)
|
|
canvas.paint(canvas.Size())
|
|
|
|
view := w.viewport
|
|
visible := w.visible
|
|
|
|
if view != nil && visible {
|
|
view.SwapBuffers()
|
|
}
|
|
|
|
// mark that we have walked the window and don't
|
|
// need to walk it again to mark caches alive
|
|
w.lastWalkedTime = time.Now()
|
|
return freed
|
|
}
|
|
|
|
// refreshWindow requests that the specified window be redrawn
|
|
func refreshWindow(w *window) {
|
|
w.canvas.SetDirty()
|
|
}
|
|
|
|
func updateGLContext(w *window) {
|
|
canvas := w.canvas
|
|
size := canvas.Size()
|
|
|
|
// w.width and w.height are not correct if we are maximised, so figure from canvas
|
|
winWidth := float32(scale.ToScreenCoordinate(canvas, size.Width)) * canvas.texScale
|
|
winHeight := float32(scale.ToScreenCoordinate(canvas, size.Height)) * canvas.texScale
|
|
|
|
canvas.Painter().SetFrameBufferScale(canvas.texScale)
|
|
canvas.Painter().SetOutputSize(int(winWidth), int(winHeight))
|
|
}
|