fyne-io / fyne

Cross platform GUI toolkit in Go inspired by Material Design
https://fyne.io/
Other
24.65k stars 1.37k forks source link

App freezes when rapidly modifying content of container #4799

Open roffe opened 5 months ago

roffe commented 5 months ago

Checklist

Describe the bug

I've for quiet some time been trying to chase down a application freeze when users are switching between preferences in my app it sometimes randomly just freezes.

I've managed to create a program that replicates the behaviour that makes the main window freeze and become unresponsive.

How to reproduce

Run attached program code. window should freeze within 3-4 seconds

Screenshots

image

Example code

package main

import (
    "encoding/json"
    "fmt"
    "image/color"
    "log"
    "math/rand/v2"
    "strconv"
    "sync"
    "time"

    "fyne.io/fyne/v2"
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/canvas"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/theme"
    "fyne.io/fyne/v2/widget"
    symbol "github.com/roffe/ecusymbol"
    "github.com/roffe/txlogger/pkg/layout"
)

var Map = make(map[string]string)

func init() {
    log.SetFlags(log.LstdFlags | log.Lshortfile)
    Map["T5 Dash"] = `[{"Name":"Rpm","Number":86,"SramOffset":4194,"Address":0,"Length":2,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Medeltrot","Number":80,"SramOffset":4150,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Ign_angle","Number":168,"SramOffset":4228,"Address":0,"Length":2,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Lufttemp","Number":75,"SramOffset":4145,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"P_medel","Number":320,"SramOffset":10751,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Max_tryck","Number":312,"SramOffset":10747,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Regl_tryck","Number":315,"SramOffset":10748,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":0.01},{"Name":"PWM_ut10","Number":318,"SramOffset":10754,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"P_fak","Number":313,"SramOffset":11046,"Address":0,"Length":2,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"I_fak","Number":314,"SramOffset":11044,"Address":0,"Length":2,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"D_fak","Number":311,"SramOffset":11042,"Address":0,"Length":2,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"AD_EGR","Number":9,"SramOffset":4118,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Kyl_temp","Number":72,"SramOffset":4141,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Bil_hast","Number":60,"SramOffset":4123,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Knock_offset1234","Number":131,"SramOffset":4236,"Address":0,"Length":2,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Batt_volt","Number":61,"SramOffset":4122,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Insptid_ms10","Number":64,"SramOffset":4190,"Address":0,"Length":2,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1},{"Name":"Lambdaint","Number":73,"SramOffset":4143,"Address":0,"Length":1,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1}]`
    Map["T7 Dash"] = `[{"Name":"ActualIn.n_Engine","Number":3462,"SramOffset":0,"Address":15788900,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":1},{"Name":"Out.X_AccPedal","Number":3672,"SramOffset":0,"Address":15789336,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.1,"Unit":"%"},{"Name":"In.v_Vehicle","Number":3409,"SramOffset":0,"Address":15788816,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.1,"Unit":"Km/h"},{"Name":"ActualIn.T_Engine","Number":3469,"SramOffset":0,"Address":15788916,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":1},{"Name":"ActualIn.T_AirInlet","Number":3470,"SramOffset":0,"Address":15788918,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":1},{"Name":"IgnProt.fi_Offset","Number":3045,"SramOffset":0,"Address":15787464,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.1,"Unit":"Degrees"},{"Name":"Out.fi_Ignition","Number":3686,"SramOffset":0,"Address":15789366,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.1,"Unit":"° BTDC"},{"Name":"Out.PWM_BoostCntrl","Number":3645,"SramOffset":0,"Address":15789300,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.1,"Unit":"%"},{"Name":"ActualIn.p_AirInlet","Number":3472,"SramOffset":0,"Address":15788922,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.001},{"Name":"In.p_AirBefThrottle","Number":3395,"SramOffset":0,"Address":15788788,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.001,"Unit":"Bar"},{"Name":"ECMStat.p_Diff","Number":3759,"SramOffset":0,"Address":15789466,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.001,"Unit":"Bar"},{"Name":"MAF.m_AirInlet","Number":452,"SramOffset":0,"Address":15775882,"Length":2,"Mask":0,"Type":32,"ExtendedType":0,"Correctionfactor":1,"Unit":"Mg/c"},{"Name":"m_Request","Number":59,"SramOffset":0,"Address":15775190,"Length":2,"Mask":0,"Type":0,"ExtendedType":0,"Correctionfactor":1,"Unit":"Mg/c"},{"Name":"ECMStat.ST_ActiveAirDem","Number":3754,"SramOffset":0,"Address":15789448,"Length":1,"Mask":0,"Type":36,"ExtendedType":0,"Correctionfactor":1},{"Name":"DisplProt.LambdaScanner","Number":3316,"SramOffset":0,"Address":15788686,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.01},{"Name":"Lambda.LambdaInt","Number":2606,"SramOffset":0,"Address":15787098,"Length":2,"Mask":0,"Type":33,"ExtendedType":0,"Correctionfactor":0.01}]`
    Map["T8 Dash"] = `[{"Name":"ActualIn.n_Engine","Number":4181,"SramOffset":0,"Address":1066236,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":1},{"Name":"Out.X_AccPos","Number":4772,"SramOffset":0,"Address":1066522,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":0.1},{"Name":"In.v_Vehicle","Number":4024,"SramOffset":0,"Address":1065980,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":0.1,"Unit":"Km/h"},{"Name":"ActualIn.T_Engine","Number":4155,"SramOffset":0,"Address":1066180,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":1},{"Name":"ActualIn.T_AirInlet","Number":4171,"SramOffset":0,"Address":1066214,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":1},{"Name":"IgnMastProt.fi_Offset","Number":3235,"SramOffset":0,"Address":1065164,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":0.1},{"Name":"Out.fi_Ignition","Number":4870,"SramOffset":0,"Address":1066662,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":0.1,"Unit":"° BTDC"},{"Name":"Out.PWM_BoostCntrl","Number":4843,"SramOffset":0,"Address":1066622,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":0.1,"Unit":"%"},{"Name":"In.p_AirInlet","Number":4004,"SramOffset":0,"Address":1065938,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":0.001},{"Name":"ActualIn.p_AirBefThrottle","Number":4159,"SramOffset":0,"Address":1066188,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":0.001},{"Name":"MAF.m_AirInlet","Number":383,"SramOffset":0,"Address":1056888,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":1,"Unit":"Mg/c"},{"Name":"AirMassMast.m_Request","Number":260,"SramOffset":0,"Address":1056830,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":1},{"Name":"ECMStat.ST_ActiveAirDem","Number":4977,"SramOffset":0,"Address":1067070,"Length":1,"Mask":0,"Type":4,"ExtendedType":0,"Correctionfactor":1},{"Name":"Lambda.LambdaInt","Number":2729,"SramOffset":0,"Address":1064772,"Length":2,"Mask":0,"Type":1,"ExtendedType":0,"Correctionfactor":0.01}]`
}

