diff --git a/internal/cache/text.go b/internal/cache/text.go index 0cbf754e9f..84381fafac 100644 --- a/internal/cache/text.go +++ b/internal/cache/text.go @@ -1,6 +1,7 @@ package cache import ( + "image/color" "sync" "time" @@ -19,10 +20,16 @@ type fontMetric struct { } type fontSizeEntry struct { - text string - size float32 - style fyne.TextStyle - custom string + Text string + Size float32 + Style fyne.TextStyle + Source string +} + +type FontCacheEntry struct { + fontSizeEntry + + Color color.Color } // GetFontMetrics looks up a calculated size and baseline required for the specified text parameters. diff --git a/internal/cache/texture_common.go b/internal/cache/texture_common.go index abfd54f644..be0dce90b1 100644 --- a/internal/cache/texture_common.go +++ b/internal/cache/texture_common.go @@ -13,8 +13,17 @@ func DeleteTexture(obj fyne.CanvasObject) { textures.Delete(obj) } +// GetTextTexture gets cached texture for a text run. +func GetTextTexture(ent FontCacheEntry) (TextureType, bool) { + return load(ent) +} + // GetTexture gets cached texture. func GetTexture(obj fyne.CanvasObject) (TextureType, bool) { + return load(obj) +} + +func load(obj any) (TextureType, bool) { t, ok := textures.Load(obj) if t == nil || !ok { return NoTexture, false @@ -31,6 +40,17 @@ func GetTexture(obj fyne.CanvasObject) (TextureType, bool) { func RangeExpiredTexturesFor(canvas fyne.Canvas, f func(fyne.CanvasObject)) { now := timeNow() textures.Range(func(key, value any) bool { + if _, ok := key.(FontCacheEntry); ok { + tinfo := value.(*textureInfo) + + // just free text directly when that string/style combo is done + if tinfo.isExpired(now) && tinfo.canvas == canvas { + textures.Delete(key) + tinfo.textFree() + } + + return true + } obj, tinfo := key.(fyne.CanvasObject), value.(*textureInfo) if tinfo.isExpired(now) && tinfo.canvas == canvas { f(obj) @@ -40,11 +60,16 @@ func RangeExpiredTexturesFor(canvas fyne.Canvas, f func(fyne.CanvasObject)) { } // RangeTexturesFor range over the textures for the specified canvas. +// It will not return the texture for a `canvas.Text` as their render lifecycle is handled separately. // // Note: If this is used to free textures, then it should be called inside a current // gl context to ensure textures are deleted from gl. func RangeTexturesFor(canvas fyne.Canvas, f func(fyne.CanvasObject)) { textures.Range(func(key, value any) bool { + if _, ok := key.(FontCacheEntry); ok { + return true // do nothing, text cache lives outside the scope of an object + } + obj, tinfo := key.(fyne.CanvasObject), value.(*textureInfo) if tinfo.canvas == canvas { f(obj) @@ -53,9 +78,21 @@ func RangeTexturesFor(canvas fyne.Canvas, f func(fyne.CanvasObject)) { }) } +// SetTextTexture sets cached texture for a text run. +func SetTextTexture(ent FontCacheEntry, texture TextureType, canvas fyne.Canvas, free func()) { + store(ent, texture, canvas, free) +} + // SetTexture sets cached texture. func SetTexture(obj fyne.CanvasObject, texture TextureType, canvas fyne.Canvas) { + store(obj, texture, canvas, nil) +} + +func store(obj any, texture TextureType, canvas fyne.Canvas, free func()) { texInfo := &textureInfo{texture: texture} + if free != nil { + texInfo.textFree = free + } texInfo.canvas = canvas texInfo.setAlive() textures.Store(obj, texInfo) diff --git a/internal/cache/texture_desktop.go b/internal/cache/texture_desktop.go index 26062eb7e5..9627731d54 100644 --- a/internal/cache/texture_desktop.go +++ b/internal/cache/texture_desktop.go @@ -10,7 +10,9 @@ var NoTexture = TextureType(0) type textureInfo struct { textureCacheBase - texture TextureType + + texture TextureType + textFree func() } // IsValid will return true if the passed texture is potentially a texture diff --git a/internal/cache/texture_gomobile.go b/internal/cache/texture_gomobile.go index 4b0d89cfd7..14ea8cc74e 100644 --- a/internal/cache/texture_gomobile.go +++ b/internal/cache/texture_gomobile.go @@ -11,7 +11,9 @@ var NoTexture = gl.Texture{0} type textureInfo struct { textureCacheBase - texture TextureType + + texture TextureType + textFree func() } // IsValid will return true if the passed texture is potentially a texture diff --git a/internal/cache/texture_goxjs.go b/internal/cache/texture_goxjs.go index 17669ea596..246f9021b6 100644 --- a/internal/cache/texture_goxjs.go +++ b/internal/cache/texture_goxjs.go @@ -2,7 +2,7 @@ package cache -import gl "github.com/fyne-io/gl-js" +import "github.com/fyne-io/gl-js" // TextureType represents an uploaded GL texture type TextureType = gl.Texture @@ -11,7 +11,9 @@ var NoTexture = gl.NoTexture type textureInfo struct { textureCacheBase - texture TextureType + + texture TextureType + textFree func() } // IsValid will return true if the passed texture is potentially a texture diff --git a/internal/painter/gl/texture.go b/internal/painter/gl/texture.go index 207ee6cd12..79dedf704b 100644 --- a/internal/painter/gl/texture.go +++ b/internal/painter/gl/texture.go @@ -33,6 +33,30 @@ func (p *painter) freeTexture(obj fyne.CanvasObject) { } func (p *painter) getTexture(object fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture) (Texture, error) { + if t, ok := object.(*canvas.Text); ok { + custom := "" + if t.FontSource != nil { + custom = t.FontSource.Name() + } + ent := cache.FontCacheEntry{Color: t.Color} + ent.Text = t.Text + ent.Size = t.TextSize + ent.Style = t.TextStyle + ent.Source = custom + + texture, ok := cache.GetTextTexture(ent) + + if !ok { + tex := creator(object) + texture = cache.TextureType(tex) + cache.SetTextTexture(ent, texture, p.canvas, func() { + p.ctx.DeleteTexture(tex) + }) + } + + return Texture(texture), nil + } + texture, ok := cache.GetTexture(object) if !ok {