fyne icon indicating copy to clipboard operation
fyne copied to clipboard

App freezes when rapidly modifying content of container

Open roffe opened this issue 2 months ago • 5 comments

Checklist

  • [X] I have searched the issue tracker for open issues that relate to the same problem, before opening a new one.
  • [X] This issue only relates to a single bug. I will open new issues for any other problems.

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

roffe avatar Apr 20 '24 20:04 roffe