func main() {
    a := app.NewWithID("com.roffe.freezebug")
    w := a.NewWindow("freezebug")

    updatefunc := func(s []*symbol.Symbol) {
    }
    lst := NewSymbolListWidget(w, updatefunc)
    cnt := container.NewStack(lst)
    w.SetContent(cnt)
    w.Resize(fyne.NewSize(800, 600))

    go func() {
        for {
            time.Sleep(300 * time.Millisecond)
            syms := loadpref()
            lst.LoadSymbols(syms...)
            cnt.Refresh()
        }
    }()

    w.ShowAndRun()
}

func loadpref() []*symbol.Symbol {
    var data string
    switch rand.IntN(3) {
    case 0:
        log.Println("T5 Dash")
        data = Map["T5 Dash"]
    case 1:
        log.Println("T7 Dash")
        data = Map["T7 Dash"]
    case 2:
        log.Println("T8 Dash")
        data = Map["T8 Dash"]
    default:
        log.Println("oops")
    }
    var symbols []*symbol.Symbol
    if err := json.Unmarshal([]byte(data), &symbols); err != nil {
        panic(err)
    }
    return symbols
}

type SymbolListWidget struct {
    widget.BaseWidget
    symbols    []*symbol.Symbol
    entryMap   map[string]*SymbolWidgetEntry
    entries    []*SymbolWidgetEntry
    container  *fyne.Container
    border     *fyne.Container
    scroll     *container.Scroll
    mu         sync.Mutex
    updateBars bool
    onUpdate   func([]*symbol.Symbol)
    w          fyne.Window
}

func NewSymbolListWidget(w fyne.Window, updateFunc func([]*symbol.Symbol), symbols ...*symbol.Symbol) *SymbolListWidget {
    sl := &SymbolListWidget{
        entryMap: make(map[string]*SymbolWidgetEntry),
        onUpdate: updateFunc,
        w:        w,
    }
    sl.ExtendBaseWidget(sl)
    sl.render()
    sl.LoadSymbols(symbols...)
    return sl
}

func (s *SymbolListWidget) render() {
    s.container = container.NewVBox()
    s.scroll = container.NewVScroll(s.container)

    name := widget.NewLabel("Name")
    name.TextStyle = fyne.TextStyle{Bold: true}

    value := widget.NewLabel("Value")
    value.TextStyle = fyne.TextStyle{Bold: true}

    num := widget.NewLabel("#")
    num.TextStyle = fyne.TextStyle{Bold: true}

    typ := widget.NewLabel("Type")
    typ.TextStyle = fyne.TextStyle{Bold: true}

    factor := widget.NewLabel("Factor")
    factor.TextStyle = fyne.TextStyle{Bold: true}

    s.border = container.NewBorder(
        container.New(&layout.RatioContainer{Widths: sz},
            widget.NewLabel(""),
            name,
            value,
            num,
            typ,
            factor,
        ),
        nil,
        nil,
        nil,
        s.scroll,
    )
}

func (s *SymbolListWidget) UpdateBars(enabled bool) {
    s.updateBars = enabled
}

func (s *SymbolListWidget) SetValue(name string, value float64) {
    val, found := s.entryMap[name]
    if found {
        val.value = value
        if value < val.min {
            val.min = value
        } else if value > val.max {
            val.max = value
        }
        if s.updateBars {
            factor := float32((value - val.min) / (val.max - val.min))
            col := GetColorInterpolation(val.min, val.max, value)
            col.A = 30
            val.valueBar.FillColor = col
            val.valueBar.Resize(fyne.NewSize(factor*100, 26))
        }
        switch val.symbol.Correctionfactor {
        case 1:
            val.symbolValue.SetText(strconv.FormatFloat(value, 'f', 0, 64))
            return
        case 0.1:
            val.symbolValue.SetText(strconv.FormatFloat(value, 'f', 1, 64))
            return
        case 0.01:
            val.symbolValue.SetText(strconv.FormatFloat(value, 'f', 2, 64))
            return
        case 0.001:
            val.symbolValue.SetText(strconv.FormatFloat(value, 'f', 3, 64))
            return
        default:
            val.symbolValue.SetText(strconv.FormatFloat(value, 'f', 2, 64))
            return
        }
    }
}

func (s *SymbolListWidget) Disable() {
    for _, e := range s.entries {
        e.symbolCorrectionfactor.Disable()
        e.deleteBTN.Disable()
    }
}

func (s *SymbolListWidget) Enable() {
    for _, e := range s.entries {
        e.symbolCorrectionfactor.Enable()
        e.deleteBTN.Enable()
    }
}

func (s *SymbolListWidget) Add(symbols ...*symbol.Symbol) {
    s.mu.Lock()
    defer s.mu.Unlock()
    for _, sym := range symbols {
        if _, found := s.entryMap[sym.Name]; found {
            continue
        }
        deleteFunc := func(sw *SymbolWidgetEntry) {
            for i, e := range s.entries {
                if e == sw {
                    s.mu.Lock()
                    defer s.mu.Unlock()
                    s.symbols = append(s.symbols[:i], s.symbols[i+1:]...)
                    s.entries = append(s.entries[:i], s.entries[i+1:]...)
                    delete(s.entryMap, sw.symbol.Name)
                    s.container.Remove(sw)
                    s.scroll.Refresh()
                    s.onUpdate(s.symbols)
                    break
                }
            }
        }
        entry := NewSymbolWidgetEntry(s.w, sym, deleteFunc)
        s.symbols = append(s.symbols, sym)
        s.entries = append(s.entries, entry)
        s.container.Objects = append(s.container.Objects, entry)
        s.entryMap[sym.Name] = entry
    }
    s.onUpdate(s.symbols)
}

func (s *SymbolListWidget) Clear() {
    for _, e := range s.entries {
        e.symbolValue.SetText("---")
    }
}

func (s *SymbolListWidget) clear() {
    s.mu.Lock()
    defer s.mu.Unlock()
    //s.container.Refresh()
    //s.border.Refresh()
    s.container.RemoveAll()
    s.symbols = []*symbol.Symbol{}
    s.entries = []*SymbolWidgetEntry{}
    s.entryMap = make(map[string]*SymbolWidgetEntry)
    s.onUpdate(s.symbols)
}

func (s *SymbolListWidget) LoadSymbols(symbols ...*symbol.Symbol) {
    s.clear()
    s.Add(symbols...)
}

func (s *SymbolListWidget) Count() int {
    s.mu.Lock()
    defer s.mu.Unlock()
    return len(s.symbols)
}

func (s *SymbolListWidget) Symbols() []*symbol.Symbol {
    s.mu.Lock()
    defer s.mu.Unlock()
    out := make([]*symbol.Symbol, len(s.symbols))
    copy(out, s.symbols)
    return out
}

func (s *SymbolListWidget) CreateRenderer() fyne.WidgetRenderer {
    swr := &SymbolListWidgetRenderer{
        sl: s,
    }
    return swr
}

type SymbolListWidgetRenderer struct {
    sl *SymbolListWidget
}

func (sr *SymbolListWidgetRenderer) Layout(size fyne.Size) {
    sr.sl.border.Resize(size)
}

func (sr *SymbolListWidgetRenderer) MinSize() fyne.Size {
    var width float32
    var height float32
    for _, en := range sr.sl.entries {
        sz := en.MinSize()
        if sz.Width > width {
            width = sz.Width
        }
        height += sz.Height
    }
    return fyne.NewSize(width, min(height, 200))
}

func (sr *SymbolListWidgetRenderer) Refresh() {
    for _, e := range sr.sl.entries {
        e.Refresh()
    }
}

func (sr *SymbolListWidgetRenderer) Destroy() {
}

func (sr *SymbolListWidgetRenderer) Objects() []fyne.CanvasObject {
    return []fyne.CanvasObject{sr.sl.border}
}

type SymbolWidgetEntry struct {
    widget.BaseWidget
    symbol                 *symbol.Symbol
    copyName               *widget.Button
    symbolName             *widget.Label
    symbolValue            *widget.Label
    symbolNumber           *widget.Label
    symbolType             *widget.Label
    symbolCorrectionfactor *widget.Entry
    deleteBTN              *widget.Button
    valueBar               *canvas.Rectangle

    container *fyne.Container

    deleteFunc func(*SymbolWidgetEntry)

    //valueSet bool
    value    float64
    min, max float64
}

func NewSymbolWidgetEntry(w fyne.Window, sym *symbol.Symbol, deleteFunc func(*SymbolWidgetEntry)) *SymbolWidgetEntry {
    sw := &SymbolWidgetEntry{
        symbol:     sym,
        deleteFunc: deleteFunc,
    }
    sw.ExtendBaseWidget(sw)
    sw.copyName = widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() {
        w.Clipboard().SetContent(sym.Name)
    })
    sw.symbolName = widget.NewLabel(sw.symbol.Name)
    sw.symbolValue = widget.NewLabel("---")
    sw.symbolNumber = widget.NewLabel(strconv.Itoa(sw.symbol.Number))
    sw.symbolType = widget.NewLabel(fmt.Sprintf("%02X", sw.symbol.Type))
    sw.symbolCorrectionfactor = widget.NewEntry()

    sw.symbolCorrectionfactor.OnChanged = func(s string) {
        f, err := strconv.ParseFloat(s, 64)
        if err != nil {
            return
        }
        sw.symbol.Correctionfactor = f
    }

    sw.SetCorrectionFactor(sym.Correctionfactor)

    sw.deleteBTN = widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
        if sw.deleteFunc != nil {
            sw.deleteFunc(sw)
        }
    })

    sw.valueBar = canvas.NewRectangle(color.RGBA{255, 0, 0, 255})

    sw.container = container.NewWithoutLayout(
        sw.copyName,
        sw.valueBar,
        sw.symbolName,
        sw.symbolValue,
        sw.symbolNumber,
        sw.symbolType,
        sw.symbolCorrectionfactor,
        sw.deleteBTN,
    )
    return sw
}

func (sw *SymbolWidgetEntry) SetCorrectionFactor(f float64) {
    sw.symbol.Correctionfactor = f
    switch f {
    case 1:
        sw.symbolCorrectionfactor.SetText(strconv.FormatFloat(f, 'f', 0, 64))
    case 0.1:
        sw.symbolCorrectionfactor.SetText(strconv.FormatFloat(f, 'f', 1, 64))
    case 0.01:
        sw.symbolCorrectionfactor.SetText(strconv.FormatFloat(f, 'f', 2, 64))
    case 0.001:
        sw.symbolCorrectionfactor.SetText(strconv.FormatFloat(f, 'f', 3, 64))
    default:
        sw.symbolCorrectionfactor.SetText(strconv.FormatFloat(f, 'f', 4, 64))
    }
}

func (sw *SymbolWidgetEntry) CreateRenderer() fyne.WidgetRenderer {
    swr := &SymbolWidgetEntryRenderer{
        sw: sw,
    }
    return swr
}

type SymbolWidgetEntryRenderer struct {
    sw *SymbolWidgetEntry
}

var sz = []float32{
    .04, // copy
    .32, // name
    .18, // value
    .12, // number
    .14, // correctionfactor
    .08, // type
    .04, // deletebtn
}

func sumFloat32(a []float32) float32 {
    var sum float32
    for _, v := range a {
        sum += v
    }
    return sum
}

func (sr *SymbolWidgetEntryRenderer) Layout(size fyne.Size) {
    sw := sr.sw
    padd := size.Width * ((1.0 - sumFloat32(sz)) / float32(len(sz)))
    sw.copyName.Resize(fyne.NewSize(size.Width*sz[0], size.Height))
    sw.symbolName.Resize(fyne.NewSize(size.Width*sz[1], size.Height))
    sw.symbolValue.Resize(fyne.NewSize(size.Width*sz[2], size.Height))
    sw.symbolNumber.Resize(fyne.NewSize(size.Width*sz[3], size.Height))
    sw.symbolType.Resize(fyne.NewSize(size.Width*sz[4], size.Height))
    sw.symbolCorrectionfactor.Resize(fyne.NewSize(size.Width*sz[5], size.Height))
    sw.deleteBTN.Resize(fyne.NewSize(size.Width*sz[6], size.Height))

    var x float32

    sw.copyName.Move(fyne.NewPos(x, 0))
    x += sw.copyName.Size().Width + padd

    sw.symbolName.Move(fyne.NewPos(x, 0))
    x += sw.symbolName.Size().Width + padd

    sw.symbolValue.Move(fyne.NewPos(x, 0))
    sw.valueBar.Move(fyne.NewPos(x, 6))
    x += sw.symbolValue.Size().Width + padd

    sw.symbolNumber.Move(fyne.NewPos(x, 0))
    x += sw.symbolNumber.Size().Width + padd

    sw.symbolType.Move(fyne.NewPos(x, 0))
    x += sw.symbolType.Size().Width + padd

    sw.symbolCorrectionfactor.Move(fyne.NewPos(x, 0))
    x += sw.symbolCorrectionfactor.Size().Width + padd

    sw.deleteBTN.Move(fyne.NewPos(x, 0))
}

func (sr *SymbolWidgetEntryRenderer) MinSize() fyne.Size {
    sw := sr.sw
    var width float32
    var height float32 = sw.symbolName.MinSize().Height
    width += sw.copyName.MinSize().Width
    width += sw.symbolName.MinSize().Width
    width += sw.symbolValue.MinSize().Width
    width += sw.symbolNumber.MinSize().Width
    width += sw.symbolCorrectionfactor.MinSize().Width
    width += sw.deleteBTN.MinSize().Width
    return fyne.NewSize(width, height)
}

func (sr *SymbolWidgetEntryRenderer) Refresh() {
    sr.sw.symbolName.Refresh()
    sr.sw.symbolValue.Refresh()
    sr.sw.symbolNumber.Refresh()
    sr.sw.symbolType.Refresh()
    sr.sw.symbolCorrectionfactor.Refresh()
}

func (sr *SymbolWidgetEntryRenderer) Destroy() {
}

func (sr *SymbolWidgetEntryRenderer) Objects() []fyne.CanvasObject {
    return []fyne.CanvasObject{sr.sw.container}
}

func GetColorInterpolation(min, max, value float64) color.RGBA {
    //log.Println("getColorInterpolation", min, max, value)
    // Normalize the value to a 0-1 range
    t := (value - min) / (max - min)
    divider := .5
    var r, g, b float64
    if t < divider { // Green to Yellow interpolation
        r = lerp(0, 1, t/divider)
        g = 1
    } else { // Yellow to Red interpolation
        r = 1
        g = lerp(1, 0, (t-divider)/(1-divider))
    }
    b = 0
    // Convert from 0-1 range to 0-255 for color.RGBA
    return color.RGBA{
        R: uint8(r * 255),
        G: uint8(g * 255),
        B: uint8(b * 255),
        A: 255,
    }
}

func lerp(a, b, t float64) float64 {
    return a + (b-a)*t
}

Fyne version

fyne.io/fyne/v2 v2.4.6-0.20240418153625-66b892df8f5e

Go compiler version

go version go1.22.0 windows/amd64

Operating system and version

Windows 11

Additional Information

No response

andydotxyz commented 5 months ago

Can you replicate this without your custom widget code?

roffe commented 5 months ago

i've managed to make some more discoveries., i added a bunch of debug output and it's never inside the widget code it freezes.

however! it seems related to mouse somehow... if i start the test program and don't put the mouse pointer inside the window it will run forever. but as soon as i select the window and move the mouse inside it then it freezes within a few seconds

roffe commented 5 months ago

also the go thread to swap content keeps running the whole time so the go runtime is not dead when this happends

andydotxyz commented 5 months ago

also the go thread to swap content keeps running the whole time so the go runtime is not dead when this happends

That makes sense - it would be a guess that the event handling thread is deadlocked. I'm not sure where the debug was added, but the best step forward is to check which goroutines are hung and on what lock (either it's a deadlock or some other kind of wait I think...)

Jacalz commented 5 months ago

Send a SIGQUIT to the app and you should get a nice clean stack trace