From 92b8e8c47f7394ce53ffc26712a25f67ea86eaa3 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 3 Jun 2026 22:49:34 +0200 Subject: [PATCH 001/102] attempt to refactor the logwriter to have a more clean surface --- pkg/datalogger/baselogger.go | 31 ++++++++++ pkg/datalogger/channel.go | 90 +++++++++++++++++++++++++++++ pkg/datalogger/datalogger.go | 2 +- pkg/datalogger/log.go | 4 +- pkg/datalogger/log_bpl.go | 29 +++------- pkg/datalogger/log_csv.go | 40 +++---------- pkg/datalogger/log_txl.go | 29 ++-------- pkg/datalogger/t5logger.go | 12 ++-- pkg/datalogger/t7logger.go | 46 +++++---------- pkg/datalogger/t8logger.go | 14 ++--- pkg/datalogger/txbridgelogger_t5.go | 12 ++-- pkg/datalogger/txbridgelogger_t7.go | 47 +++++++++++---- pkg/datalogger/txbridgelogger_t8.go | 10 ++-- 13 files changed, 221 insertions(+), 145 deletions(-) create mode 100644 pkg/datalogger/channel.go diff --git a/pkg/datalogger/baselogger.go b/pkg/datalogger/baselogger.go index 0de07ce1..56d3bff3 100644 --- a/pkg/datalogger/baselogger.go +++ b/pkg/datalogger/baselogger.go @@ -114,6 +114,37 @@ func (bl *BaseLogger) calculateCompensatedTimestamp() time.Time { return bl.firstTime.Add(time.Duration(bl.currtimestamp-bl.firstTimestamp) * time.Millisecond) } +// appendExtraSysvars appends the wideband and AD scanner pseudo-symbol names to +// a sysvar order slice when they are active, in the same order they are +// written to the log. +func (bl *BaseLogger) appendExtraSysvars(order []string) []string { + if bl.lamb != nil { + order = append(order, EXTERNALWBLSYM) + } + if bl.WidebandConfig.ADScanner && bl.WidebandConfig.Name == "ECU" { + order = append(order, LAMBDAADSCANNER) + } + return order +} + +// buildChannels assembles the standard log layout: every name in order becomes +// an asynchronous sysvar channel, followed by every polled symbol (Number >= +// 0) as a symbol channel. Symbols with a negative number are either replaced by +// a broadcast/derived sysvar or sourced elsewhere and are not log columns. +func (bl *BaseLogger) buildChannels(order []string) []Channel { + channels := make([]Channel, 0, len(order)+len(bl.Symbols)) + for _, name := range order { + channels = append(channels, newSysvarChannel(bl.sysvars, name)) + } + for _, sym := range bl.Symbols { + if sym.Number < 0 { + continue + } + channels = append(channels, newSymbolChannel(sym)) + } + return channels +} + func (bl *BaseLogger) setupWBL(ctx context.Context, cl *gocan.Client) error { cfg := &wbl.WBLConfig{ WBLType: bl.Config.WidebandConfig.Name, diff --git a/pkg/datalogger/channel.go b/pkg/datalogger/channel.go new file mode 100644 index 00000000..e347be14 --- /dev/null +++ b/pkg/datalogger/channel.go @@ -0,0 +1,90 @@ +package datalogger + +import ( + "math" + "strconv" + + symbol "github.com/roffe/ecusymbol" +) + +// Channel is one ordered column in a log. It pairs a name with a way to read +// its current value and a way to format that value as text. +// +// Whether the value comes from a polled ECU symbol, an asynchronous broadcast +// frame, the wideband controller or anywhere else is captured in the read +// closure and decided once when logging starts. Writers therefore only ever +// see a flat, ordered list of named channels and never need to know about +// sysvars vs symbols or sync vs async values. +type Channel struct { + Name string + read func() float64 + format func(float64) string +} + +// Value returns the current value of the channel. +func (c *Channel) Value() float64 { return c.read() } + +// String returns the current value formatted as text. +func (c *Channel) String() string { return c.format(c.read()) } + +// newSysvarChannel reads the latest value of a named entry in the shared sysvars +// map. Used for asynchronously updated values such as T7 broadcast frames, the +// wideband and AD scanner lambda and other derived values. +func newSysvarChannel(sysvars *ThreadSafeMap, name string) Channel { + return Channel{ + Name: name, + read: func() float64 { return sysvars.Get(name) }, + format: sysvarFormat(name), + } +} + +// newSymbolChannel reads the value decoded into the symbol on the most recent +// payload Read. +func newSymbolChannel(sym *symbol.Symbol) Channel { + return Channel{ + Name: sym.Name, + read: sym.Float64, + format: symbolFormat(sym.Correctionfactor), + } +} + +func newFunctionChannel(name string, read func() float64) Channel { + return Channel{ + Name: name, + read: read, + format: func(float64) string { return strconv.FormatFloat(read(), 'f', 2, 64) }, + } +} + +// sysvarFormat mirrors the precision rules the text writers used for sysvars: +// whole numbers print without decimals, the external wideband lambda prints +// with three decimals and everything else with two. +func sysvarFormat(name string) func(float64) string { + return func(v float64) string { + prec := 2 + switch { + case v == math.Trunc(v): + prec = 0 + case name == EXTERNALWBLSYM: + prec = 3 + } + return strconv.FormatFloat(v, 'f', prec, 64) + } +} + +// symbolFormat mirrors symbol.StringValue: the number of decimals is derived +// from the symbol correction factor. +func symbolFormat(correctionfactor float64) func(float64) string { + prec := 0 + switch correctionfactor { + case 0.1: + prec = 1 + case 0.01, 0.0078125, 0.0009765625, 0.00390625, 0.004: + prec = 2 + case 0.001: + prec = 3 + } + return func(v float64) string { + return strconv.FormatFloat(v, 'f', prec, 64) + } +} diff --git a/pkg/datalogger/datalogger.go b/pkg/datalogger/datalogger.go index 72eb00ab..c65a89bf 100644 --- a/pkg/datalogger/datalogger.go +++ b/pkg/datalogger/datalogger.go @@ -17,7 +17,7 @@ const ( ) type LogWriter interface { - Write(sysvars *ThreadSafeMap, sysvarOrder []string, vars []*symbol.Symbol, ts time.Time) error + Write(ts time.Time, channels []Channel) error Close() error } diff --git a/pkg/datalogger/log.go b/pkg/datalogger/log.go index cae00a60..d1fc4d00 100644 --- a/pkg/datalogger/log.go +++ b/pkg/datalogger/log.go @@ -7,7 +7,6 @@ import ( "strings" "time" - symbol "github.com/roffe/ecusymbol" "github.com/roffe/txlogger/pkg/common" ) @@ -70,7 +69,6 @@ func NewTXBinWriter(f *os.File) *TXBinWriter { } } -func (t *TXBinWriter) Write(sysvars *ThreadSafeMap, sysvarOrder []string, vars []*symbol.Symbol, ts time.Time) error { - +func (t *TXBinWriter) Write(ts time.Time, channels []Channel) error { return nil } diff --git a/pkg/datalogger/log_bpl.go b/pkg/datalogger/log_bpl.go index c223928e..3fd958f2 100644 --- a/pkg/datalogger/log_bpl.go +++ b/pkg/datalogger/log_bpl.go @@ -6,8 +6,6 @@ import ( "math" "os" "time" - - symbol "github.com/roffe/ecusymbol" ) // BPL (Binary Packed Logfile) on-disk layout. All multi-byte integers are @@ -48,9 +46,9 @@ type BPLWriter struct { buf []byte // reusable per-record encode buffer } -func (b *BPLWriter) Write(sysvars *ThreadSafeMap, sysvarOrder []string, vars []*symbol.Symbol, ts time.Time) error { +func (b *BPLWriter) Write(ts time.Time, channels []Channel) error { if !b.headerWritten { - if err := b.writeHeader(vars, sysvarOrder); err != nil { + if err := b.writeHeader(channels); err != nil { return err } } @@ -59,15 +57,8 @@ func (b *BPLWriter) Write(sysvars *ThreadSafeMap, sysvarOrder []string, vars []* binary.LittleEndian.PutUint64(b.buf[off:], uint64(ts.UnixNano())) off += 8 - for _, k := range sysvarOrder { - binary.LittleEndian.PutUint32(b.buf[off:], math.Float32bits(float32(sysvars.Get(k)))) - off += 4 - } - for _, va := range vars { - if va.Number < 0 { - continue - } - binary.LittleEndian.PutUint32(b.buf[off:], math.Float32bits(float32(va.Float64()))) + for i := range channels { + binary.LittleEndian.PutUint32(b.buf[off:], math.Float32bits(float32(channels[i].Value()))) off += 4 } @@ -75,14 +66,10 @@ func (b *BPLWriter) Write(sysvars *ThreadSafeMap, sysvarOrder []string, vars []* return err } -func (b *BPLWriter) writeHeader(vars []*symbol.Symbol, sysvarOrder []string) error { - cols := make([]string, 0, len(sysvarOrder)+len(vars)) - cols = append(cols, sysvarOrder...) - for _, va := range vars { - if va.Number < 0 { - continue - } - cols = append(cols, va.Name) +func (b *BPLWriter) writeHeader(channels []Channel) error { + cols := make([]string, 0, len(channels)) + for i := range channels { + cols = append(cols, channels[i].Name) } if _, err := b.bw.WriteString(bplMagic); err != nil { diff --git a/pkg/datalogger/log_csv.go b/pkg/datalogger/log_csv.go index bc247f01..03352f94 100644 --- a/pkg/datalogger/log_csv.go +++ b/pkg/datalogger/log_csv.go @@ -2,12 +2,9 @@ package datalogger import ( "encoding/csv" - "math" "os" - "strconv" "time" - symbol "github.com/roffe/ecusymbol" "github.com/roffe/txlogger/pkg/logfile" ) @@ -22,46 +19,27 @@ type CSVWriter struct { file *os.File headerWritten bool cw *csv.Writer - precission int } -func (c *CSVWriter) Write(sysvars *ThreadSafeMap, sysvarOrder []string, vars []*symbol.Symbol, ts time.Time) error { +func (c *CSVWriter) Write(ts time.Time, channels []Channel) error { if !c.headerWritten { - if err := c.writeHeader(vars, sysvarOrder); err != nil { + if err := c.writeHeader(channels); err != nil { return err } } - var record []string + record := make([]string, 0, len(channels)+1) record = append(record, ts.Format(logfile.ISONICO)) - for _, k := range sysvarOrder { - val := sysvars.Get(k) - if val == math.Trunc(val) { - c.precission = 0 - } else if k == "Lambda.External" { - c.precission = 3 - } else { - c.precission = 2 - } - record = append(record, strconv.FormatFloat(val, 'f', c.precission, 64)) - } - for _, va := range vars { - if va.Number < 0 { - continue - } - record = append(record, va.StringValue()) + for i := range channels { + record = append(record, channels[i].String()) } return c.cw.Write(record) } -func (c *CSVWriter) writeHeader(vars []*symbol.Symbol, sysvarOrder []string) error { - var header []string +func (c *CSVWriter) writeHeader(channels []Channel) error { + header := make([]string, 0, len(channels)+1) header = append(header, "Time") - header = append(header, sysvarOrder...) - for _, va := range vars { - if va.Number < 0 { - continue - } - header = append(header, va.Name) + for i := range channels { + header = append(header, channels[i].Name) } c.headerWritten = true return c.cw.Write(header) diff --git a/pkg/datalogger/log_txl.go b/pkg/datalogger/log_txl.go index 922c861e..e323cf3c 100644 --- a/pkg/datalogger/log_txl.go +++ b/pkg/datalogger/log_txl.go @@ -1,12 +1,8 @@ package datalogger import ( - "math" "os" - "strconv" "time" - - symbol "github.com/roffe/ecusymbol" ) func NewTXLWriter(f *os.File) *TXWriter { @@ -16,33 +12,16 @@ func NewTXLWriter(f *os.File) *TXWriter { } type TXWriter struct { - file *os.File - precission int + file *os.File } -func (t *TXWriter) Write(sysvars *ThreadSafeMap, sysvarOrder []string, vars []*symbol.Symbol, ts time.Time) error { +func (t *TXWriter) Write(ts time.Time, channels []Channel) error { _, err := t.file.Write([]byte(ts.Format("02-01-2006 15:04:05.999") + "|")) if err != nil { return err } - for _, k := range sysvarOrder { - val := sysvars.Get(k) - if val == math.Trunc(val) { - t.precission = 0 - } else if k == "Lambda.External" { - t.precission = 3 - } else { - t.precission = 2 - } - if _, err := t.file.Write([]byte(k + "=" + replaceDot(strconv.FormatFloat(val, 'f', t.precission, 64)) + "|")); err != nil { - return err - } - } - for _, va := range vars { - if va.Number < 0 { - continue - } - if _, err := t.file.Write([]byte(va.Name + "=" + replaceDot(va.StringValue()) + "|")); err != nil { + for i := range channels { + if _, err := t.file.Write([]byte(channels[i].Name + "=" + replaceDot(channels[i].String()) + "|")); err != nil { return err } } diff --git a/pkg/datalogger/t5logger.go b/pkg/datalogger/t5logger.go index 8a828ec8..c6e606c9 100644 --- a/pkg/datalogger/t5logger.go +++ b/pkg/datalogger/t5logger.go @@ -7,7 +7,6 @@ import ( "math" "time" - symbol "github.com/roffe/ecusymbol" "github.com/roffe/gocan" "github.com/roffe/txlogger/pkg/ebus" "github.com/roffe/txlogger/pkg/t5can" @@ -57,11 +56,14 @@ func (c *T5Client) Start() error { if c.lamb != nil { defer c.lamb.Stop() - sysvarOrder = append(sysvarOrder, EXTERNALWBLSYM) } + sysvarOrder = c.appendExtraSysvars(sysvarOrder) - if c.WidebandConfig.ADScanner && c.WidebandConfig.Name == "ECU" { - sysvarOrder = append(sysvarOrder, LAMBDAADSCANNER) + // T5 decodes every value into sysvars (see newT5Converter), so all columns + // are sysvar channels. + channels := make([]Channel, len(sysvarOrder)) + for i, name := range sysvarOrder { + channels[i] = newSysvarChannel(c.sysvars, name) } tx := cl.Subscribe(ctx, gocan.SystemMsgDataResponse) @@ -131,7 +133,7 @@ func (c *T5Client) Start() error { ebus.Publish(EXTERNALWBLSYM, lambda) } - if err := c.lw.Write(c.sysvars, sysvarOrder, []*symbol.Symbol{}, ts); err != nil { + if err := c.lw.Write(ts, channels); err != nil { c.OnMessage("failed to write log: " + err.Error()) return } diff --git a/pkg/datalogger/t7logger.go b/pkg/datalogger/t7logger.go index fe7f2b04..8dde731b 100644 --- a/pkg/datalogger/t7logger.go +++ b/pkg/datalogger/t7logger.go @@ -122,12 +122,9 @@ func (c *T7Client) Start() error { if c.lamb != nil { defer c.lamb.Stop() - sysvarOrder = append(sysvarOrder, EXTERNALWBLSYM) } - if c.WidebandConfig.ADScanner && c.WidebandConfig.Name == "ECU" { - sysvarOrder = append(sysvarOrder, LAMBDAADSCANNER) - } + sysvarOrder = c.appendExtraSysvars(sysvarOrder) for _, sym := range c.Symbols { if c.sysvars.Exists(sym.Name) { @@ -137,6 +134,19 @@ func (c *T7Client) Start() error { } } + // Broadcast/derived values resolved above become async sysvar channels; + // the remaining symbols (Number >= 0) are polled each tick. + channels := c.buildChannels(sysvarOrder) + /* + if c.lamb != nil { + channels = append(channels, newFunctionChannel(EXTERNALWBLSYM, func() float64 { + lamb := c.lamb.GetLambda() + ebus.Publish(EXTERNALWBLSYM, lamb) + return lamb + })) + } + */ + kwp := kwp2000.New(cl) adConverter := NewWBLInterpolator(c.WidebandConfig) @@ -333,31 +343,7 @@ func (c *T7Client) Start() error { ebus.Publish(EXTERNALWBLSYM, lambda) } - /* - // New shit ----- - if c.r != nil { - var values relayserver.LogValues - for _, name := range sysvarOrder { - val := c.sysvars.Get(name) - values = append(values, relayserver.LogValue{Name: name, Value: val}) - } - for _, va := range c.Symbols { - if va.Number < 0 { - continue - } - values = append(values, relayserver.LogValue{Name: va.Name, Value: va.Float64()}) - } - if err := c.r.Send(relayserver.Message{ - Kind: relayserver.MsgTypeData, - Body: values, - }); err != nil { - c.onError() - c.OnMessage("failed to send relay message: " + err.Error()) - } - } - */ - - if err := c.lw.Write(c.sysvars, sysvarOrder, c.Symbols, timeStamp); err != nil { + if err := c.lw.Write(timeStamp, channels); err != nil { c.onError() c.OnMessage("failed to write log: " + err.Error()) } @@ -380,7 +366,7 @@ func initT7logging(ctx context.Context, kwp *kwp2000.Client, symbols []*symbol.S } if !granted { - onMessage("Security access not granted!") + return errors.New("security access not granted") } else { onMessage("Security access granted") } diff --git a/pkg/datalogger/t8logger.go b/pkg/datalogger/t8logger.go index 7415b659..579b24ea 100644 --- a/pkg/datalogger/t8logger.go +++ b/pkg/datalogger/t8logger.go @@ -67,16 +67,14 @@ func (c *T8Client) Start() error { if c.lamb != nil { defer c.lamb.Stop() - order = append(order, EXTERNALWBLSYM) - } - - if c.WidebandConfig.ADScanner && c.WidebandConfig.Name == "ECU" { - order = append(order, LAMBDAADSCANNER) } + order = c.appendExtraSysvars(order) // sort order sort.StringSlice(order).Sort() + channels := c.buildChannels(order) + opts := []gmlan.GMLanOption{gmlan.WithCanID(0x7E0), gmlan.WithRecvID(0x7E8)} if cl.AdapterName() == "ELM327" { opts = append(opts, gmlan.WithDefaultTimeout(400*time.Millisecond)) @@ -88,12 +86,12 @@ func (c *T8Client) Start() error { return fmt.Errorf("failed to init t8 logging: %w", err) } - go c.run(ctx, cl, gm, order) + go c.run(ctx, cl, gm, channels) return cl.Wait(ctx) } -func (c *T8Client) run(ctx context.Context, cl *gocan.Client, gm *gmlan.Client, order []string) { +func (c *T8Client) run(ctx context.Context, cl *gocan.Client, gm *gmlan.Client, channels []Channel) { defer cl.Close() var timeStamp time.Time @@ -215,7 +213,7 @@ func (c *T8Client) run(ctx context.Context, cl *gocan.Client, gm *gmlan.Client, c.sysvars.Set(EXTERNALWBLSYM, c.lamb.GetLambda()) } - if err := c.lw.Write(c.sysvars, order, c.Symbols, timeStamp); err != nil { + if err := c.lw.Write(timeStamp, channels); err != nil { c.onError() c.OnMessage("failed to write log: " + err.Error()) } diff --git a/pkg/datalogger/txbridgelogger_t5.go b/pkg/datalogger/txbridgelogger_t5.go index fbf2b7da..b7ea30e6 100644 --- a/pkg/datalogger/txbridgelogger_t5.go +++ b/pkg/datalogger/txbridgelogger_t5.go @@ -8,7 +8,6 @@ import ( "log" "time" - symbol "github.com/roffe/ecusymbol" "github.com/roffe/gocan" "github.com/roffe/gocan/pkg/serialcommand" "github.com/roffe/txlogger/pkg/ebus" @@ -26,11 +25,14 @@ func (c *TxBridge) t5(pctx context.Context, cl *gocan.Client) error { if c.lamb != nil { defer c.lamb.Stop() - sysvarOrder = append(sysvarOrder, EXTERNALWBLSYM) } + sysvarOrder = c.appendExtraSysvars(sysvarOrder) - if c.WidebandConfig.ADScanner && c.WidebandConfig.Name == "ECU" { - sysvarOrder = append(sysvarOrder, LAMBDAADSCANNER) + // T5 decodes every value into sysvars (see newT5Converter), so all columns + // are sysvar channels. + channels := make([]Channel, len(sysvarOrder)) + for i, name := range sysvarOrder { + channels[i] = newSysvarChannel(c.sysvars, name) } expectedPayloadSize, err := c.configureT5Symbols(cl) @@ -191,7 +193,7 @@ func (c *TxBridge) t5(pctx context.Context, cl *gocan.Client) error { ebus.Publish(EXTERNALWBLSYM, lambda) } - if err := c.lw.Write(c.sysvars, sysvarOrder, []*symbol.Symbol{}, timeStamp); err != nil { + if err := c.lw.Write(timeStamp, channels); err != nil { c.OnMessage("failed to write log: " + err.Error()) return } diff --git a/pkg/datalogger/txbridgelogger_t7.go b/pkg/datalogger/txbridgelogger_t7.go index a0e3812e..e17edc4c 100644 --- a/pkg/datalogger/txbridgelogger_t7.go +++ b/pkg/datalogger/txbridgelogger_t7.go @@ -9,6 +9,7 @@ import ( "sort" "time" + symbol "github.com/roffe/ecusymbol" "github.com/roffe/gocan" "github.com/roffe/gocan/pkg/serialcommand" "github.com/roffe/txlogger/pkg/ebus" @@ -35,12 +36,8 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { if c.lamb != nil { defer c.lamb.Stop() - sysvarOrder = append(sysvarOrder, EXTERNALWBLSYM) - } - - if c.WidebandConfig.ADScanner && c.WidebandConfig.Name == "ECU" { - sysvarOrder = append(sysvarOrder, LAMBDAADSCANNER) } + sysvarOrder = c.appendExtraSysvars(sysvarOrder) for _, sym := range c.Symbols { if c.sysvars.Exists(sym.Name) { @@ -50,6 +47,8 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { } } + channels := c.buildChannels(sysvarOrder) + kwp := kwp2000.New(cl) if err := initT7logging(ctx, kwp, c.Symbols, c.OnMessage); err != nil { return fmt.Errorf("failed to init t7 logging: %w", err) @@ -72,6 +71,35 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { adConverter := NewWBLInterpolator(c.WidebandConfig) + router := map[string]func(s *symbol.Symbol) bool{ + "IgnKnk.fi_Offset": func(s *symbol.Symbol) bool { + data := s.Bytes() + if len(data) != 8 { + return false + } + + ioffCyl1 := int16(binary.BigEndian.Uint16(data[0:2])) + ioffCyl2 := int16(binary.BigEndian.Uint16(data[2:4])) + ioffCyl3 := int16(binary.BigEndian.Uint16(data[4:6])) + ioffCyl4 := int16(binary.BigEndian.Uint16(data[6:8])) + + ebus.Publish("IgnKnk.fi_Offset.Cyl1", float64(ioffCyl1)/10) + ebus.Publish("IgnKnk.fi_Offset.Cyl2", float64(ioffCyl2)/10) + ebus.Publish("IgnKnk.fi_Offset.Cyl3", float64(ioffCyl3)/10) + ebus.Publish("IgnKnk.fi_Offset.Cyl4", float64(ioffCyl4)/10) + return true + }, + } + + if c.WidebandConfig.ADScanner { + router[c.WidebandConfig.ADScannerSymbol] = func(s *symbol.Symbol) bool { + lambda := adConverter(s.Int()) + c.sysvars.Set(LAMBDAADSCANNER, lambda) + ebus.Publish(LAMBDAADSCANNER, lambda) + return true + } + } + go func() { defer cl.Close() defer func() { @@ -211,10 +239,9 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { c.OnMessage(err.Error()) break } - if c.WidebandConfig.ADScanner && va.Name == c.WidebandConfig.ADScannerSymbol { - lambda := adConverter(va.Int()) - c.sysvars.Set(LAMBDAADSCANNER, lambda) - ebus.Publish(LAMBDAADSCANNER, lambda) + + if fn, ok := router[va.Name]; ok && fn(va) { + continue } ebus.Publish(va.Name, va.Float64()) @@ -230,7 +257,7 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { ebus.Publish(EXTERNALWBLSYM, lambda) } - if err := c.lw.Write(c.sysvars, sysvarOrder, c.Symbols, timeStamp); err != nil { + if err := c.lw.Write(timeStamp, channels); err != nil { c.onError() c.OnMessage("failed to write log: " + err.Error()) } diff --git a/pkg/datalogger/txbridgelogger_t8.go b/pkg/datalogger/txbridgelogger_t8.go index ef945071..93cd6199 100644 --- a/pkg/datalogger/txbridgelogger_t8.go +++ b/pkg/datalogger/txbridgelogger_t8.go @@ -22,15 +22,13 @@ func (c *TxBridge) t8(pctx context.Context, cl *gocan.Client) error { order := c.sysvars.Keys() if c.lamb != nil { defer c.lamb.Stop() - order = append(order, EXTERNALWBLSYM) - } - - if c.WidebandConfig.ADScanner && c.WidebandConfig.Name == "ECU" { - order = append(order, LAMBDAADSCANNER) } + order = c.appendExtraSysvars(order) sort.StringSlice(order).Sort() + channels := c.buildChannels(order) + gm := gmlan.New(cl, 0x7e0, 0x7e8) if err := initT8Logging(ctx, gm, c.Symbols, c.OnMessage); err != nil { @@ -145,7 +143,7 @@ func (c *TxBridge) t8(pctx context.Context, cl *gocan.Client) error { ebus.Publish(EXTERNALWBLSYM, lambda) } - if err := c.lw.Write(c.sysvars, order, c.Symbols, timeStamp); err != nil { + if err := c.lw.Write(timeStamp, channels); err != nil { c.onError() c.OnMessage("failed to write log: " + err.Error()) } From 3d19825bc8635aa29f1fca358df62c2100e4e690 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 3 Jun 2026 23:53:04 +0200 Subject: [PATCH 002/102] update deps --- go.mod | 8 ++++---- go.sum | 17 ++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 972bd6d3..ed150596 100644 --- a/go.mod +++ b/go.mod @@ -16,8 +16,8 @@ require ( github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 - github.com/roffe/ecusymbol v1.1.5 - github.com/roffe/gocan v1.3.9 + github.com/roffe/ecusymbol v1.1.7 + github.com/roffe/gocan v1.4.0 go.bug.st/serial v1.6.4 golang.org/x/image v0.40.0 golang.org/x/mod v0.36.0 @@ -46,7 +46,7 @@ require ( github.com/creack/goselect v0.1.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/ebitengine/purego v0.9.1 // indirect - github.com/fatih/color v1.18.0 // indirect + github.com/fatih/color v1.19.0 // indirect github.com/fredbi/uri v1.1.1 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fyne-io/gl-js v0.2.1-0.20260315212741-029c47fd27e8 // indirect @@ -66,7 +66,7 @@ require ( github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-runewidth v0.0.17 // indirect github.com/mdlayher/netlink v1.8.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect diff --git a/go.sum b/go.sum index 74c03543..4020c764 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/ebitengine/oto/v3 v3.4.0 h1:br0PgASsEWaoWn38b2Goe7m1GKFYfNgnsjSd5Gg+/ github.com/ebitengine/oto/v3 v3.4.0/go.mod h1:IOleLVD0m+CMak3mRVwsYY8vTctQgOM0iiL6S7Ar7eI= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko= @@ -106,8 +106,8 @@ github.com/lusingander/colorpicker v0.7.5/go.mod h1:fSixgf1m1Hx7GZUTZhKfPoSrgqrL github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ= github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mdlayher/netlink v1.8.0 h1:e7XNIYJKD7hUct3Px04RuIGJbBxy1/c4nX7D5YyvvlM= @@ -136,10 +136,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/roffe/ecusymbol v1.1.5 h1:mL/i1k8iY85+GLKOpa7JtkyrfDeQLa++89fIGD7XmpI= -github.com/roffe/ecusymbol v1.1.5/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= -github.com/roffe/gocan v1.3.9 h1:6eQ6K4KSLqIQiYWSX4z64PLMcC3PGxZjWs0lv4+xSS8= -github.com/roffe/gocan v1.3.9/go.mod h1:AFv2PzvjSrxeyy2eJgvyDxpLMTTUf7Hx1HfIYhKySlc= +github.com/roffe/ecusymbol v1.1.7 h1:Ub6Dax1V19Jl5y3iXbuhPsPM/XCxEk77Bnal6QU43H4= +github.com/roffe/ecusymbol v1.1.7/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= +github.com/roffe/gocan v1.4.0 h1:OSs//lr4vy/ozyMPUbgZaNFVZWMeXzOsXhCujpA4WRs= +github.com/roffe/gocan v1.4.0/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -213,7 +213,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From 618e30b8317ff89023068efd169addb6d73ea777 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 5 Jun 2026 22:55:28 +0200 Subject: [PATCH 003/102] get rid of sysvar order --- pkg/datalogger/baselogger.go | 14 +++++++++----- pkg/datalogger/t5logger.go | 17 +++++++---------- pkg/datalogger/t7logger.go | 25 ++++--------------------- pkg/datalogger/t8logger.go | 9 +-------- pkg/datalogger/txbridgelogger_t5.go | 17 +++++++---------- pkg/datalogger/txbridgelogger_t7.go | 11 ++++------- pkg/datalogger/txbridgelogger_t8.go | 7 +------ 7 files changed, 33 insertions(+), 67 deletions(-) diff --git a/pkg/datalogger/baselogger.go b/pkg/datalogger/baselogger.go index 56d3bff3..bce18dc1 100644 --- a/pkg/datalogger/baselogger.go +++ b/pkg/datalogger/baselogger.go @@ -127,11 +127,15 @@ func (bl *BaseLogger) appendExtraSysvars(order []string) []string { return order } -// buildChannels assembles the standard log layout: every name in order becomes -// an asynchronous sysvar channel, followed by every polled symbol (Number >= -// 0) as a symbol channel. Symbols with a negative number are either replaced by -// a broadcast/derived sysvar or sourced elsewhere and are not log columns. -func (bl *BaseLogger) buildChannels(order []string) []Channel { +// buildChannels assembles the standard log layout: every current sysvar +// (broadcast/derived values plus the active wideband / AD scanner pseudo-symbols) +// becomes an asynchronous sysvar channel, followed by every polled symbol +// (Number >= 0) as a symbol channel. Symbols with a negative number are either +// replaced by a broadcast/derived sysvar or sourced elsewhere and are not log +// columns. Column order is whatever the channel slice yields; the writer is +// consistent across the header and every row because it iterates this slice. +func (bl *BaseLogger) buildChannels() []Channel { + order := bl.appendExtraSysvars(bl.sysvars.Keys()) channels := make([]Channel, 0, len(order)+len(bl.Symbols)) for _, name := range order { channels = append(channels, newSysvarChannel(bl.sysvars, name)) diff --git a/pkg/datalogger/t5logger.go b/pkg/datalogger/t5logger.go index c6e606c9..f8bf8712 100644 --- a/pkg/datalogger/t5logger.go +++ b/pkg/datalogger/t5logger.go @@ -44,10 +44,12 @@ func (c *T5Client) Start() error { defer t.Stop() t5 := t5can.NewClient(cl) - sysvarOrder := make([]string, len(c.Symbols)) - for n, s := range c.Symbols { - sysvarOrder[n] = s.Name + // T5 decodes every value into sysvars (see newT5Converter), so all columns + // are sysvar channels. + channels := make([]Channel, 0, len(c.Symbols)+2) + for _, s := range c.Symbols { s.Correctionfactor = 0.1 + channels = append(channels, newSysvarChannel(c.sysvars, s.Name)) } if err := c.setupWBL(ctx, cl); err != nil { @@ -57,13 +59,8 @@ func (c *T5Client) Start() error { if c.lamb != nil { defer c.lamb.Stop() } - sysvarOrder = c.appendExtraSysvars(sysvarOrder) - - // T5 decodes every value into sysvars (see newT5Converter), so all columns - // are sysvar channels. - channels := make([]Channel, len(sysvarOrder)) - for i, name := range sysvarOrder { - channels[i] = newSysvarChannel(c.sysvars, name) + for _, name := range c.appendExtraSysvars(nil) { + channels = append(channels, newSysvarChannel(c.sysvars, name)) } tx := cl.Subscribe(ctx, gocan.SystemMsgDataResponse) diff --git a/pkg/datalogger/t7logger.go b/pkg/datalogger/t7logger.go index 8dde731b..ff8309fd 100644 --- a/pkg/datalogger/t7logger.go +++ b/pkg/datalogger/t7logger.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "log" - "sort" "strings" "time" @@ -97,7 +96,6 @@ func (c *T7Client) Start() error { checkBroadcast = false } - var sysvarOrder []string if checkBroadcast { bctx, bcancel := context.WithCancel(ctx) defer bcancel() @@ -105,13 +103,9 @@ func (c *T7Client) Start() error { c.OnMessage("Watching for broadcast messages") <-time.After(1550 * time.Millisecond) - sysvarOrder = c.sysvars.Keys() - sort.StringSlice(sysvarOrder).Sort() - if len(sysvarOrder) > 0 { - c.OnMessage(fmt.Sprintf("Found %s", sysvarOrder)) - } - - if len(sysvarOrder) == 0 { + if found := c.sysvars.Keys(); len(found) > 0 { + c.OnMessage(fmt.Sprintf("Found %s", found)) + } else { c.OnMessage("No broadcast messages found, stopping broadcast listener") bcancel() } @@ -124,8 +118,6 @@ func (c *T7Client) Start() error { defer c.lamb.Stop() } - sysvarOrder = c.appendExtraSysvars(sysvarOrder) - for _, sym := range c.Symbols { if c.sysvars.Exists(sym.Name) { log.Println("Skipping", sym.Name, "in broadcast") @@ -136,16 +128,7 @@ func (c *T7Client) Start() error { // Broadcast/derived values resolved above become async sysvar channels; // the remaining symbols (Number >= 0) are polled each tick. - channels := c.buildChannels(sysvarOrder) - /* - if c.lamb != nil { - channels = append(channels, newFunctionChannel(EXTERNALWBLSYM, func() float64 { - lamb := c.lamb.GetLambda() - ebus.Publish(EXTERNALWBLSYM, lamb) - return lamb - })) - } - */ + channels := c.buildChannels() kwp := kwp2000.New(cl) diff --git a/pkg/datalogger/t8logger.go b/pkg/datalogger/t8logger.go index 579b24ea..8b08fd8c 100644 --- a/pkg/datalogger/t8logger.go +++ b/pkg/datalogger/t8logger.go @@ -6,7 +6,6 @@ import ( "fmt" "log" "math" - "sort" "time" symbol "github.com/roffe/ecusymbol" @@ -59,8 +58,6 @@ func (c *T8Client) Start() error { } defer cl.Close() - order := c.sysvars.Keys() - if err := c.setupWBL(ctx, cl); err != nil { return err } @@ -68,12 +65,8 @@ func (c *T8Client) Start() error { if c.lamb != nil { defer c.lamb.Stop() } - order = c.appendExtraSysvars(order) - - // sort order - sort.StringSlice(order).Sort() - channels := c.buildChannels(order) + channels := c.buildChannels() opts := []gmlan.GMLanOption{gmlan.WithCanID(0x7E0), gmlan.WithRecvID(0x7E8)} if cl.AdapterName() == "ELM327" { diff --git a/pkg/datalogger/txbridgelogger_t5.go b/pkg/datalogger/txbridgelogger_t5.go index b7ea30e6..36fe53d6 100644 --- a/pkg/datalogger/txbridgelogger_t5.go +++ b/pkg/datalogger/txbridgelogger_t5.go @@ -17,22 +17,19 @@ func (c *TxBridge) t5(pctx context.Context, cl *gocan.Client) error { ctx, cancel := context.WithCancel(pctx) defer cancel() - sysvarOrder := make([]string, len(c.Symbols)) - for n, s := range c.Symbols { - sysvarOrder[n] = s.Name + // T5 decodes every value into sysvars (see newT5Converter), so all columns + // are sysvar channels. + channels := make([]Channel, 0, len(c.Symbols)+2) + for _, s := range c.Symbols { s.Correctionfactor = 0.1 + channels = append(channels, newSysvarChannel(c.sysvars, s.Name)) } if c.lamb != nil { defer c.lamb.Stop() } - sysvarOrder = c.appendExtraSysvars(sysvarOrder) - - // T5 decodes every value into sysvars (see newT5Converter), so all columns - // are sysvar channels. - channels := make([]Channel, len(sysvarOrder)) - for i, name := range sysvarOrder { - channels[i] = newSysvarChannel(c.sysvars, name) + for _, name := range c.appendExtraSysvars(nil) { + channels = append(channels, newSysvarChannel(c.sysvars, name)) } expectedPayloadSize, err := c.configureT5Symbols(cl) diff --git a/pkg/datalogger/txbridgelogger_t7.go b/pkg/datalogger/txbridgelogger_t7.go index e17edc4c..c8f88f63 100644 --- a/pkg/datalogger/txbridgelogger_t7.go +++ b/pkg/datalogger/txbridgelogger_t7.go @@ -6,7 +6,6 @@ import ( "encoding/binary" "fmt" "log" - "sort" "time" symbol "github.com/roffe/ecusymbol" @@ -26,18 +25,16 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { c.OnMessage("Watching for broadcast messages") <-time.After(1550 * time.Millisecond) - sysvarOrder := c.sysvars.Keys() - sort.StringSlice(sysvarOrder).Sort() - c.OnMessage(fmt.Sprintf("Found %s", sysvarOrder)) + found := c.sysvars.Keys() + c.OnMessage(fmt.Sprintf("Found %s", found)) - if len(sysvarOrder) == 0 { + if len(found) == 0 { bcancel() } if c.lamb != nil { defer c.lamb.Stop() } - sysvarOrder = c.appendExtraSysvars(sysvarOrder) for _, sym := range c.Symbols { if c.sysvars.Exists(sym.Name) { @@ -47,7 +44,7 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { } } - channels := c.buildChannels(sysvarOrder) + channels := c.buildChannels() kwp := kwp2000.New(cl) if err := initT7logging(ctx, kwp, c.Symbols, c.OnMessage); err != nil { diff --git a/pkg/datalogger/txbridgelogger_t8.go b/pkg/datalogger/txbridgelogger_t8.go index 93cd6199..b4872bd2 100644 --- a/pkg/datalogger/txbridgelogger_t8.go +++ b/pkg/datalogger/txbridgelogger_t8.go @@ -6,7 +6,6 @@ import ( "encoding/binary" "fmt" "log" - "sort" "time" "github.com/roffe/gocan" @@ -19,15 +18,11 @@ func (c *TxBridge) t8(pctx context.Context, cl *gocan.Client) error { ctx, cancel := context.WithCancel(pctx) defer cancel() - order := c.sysvars.Keys() if c.lamb != nil { defer c.lamb.Stop() } - order = c.appendExtraSysvars(order) - sort.StringSlice(order).Sort() - - channels := c.buildChannels(order) + channels := c.buildChannels() gm := gmlan.New(cl, 0x7e0, 0x7e8) From 59af7f5ba6e393bf67cb16ee3a06db3aa2889264 Mon Sep 17 00:00:00 2001 From: roffe Date: Mon, 8 Jun 2026 22:54:17 +0200 Subject: [PATCH 004/102] add pprof under a debug tag --- main.go | 1 - pprof.go | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 pprof.go diff --git a/main.go b/main.go index 51b6f337..864c732b 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,6 @@ import ( "github.com/roffe/txlogger/pkg/presets" "github.com/roffe/txlogger/pkg/theme" "github.com/roffe/txlogger/pkg/windows" - // _ "net/http/pprof" ) var ( diff --git a/pprof.go b/pprof.go new file mode 100644 index 00000000..f86b4a01 --- /dev/null +++ b/pprof.go @@ -0,0 +1,15 @@ +//go:build pprof + +package main + +import ( + "log" + "net/http" + _ "net/http/pprof" +) + +func init() { + go func() { + log.Println(http.ListenAndServe("localhost:6060", nil)) + }() +} From d7affb7139f3cd817d26635fdfad5818cc59e9db Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 20:56:30 +0200 Subject: [PATCH 005/102] move to experiments --- .../ebusmonitor/ebusmonitor.go | 0 {pkg => experiments}/eventbus/aggregators.go | 0 experiments/eventbus/bench_test.go | 140 +++++++ {pkg => experiments}/eventbus/eventbus.go | 0 experiments/eventbus/eventbus_test.go | 372 ++++++++++++++++++ {pkg => experiments}/eventbus/unbounded.go | 0 experiments/eventbus/unbounded_test.go | 109 +++++ 7 files changed, 621 insertions(+) rename {pkg/widgets => experiments}/ebusmonitor/ebusmonitor.go (100%) rename {pkg => experiments}/eventbus/aggregators.go (100%) create mode 100644 experiments/eventbus/bench_test.go rename {pkg => experiments}/eventbus/eventbus.go (100%) create mode 100644 experiments/eventbus/eventbus_test.go rename {pkg => experiments}/eventbus/unbounded.go (100%) create mode 100644 experiments/eventbus/unbounded_test.go diff --git a/pkg/widgets/ebusmonitor/ebusmonitor.go b/experiments/ebusmonitor/ebusmonitor.go similarity index 100% rename from pkg/widgets/ebusmonitor/ebusmonitor.go rename to experiments/ebusmonitor/ebusmonitor.go diff --git a/pkg/eventbus/aggregators.go b/experiments/eventbus/aggregators.go similarity index 100% rename from pkg/eventbus/aggregators.go rename to experiments/eventbus/aggregators.go diff --git a/experiments/eventbus/bench_test.go b/experiments/eventbus/bench_test.go new file mode 100644 index 00000000..e7a2ebf5 --- /dev/null +++ b/experiments/eventbus/bench_test.go @@ -0,0 +1,140 @@ +package eventbus_test + +import ( + "strconv" + "testing" + + "github.com/roffe/txlogger/experiments/eventbus" +) + +// BenchmarkPublishNoSubscribers measures the bare cost of enqueueing a message +// when nobody is listening (the run loop still drains and processes it). +func BenchmarkPublishNoSubscribers(b *testing.B) { + c := eventbus.New(nil) + defer c.Close() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.Publish("topic", float64(i)) + } +} + +// BenchmarkPublishOneSubscriber measures publish throughput with a single +// active subscriber that immediately drains its channel. +func BenchmarkPublishOneSubscriber(b *testing.B) { + c := eventbus.New(nil) + defer c.Close() + + ch := c.Subscribe("topic") + go func() { + for range ch { + } + }() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.Publish("topic", float64(i)) + } +} + +// BenchmarkPublishManySubscribers measures fan-out cost across N subscribers. +func BenchmarkPublishManySubscribers(b *testing.B) { + for _, subs := range []int{1, 4, 16, 64} { + b.Run(strconv.Itoa(subs), func(b *testing.B) { + c := eventbus.New(nil) + defer c.Close() + + for i := 0; i < subs; i++ { + ch := c.Subscribe("topic") + go func() { + for range ch { + } + }() + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.Publish("topic", float64(i)) + } + }) + } +} + +// BenchmarkPublishParallel measures publish throughput under contention from +// multiple concurrent publishers. +func BenchmarkPublishParallel(b *testing.B) { + c := eventbus.New(nil) + defer c.Close() + + ch := c.Subscribe("topic") + go func() { + for range ch { + } + }() + + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + c.Publish("topic", 1) + } + }) +} + +// BenchmarkSubscribeUnsubscribe measures the cost of the subscribe/unsubscribe +// round trip through the run loop. +func BenchmarkSubscribeUnsubscribe(b *testing.B) { + c := eventbus.New(nil) + defer c.Close() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + ch := c.Subscribe("topic") + c.Unsubscribe(ch) + } +} + +// BenchmarkAggregatorPublish measures publishing to topics watched by the +// default DIFF aggregators, exercising the aggregator index path. +func BenchmarkAggregatorPublish(b *testing.B) { + c := eventbus.New(nil) + defer c.Close() + + out := c.Subscribe("VDIFFL") + go func() { + for range out { + } + }() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.Publish("ActualIn.v_Vehicle", float64(i)) + c.Publish("ActualIn.v_Vehicle2", float64(i)+1) + } +} + +// BenchmarkUnboundedChan measures round-trip throughput of the unbounded +// channel with a concurrent consumer. +func BenchmarkUnboundedChan(b *testing.B) { + ch := eventbus.NewUnboundedChan[int]() + done := make(chan struct{}) + go func() { + for range ch.Out() { + } + close(done) + }() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + ch.In() <- i + } + b.StopTimer() + ch.Close() + <-done +} diff --git a/pkg/eventbus/eventbus.go b/experiments/eventbus/eventbus.go similarity index 100% rename from pkg/eventbus/eventbus.go rename to experiments/eventbus/eventbus.go diff --git a/experiments/eventbus/eventbus_test.go b/experiments/eventbus/eventbus_test.go new file mode 100644 index 00000000..1c000367 --- /dev/null +++ b/experiments/eventbus/eventbus_test.go @@ -0,0 +1,372 @@ +package eventbus_test + +import ( + "io" + "log" + "os" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/roffe/txlogger/experiments/eventbus" +) + +const testTimeout = 2 * time.Second + +// TestMain silences the package's drop logging ("publish channel full" etc.), +// which is expected under load and would otherwise flood benchmark output. +func TestMain(m *testing.M) { + log.SetOutput(io.Discard) + code := m.Run() + log.SetOutput(os.Stderr) + os.Exit(code) +} + +// recvWithin returns the next value from ch, failing if nothing arrives within d. +func recvWithin(t *testing.T, ch <-chan float64, d time.Duration) float64 { + t.Helper() + select { + case v, ok := <-ch: + if !ok { + t.Fatal("channel closed while waiting for a value") + } + return v + case <-time.After(d): + t.Fatal("timed out waiting for a value") + return 0 + } +} + +// publishUntilRecv repeatedly invokes pub until a value shows up on ch. Because +// Subscribe and Publish are processed asynchronously on separate channels, a +// single Publish right after Subscribe may race ahead of the subscription being +// registered. Re-publishing until delivery makes the test deterministic. pub +// must only ever publish the same value to the topic ch is subscribed to so the +// returned value is unambiguous. +func publishUntilRecv(t *testing.T, pub func(), ch <-chan float64) float64 { + t.Helper() + deadline := time.After(testTimeout) + for { + pub() + select { + case v, ok := <-ch: + if !ok { + t.Fatal("channel closed while waiting for delivery") + } + return v + case <-time.After(2 * time.Millisecond): + } + select { + case <-deadline: + t.Fatal("subscription never received a published value") + default: + } + } +} + +// drain discards any buffered values currently sitting on ch. +func drain(ch <-chan float64) { + for { + select { + case <-ch: + default: + return + } + } +} + +func TestNewNilConfigUsesDefaults(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + ch := c.Subscribe("topic") + got := publishUntilRecv(t, func() { c.Publish("topic", 1.5) }, ch) + if got != 1.5 { + t.Fatalf("got %v, want 1.5", got) + } +} + +func TestNewCustomConfig(t *testing.T) { + cfg := &eventbus.Config{IncomingBuffer: 4, SubscribeBuffer: 2, UnsubscribeBuffer: 2} + c := eventbus.New(cfg) + defer c.Close() + + ch := c.Subscribe("topic") + got := publishUntilRecv(t, func() { c.Publish("topic", 42) }, ch) + if got != 42 { + t.Fatalf("got %v, want 42", got) + } +} + +func TestPublishDeliversToSubscriber(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + ch := c.Subscribe("rpm") + got := publishUntilRecv(t, func() { c.Publish("rpm", 3000) }, ch) + if got != 3000 { + t.Fatalf("got %v, want 3000", got) + } +} + +func TestPublishToOtherTopicNotDelivered(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + ch := c.Subscribe("a") + // Make sure the subscription is live first. + publishUntilRecv(t, func() { c.Publish("a", 1) }, ch) + drain(ch) + + c.Publish("b", 99) + select { + case v := <-ch: + t.Fatalf("received %v on topic a from a publish to topic b", v) + case <-time.After(50 * time.Millisecond): + } +} + +func TestMultipleSubscribersSameTopic(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + ch1 := c.Subscribe("temp") + ch2 := c.Subscribe("temp") + + // Keep publishing until both subscriptions are registered and have each + // received at least one value. + var got1, got2 bool + deadline := time.After(testTimeout) + for !(got1 && got2) { + c.Publish("temp", 7) + select { + case <-ch1: + got1 = true + case <-ch2: + got2 = true + case <-time.After(2 * time.Millisecond): + } + select { + case <-deadline: + t.Fatalf("both subscribers did not receive: got1=%v got2=%v", got1, got2) + default: + } + } +} + +func TestSubscribeFuncReceivesAndCancel(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + var count int64 + cancel := c.SubscribeFunc("boost", func(v float64) { + atomic.AddInt64(&count, 1) + }) + + // Publish until the callback fires at least once. + deadline := time.After(testTimeout) + for atomic.LoadInt64(&count) == 0 { + c.Publish("boost", 1) + time.Sleep(2 * time.Millisecond) + select { + case <-deadline: + t.Fatal("SubscribeFunc callback never fired") + default: + } + } + + cancel() + // Give the unsubscribe time to propagate, then confirm no further calls. + time.Sleep(20 * time.Millisecond) + before := atomic.LoadInt64(&count) + for i := 0; i < 50; i++ { + c.Publish("boost", 1) + } + time.Sleep(50 * time.Millisecond) + if after := atomic.LoadInt64(&count); after != before { + t.Fatalf("callback fired after cancel: before=%d after=%d", before, after) + } +} + +func TestUnsubscribeClosesChannel(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + ch := c.Subscribe("x") + publishUntilRecv(t, func() { c.Publish("x", 1) }, ch) + drain(ch) + + c.Unsubscribe(ch) + + // The run loop closes the channel on unsubscribe; reading should eventually + // observe a closed channel. + deadline := time.After(testTimeout) + for { + select { + case _, ok := <-ch: + if !ok { + return // closed as expected + } + case <-deadline: + t.Fatal("channel was not closed after Unsubscribe") + } + } +} + +func TestUnsubscribeUnknownChannelNoPanic(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + // A channel that was never registered should be ignored without panicking. + ch := make(chan float64, 1) + c.Unsubscribe(ch) + // Confirm the controller is still functional afterwards. + sub := c.Subscribe("ok") + got := publishUntilRecv(t, func() { c.Publish("ok", 5) }, sub) + if got != 5 { + t.Fatalf("got %v, want 5", got) + } +} + +func TestSetOnMessageInvoked(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + var mu sync.Mutex + var lastTopic string + var lastData float64 + var calls int + + c.SetOnMessage(func(topic string, data float64) { + mu.Lock() + lastTopic = topic + lastData = data + calls++ + mu.Unlock() + }) + + deadline := time.After(testTimeout) + for { + c.Publish("hook", 12.5) + time.Sleep(2 * time.Millisecond) + mu.Lock() + got := calls + mu.Unlock() + if got > 0 { + break + } + select { + case <-deadline: + t.Fatal("onMessage callback never fired") + default: + } + } + + mu.Lock() + defer mu.Unlock() + if lastTopic != "hook" || lastData != 12.5 { + t.Fatalf("got topic=%q data=%v, want hook/12.5", lastTopic, lastData) + } +} + +func TestSetOnMessageNilClears(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + var calls int64 + c.SetOnMessage(func(string, float64) { atomic.AddInt64(&calls, 1) }) + + deadline := time.After(testTimeout) + for atomic.LoadInt64(&calls) == 0 { + c.Publish("hook", 1) + time.Sleep(2 * time.Millisecond) + select { + case <-deadline: + t.Fatal("callback never fired before clearing") + default: + } + } + + c.SetOnMessage(nil) + time.Sleep(20 * time.Millisecond) + before := atomic.LoadInt64(&calls) + for i := 0; i < 50; i++ { + c.Publish("hook", 1) + } + time.Sleep(50 * time.Millisecond) + if after := atomic.LoadInt64(&calls); after != before { + t.Fatalf("callback fired after SetOnMessage(nil): before=%d after=%d", before, after) + } +} + +func TestCloseClosesSubscriberChannels(t *testing.T) { + c := eventbus.New(nil) + + ch := c.Subscribe("y") + publishUntilRecv(t, func() { c.Publish("y", 1) }, ch) + drain(ch) + + c.Close() + + deadline := time.After(testTimeout) + for { + select { + case _, ok := <-ch: + if !ok { + return // closed by cleanup + } + case <-deadline: + t.Fatal("subscriber channel not closed after Close") + } + } +} + +func TestCloseIsIdempotent(t *testing.T) { + c := eventbus.New(nil) + c.Close() + // A second Close must not panic (guarded by sync.Once). + c.Close() +} + +func TestDIFFAggregatorPublishesDifference(t *testing.T) { + c := eventbus.New(nil) + defer c.Close() + + // Default aggregators include DIFFAggregator(v_Vehicle, v_Vehicle2, VDIFFL). + out := c.Subscribe("VDIFFL") + + // Publish both inputs repeatedly until the aggregated diff is delivered. + // Each completed pair yields (second - first) = 30 - 10 = 20. + got := publishUntilRecv(t, func() { + c.Publish("ActualIn.v_Vehicle", 10) + c.Publish("ActualIn.v_Vehicle2", 30) + }, out) + if got != 20 { + t.Fatalf("VDIFFL = %v, want 20", got) + } +} + +func TestConcurrentPublishersNoRace(t *testing.T) { + // Primarily intended to be run with -race. + c := eventbus.New(nil) + defer c.Close() + + ch := c.Subscribe("hot") + go func() { + for range ch { + } + }() + + var wg sync.WaitGroup + for i := 0; i < 8; i++ { + wg.Add(1) + go func(n int) { + defer wg.Done() + for j := 0; j < 1000; j++ { + c.Publish("hot", float64(n)) + } + }(i) + } + wg.Wait() +} diff --git a/pkg/eventbus/unbounded.go b/experiments/eventbus/unbounded.go similarity index 100% rename from pkg/eventbus/unbounded.go rename to experiments/eventbus/unbounded.go diff --git a/experiments/eventbus/unbounded_test.go b/experiments/eventbus/unbounded_test.go new file mode 100644 index 00000000..fc337d04 --- /dev/null +++ b/experiments/eventbus/unbounded_test.go @@ -0,0 +1,109 @@ +package eventbus_test + +import ( + "sync" + "testing" + "time" + + "github.com/roffe/txlogger/experiments/eventbus" +) + +func TestUnboundedChanPreservesOrder(t *testing.T) { + ch := eventbus.NewUnboundedChan[int]() + + const n = 10000 + go func() { + for i := 0; i < n; i++ { + ch.In() <- i + } + }() + + for i := 0; i < n; i++ { + select { + case got := <-ch.Out(): + if got != i { + t.Fatalf("out of order: got %d, want %d", got, i) + } + case <-time.After(testTimeout): + t.Fatalf("timed out waiting for value %d", i) + } + } + ch.Close() +} + +func TestUnboundedChanBuffersBeyondCapacity(t *testing.T) { + ch := eventbus.NewUnboundedChan[int]() + + // Send many more values than the underlying in/out buffers (16 each) + // without anyone reading. The unbounded buffer must absorb them without + // blocking the sender. + const n = 5000 + done := make(chan struct{}) + go func() { + for i := 0; i < n; i++ { + ch.In() <- i + } + close(done) + }() + + select { + case <-done: + case <-time.After(testTimeout): + t.Fatal("sender blocked: unbounded channel did not buffer") + } + + for i := 0; i < n; i++ { + if got := <-ch.Out(); got != i { + t.Fatalf("out of order: got %d, want %d", got, i) + } + } + ch.Close() +} + +func TestUnboundedChanCloseClosesOut(t *testing.T) { + ch := eventbus.NewUnboundedChan[int]() + ch.In() <- 1 + ch.In() <- 2 + + // Read the buffered values, then close and confirm Out() is closed. + if got := <-ch.Out(); got != 1 { + t.Fatalf("got %d, want 1", got) + } + if got := <-ch.Out(); got != 2 { + t.Fatalf("got %d, want 2", got) + } + + ch.Close() + + select { + case _, ok := <-ch.Out(): + if ok { + t.Fatal("expected Out() to be drained/closed after Close") + } + case <-time.After(testTimeout): + t.Fatal("Out() was not closed after Close") + } +} + +func TestUnboundedChanConcurrentProducerConsumer(t *testing.T) { + // Primarily intended to be run with -race. + ch := eventbus.NewUnboundedChan[int]() + + const n = 20000 + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < n; i++ { + ch.In() <- i + } + }() + + for i := 0; i < n; i++ { + if got := <-ch.Out(); got != i { + t.Fatalf("out of order: got %d, want %d", got, i) + } + } + wg.Wait() + ch.Close() +} From cda8006ea0b37fbcd8ea1bdd786e09e60eca6584 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 20:57:27 +0200 Subject: [PATCH 006/102] add new bus --- pkg/bus/aggregators.go | 74 +++++++++++++ pkg/bus/aggregators_test.go | 144 +++++++++++++++++++++++++ pkg/bus/bus.go | 140 ++++++++++++++++++++++++ pkg/bus/bus_bench_test.go | 40 +++++++ pkg/bus/bus_test.go | 210 ++++++++++++++++++++++++++++++++++++ pkg/ebus/ebus.go | 45 ++------ pkg/ebus/ebus.old | 72 +++++++++++++ 7 files changed, 690 insertions(+), 35 deletions(-) create mode 100644 pkg/bus/aggregators.go create mode 100644 pkg/bus/aggregators_test.go create mode 100644 pkg/bus/bus.go create mode 100644 pkg/bus/bus_bench_test.go create mode 100644 pkg/bus/bus_test.go create mode 100644 pkg/ebus/ebus.old diff --git a/pkg/bus/aggregators.go b/pkg/bus/aggregators.go new file mode 100644 index 00000000..bbebbf2c --- /dev/null +++ b/pkg/bus/aggregators.go @@ -0,0 +1,74 @@ +package bus + +import "sync" + +// Number is the set of value types DIFFAggregator can subtract. +type Number interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | + ~float32 | ~float64 +} + +// DIFFAggregator subscribes to the first and second topics and, once both have +// produced a value, publishes their difference (second - first) to the output +// topic. The internal state resets after each emission, so a fresh value must +// arrive on both inputs before the next diff is published. +// +// Unlike the bus itself, Publish may be called concurrently from several +// goroutines, so the aggregator guards its own state with a mutex. The diff is +// published outside the lock to avoid stalling other publishers and to keep the +// output safe to feed back into the bus. +// +// The returned unsubscribe function removes both input subscriptions; calling +// it more than once is safe and has no further effect. +func DIFFAggregator[K comparable, V Number](c *Controller[K, V], first, second, output K) (unsubscribe func()) { + var ( + mu sync.Mutex + firstUpdated bool + secondUpdated bool + firstValue V + secondValue V + ) + + // combine reports the diff when both inputs are fresh, resetting state so the + // next diff waits for new values. Callers must hold mu. + combine := func() (V, bool) { + if firstUpdated && secondUpdated { + diff := secondValue - firstValue + firstUpdated, secondUpdated = false, false + return diff, true + } + var zero V + return zero, false + } + + stopFirst := c.SubscribeFunc(first, func(v V) { + mu.Lock() + firstValue = v + firstUpdated = true + diff, ok := combine() + mu.Unlock() + if ok { + c.Publish(output, diff) + } + }) + + stopSecond := c.SubscribeFunc(second, func(v V) { + mu.Lock() + secondValue = v + secondUpdated = true + diff, ok := combine() + mu.Unlock() + if ok { + c.Publish(output, diff) + } + }) + + var once sync.Once + return func() { + once.Do(func() { + stopFirst() + stopSecond() + }) + } +} diff --git a/pkg/bus/aggregators_test.go b/pkg/bus/aggregators_test.go new file mode 100644 index 00000000..9aee41f8 --- /dev/null +++ b/pkg/bus/aggregators_test.go @@ -0,0 +1,144 @@ +package bus + +import ( + "sync" + "sync/atomic" + "testing" +) + +func TestDIFFAggregatorPublishesDifference(t *testing.T) { + b := NewBus[string, float64]() + DIFFAggregator(b, "first", "second", "out") + + var got []float64 + b.SubscribeFunc("out", func(v float64) { got = append(got, v) }) + + b.Publish("first", 5) + b.Publish("second", 25) + + if len(got) != 1 { + t.Fatalf("expected one diff, got %v", got) + } + if got[0] != 20 { // second - first + t.Fatalf("diff = %v, want 20", got[0]) + } +} + +// A diff is published only once both inputs have a fresh value; a single input +// updating must not emit anything. +func TestDIFFAggregatorWaitsForBothInputs(t *testing.T) { + b := NewBus[string, float64]() + DIFFAggregator(b, "first", "second", "out") + + var count int + b.SubscribeFunc("out", func(float64) { count++ }) + + b.Publish("first", 1) + b.Publish("first", 2) + b.Publish("first", 3) + + if count != 0 { + t.Fatalf("expected no emission with only one input, got %d", count) + } + + b.Publish("second", 10) + if count != 1 { + t.Fatalf("expected one emission once both inputs seen, got %d", count) + } +} + +// State resets after each emission: a new diff requires fresh values on both +// inputs again, not just one. +func TestDIFFAggregatorResetsAfterEmit(t *testing.T) { + b := NewBus[string, float64]() + DIFFAggregator(b, "first", "second", "out") + + var got []float64 + b.SubscribeFunc("out", func(v float64) { got = append(got, v) }) + + b.Publish("first", 1) + b.Publish("second", 4) // emits 3 + b.Publish("second", 9) // no second emit: first is stale + b.Publish("first", 2) // emits 9 - 2 = 7 + + want := []float64{3, 7} + if len(got) != len(want) { + t.Fatalf("got %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("got %v, want %v", got, want) + } + } +} + +func TestDIFFAggregatorUnsubscribeStops(t *testing.T) { + b := NewBus[string, float64]() + unsub := DIFFAggregator(b, "first", "second", "out") + + var count int + b.SubscribeFunc("out", func(float64) { count++ }) + + unsub() + unsub() // idempotent + + b.Publish("first", 1) + b.Publish("second", 2) + + if count != 0 { + t.Fatalf("expected no emissions after unsubscribe, got %d", count) + } +} + +// Two aggregators sharing an input topic and output topic keep independent +// state, mirroring the AirDIFF wiring in package ebus. +func TestDIFFAggregatorIndependentStateOnSharedTopics(t *testing.T) { + b := NewBus[string, float64]() + DIFFAggregator(b, "shared", "a", "out") + DIFFAggregator(b, "shared", "b", "out") + + var got []float64 + b.SubscribeFunc("out", func(v float64) { got = append(got, v) }) + + b.Publish("shared", 10) // primes the shared input of both aggregators + b.Publish("a", 30) // first aggregator emits 20 + b.Publish("b", 100) // second aggregator emits 90 + + want := []float64{20, 90} + if len(got) != len(want) { + t.Fatalf("got %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("got %v, want %v", got, want) + } + } +} + +// The aggregator must stay race-free when its inputs are published from +// multiple goroutines; meaningful only under -race. +func TestDIFFAggregatorConcurrentPublish(t *testing.T) { + b := NewBus[string, float64]() + DIFFAggregator(b, "first", "second", "out") + + var emitted atomic.Int64 + b.SubscribeFunc("out", func(float64) { emitted.Add(1) }) + + var wg sync.WaitGroup + for _, topic := range []string{"first", "second"} { + wg.Add(1) + go func() { + defer wg.Done() + for i := range 1000 { + b.Publish(topic, float64(i)) + } + }() + } + wg.Wait() + + // Exact count is non-deterministic under interleaving; just confirm the + // aggregator made progress without racing. + if emitted.Load() == 0 { + t.Fatal("expected at least one diff emission") + } +} diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go new file mode 100644 index 00000000..1b055285 --- /dev/null +++ b/pkg/bus/bus.go @@ -0,0 +1,140 @@ +// Package bus implements a small, type-safe publish/subscribe message bus. +// +// Topics are keyed by any comparable type K and carry values of type V. +// Subscribers register either a channel (Subscribe) or a callback +// (SubscribeFunc) and receive every value published to their topic until +// they unsubscribe. +// +// The bus is optimised for a publish-heavy, subscription-stable workload: the +// subscriber table is held as an immutable snapshot behind an atomic pointer, +// so Publish reads it lock-free and allocation-free and scales linearly across +// goroutines. Subscribe/Unsubscribe copy the table (copy-on-write), so they +// cost O(topics + subscribers) — cheap when subscriptions change rarely, which +// is the intended use. +// +// Callbacks run synchronously in the publishing goroutine, so they must be +// fast and non-blocking. A slow callback stalls that Publish call; if you need +// blocking work, hand off to your own goroutine or use Subscribe's buffered +// channel. +package bus + +import ( + "maps" + "sync" + "sync/atomic" +) + +type subscriber[V any] struct { + id uint64 + fn func(V) +} + +// Controller is a concurrency-safe pub/sub bus. The zero value is not usable; +// create one with NewBus. +type Controller[K comparable, V any] struct { + mu sync.Mutex // serialises writers (Subscribe/Unsubscribe) only + nextID uint64 + state atomic.Pointer[map[K][]subscriber[V]] +} + +// NewBus creates an empty bus for topics of type K carrying values of type V. +func NewBus[K comparable, V any]() *Controller[K, V] { + c := &Controller[K, V]{} + empty := make(map[K][]subscriber[V]) + c.state.Store(&empty) + return c +} + +// SubscribeFunc registers fn to be called for every value published to topic. +// It returns an unsubscribe function that removes the subscription; calling it +// more than once is safe and has no further effect. +// +// fn runs synchronously in the goroutine that calls Publish and must not block. +func (c *Controller[K, V]) SubscribeFunc(topic K, fn func(V)) (unsubscribe func()) { + c.mu.Lock() + id := c.nextID + c.nextID++ + c.replace(func(m map[K][]subscriber[V]) { + m[topic] = append(cloneSlice(m[topic]), subscriber[V]{id: id, fn: fn}) + }) + c.mu.Unlock() + + var once sync.Once + return func() { + once.Do(func() { + c.unsubscribe(topic, id) + }) + } +} + +// Subscribe registers a buffered channel that receives every value published +// to topic. The returned unsubscribe function removes the subscription and +// closes the channel. +// +// buffer sets the channel's capacity. If the channel is full when a value is +// published, Publish skips this subscriber rather than blocking, so size the +// buffer for your expected burst rate or drain the channel promptly. +func (c *Controller[K, V]) Subscribe(topic K, buffer int) (ch <-chan V, unsubscribe func()) { + out := make(chan V, buffer) + stop := c.SubscribeFunc(topic, func(v V) { + // Non-blocking send: a slow consumer must not stall the publisher. + select { + case out <- v: + default: + } + }) + + var once sync.Once + return out, func() { + once.Do(func() { + stop() + close(out) + }) + } +} + +// Publish delivers v to every current subscriber of topic. Callbacks run +// synchronously in the caller's goroutine in an unspecified order. This is the +// hot path: it takes no locks and allocates nothing. +func (c *Controller[K, V]) Publish(topic K, v V) { + for _, s := range (*c.state.Load())[topic] { + s.fn(v) + } +} + +func (c *Controller[K, V]) unsubscribe(topic K, id uint64) { + c.mu.Lock() + defer c.mu.Unlock() + c.replace(func(m map[K][]subscriber[V]) { + subs := m[topic] + out := make([]subscriber[V], 0, len(subs)) + for _, s := range subs { + if s.id != id { + out = append(out, s) + } + } + if len(out) == 0 { + delete(m, topic) + } else { + m[topic] = out + } + }) +} + +// replace builds a shallow copy of the current subscriber table, applies mutate +// to the copy, and atomically swaps it in. Callers must hold c.mu so writers +// don't race each other; readers (Publish) never block. The previous table is +// never mutated, so any in-flight Publish keeps iterating a consistent view. +func (c *Controller[K, V]) replace(mutate func(map[K][]subscriber[V])) { + old := *c.state.Load() + next := make(map[K][]subscriber[V], len(old)+1) + maps.Copy(next, old) // slices are immutable once published; share them + mutate(next) + c.state.Store(&next) +} + +func cloneSlice[V any](s []subscriber[V]) []subscriber[V] { + out := make([]subscriber[V], len(s), len(s)+1) + copy(out, s) + return out +} diff --git a/pkg/bus/bus_bench_test.go b/pkg/bus/bus_bench_test.go new file mode 100644 index 00000000..b5cfaa3c --- /dev/null +++ b/pkg/bus/bus_bench_test.go @@ -0,0 +1,40 @@ +package bus + +import ( + "sync/atomic" + "testing" +) + +func benchPublish(b *testing.B, nSubs int) { + bus := NewBus[string, int]() + var sink atomic.Int64 + for range nSubs { + bus.SubscribeFunc("t", func(v int) { sink.Add(int64(v)) }) + } + b.ReportAllocs() + b.ResetTimer() + for range b.N { + bus.Publish("t", 1) + } +} + +func BenchmarkPublish1(b *testing.B) { benchPublish(b, 1) } +func BenchmarkPublish10(b *testing.B) { benchPublish(b, 10) } +func BenchmarkPublish100(b *testing.B) { benchPublish(b, 100) } +func BenchmarkPublish1000(b *testing.B) { benchPublish(b, 1000) } + +// Contended: many goroutines publishing to the same topic concurrently. +func BenchmarkPublishParallel(b *testing.B) { + bus := NewBus[string, int]() + var sink atomic.Int64 + for range 100 { + bus.SubscribeFunc("t", func(v int) { sink.Add(int64(v)) }) + } + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + bus.Publish("t", 1) + } + }) +} diff --git a/pkg/bus/bus_test.go b/pkg/bus/bus_test.go new file mode 100644 index 00000000..3881b7b0 --- /dev/null +++ b/pkg/bus/bus_test.go @@ -0,0 +1,210 @@ +package bus + +import ( + "sync" + "sync/atomic" + "testing" + "time" +) + +func TestSubscribeFuncReceivesPublishes(t *testing.T) { + b := NewBus[string, int]() + var got []int + b.SubscribeFunc("t", func(v int) { got = append(got, v) }) + + for _, v := range []int{1, 2, 3} { + b.Publish("t", v) + } + + want := []int{1, 2, 3} + if len(got) != len(want) { + t.Fatalf("got %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("got %v, want %v", got, want) + } + } +} + +func TestPublishToUnknownTopicIsNoop(t *testing.T) { + b := NewBus[string, int]() + b.SubscribeFunc("a", func(int) { t.Fatal("subscriber on topic a should not fire") }) + b.Publish("b", 1) // different topic — must not panic or deliver +} + +func TestMultipleSubscribersEachReceive(t *testing.T) { + b := NewBus[string, int]() + var c1, c2 int + b.SubscribeFunc("t", func(int) { c1++ }) + b.SubscribeFunc("t", func(int) { c2++ }) + + b.Publish("t", 1) + b.Publish("t", 1) + + if c1 != 2 || c2 != 2 { + t.Fatalf("each subscriber should see 2 messages, got c1=%d c2=%d", c1, c2) + } +} + +func TestUnsubscribeStopsDelivery(t *testing.T) { + b := NewBus[string, int]() + var count int + unsub := b.SubscribeFunc("t", func(int) { count++ }) + + b.Publish("t", 1) + unsub() + b.Publish("t", 1) + + if count != 1 { + t.Fatalf("expected 1 delivery before unsubscribe, got %d", count) + } +} + +func TestUnsubscribeIsIdempotent(t *testing.T) { + b := NewBus[string, int]() + var count int + unsub := b.SubscribeFunc("t", func(int) { count++ }) + + unsub() + unsub() // second call must be a safe no-op + b.Publish("t", 1) + + if count != 0 { + t.Fatalf("expected no deliveries after unsubscribe, got %d", count) + } +} + +// Unsubscribing one subscriber must not affect the others on the same topic. +func TestUnsubscribeLeavesOthersIntact(t *testing.T) { + b := NewBus[string, int]() + var a, c int + unsubA := b.SubscribeFunc("t", func(int) { a++ }) + b.SubscribeFunc("t", func(int) { c++ }) + + unsubA() + b.Publish("t", 1) + + if a != 0 { + t.Fatalf("unsubscribed handler fired %d times", a) + } + if c != 1 { + t.Fatalf("remaining handler should fire once, got %d", c) + } +} + +// A Publish already iterating its snapshot must complete against a consistent +// view even if a handler unsubscribes mid-dispatch. +func TestUnsubscribeFromWithinCallback(t *testing.T) { + b := NewBus[string, int]() + var unsub func() + var calls int + unsub = b.SubscribeFunc("t", func(int) { + calls++ + unsub() // remove self during dispatch + }) + b.SubscribeFunc("t", func(int) {}) // a second sub keeps the topic alive + + b.Publish("t", 1) + b.Publish("t", 1) + + if calls != 1 { + t.Fatalf("self-unsubscribing handler should fire exactly once, got %d", calls) + } +} + +func TestSubscribeChannelDelivers(t *testing.T) { + b := NewBus[string, string]() + ch, unsub := b.Subscribe("t", 4) + defer unsub() + + b.Publish("t", "hello") + select { + case got := <-ch: + if got != "hello" { + t.Fatalf("got %q, want %q", got, "hello") + } + case <-time.After(time.Second): + t.Fatal("timed out waiting for channel delivery") + } +} + +func TestSubscribeChannelClosesOnUnsubscribe(t *testing.T) { + b := NewBus[string, int]() + ch, unsub := b.Subscribe("t", 1) + unsub() + + if _, ok := <-ch; ok { + t.Fatal("channel should be closed after unsubscribe") + } +} + +// A full channel must not block the publisher; excess messages are dropped. +func TestSubscribeChannelDropsWhenFull(t *testing.T) { + b := NewBus[string, int]() + ch, unsub := b.Subscribe("t", 1) + defer unsub() + + b.Publish("t", 1) // fills the buffer + b.Publish("t", 2) // dropped, must not block + + if got := <-ch; got != 1 { + t.Fatalf("got %d, want 1", got) + } + select { + case v := <-ch: + t.Fatalf("expected no second message, got %d", v) + default: + } +} + +// Hammer subscribe/unsubscribe/publish concurrently; meaningful only under -race. +func TestConcurrentChurn(t *testing.T) { + b := NewBus[int, int]() + var wg sync.WaitGroup + var delivered atomic.Int64 + + const topics = 8 + stop := make(chan struct{}) + + // Publishers. + for range 4 { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-stop: + return + default: + for tpc := range topics { + b.Publish(tpc, 1) + } + } + } + }() + } + + // Subscribers churning in and out. + for range 8 { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-stop: + return + default: + for tpc := range topics { + unsub := b.SubscribeFunc(tpc, func(int) { delivered.Add(1) }) + unsub() + } + } + } + }() + } + + time.Sleep(100 * time.Millisecond) + close(stop) + wg.Wait() +} diff --git a/pkg/ebus/ebus.go b/pkg/ebus/ebus.go index 32e091f4..764a97da 100644 --- a/pkg/ebus/ebus.go +++ b/pkg/ebus/ebus.go @@ -1,16 +1,15 @@ package ebus import ( - "context" "sync" "fyne.io/fyne/v2" - "github.com/roffe/txlogger/pkg/eventbus" + "github.com/roffe/txlogger/pkg/bus" ) var ( once sync.Once - CONTROLLER *eventbus.Controller + CONTROLLER *bus.Controller[string, float64] ) const ( @@ -20,7 +19,12 @@ const ( func init() { once.Do(func() { - CONTROLLER = eventbus.New(eventbus.DefaultConfig) + CONTROLLER = bus.NewBus[string, float64]() + + // AirDIFF: m_AirInlet vs the requested air mass. Two instances cover the + // differing request topic names across ECU types; both publish AirDIFF. + bus.DIFFAggregator(CONTROLLER, "MAF.m_AirInlet", "m_Request", "AirDIFF") + bus.DIFFAggregator(CONTROLLER, "MAF.m_AirInlet", "AirMassMast.m_Request", "AirDIFF") }) } @@ -28,19 +32,6 @@ func Publish(topic string, data float64) { CONTROLLER.Publish(topic, data) } -/* - func SubscribeAll() chan eventbus.EBusMessage { - return eb.SubscribeAll() - } - - func SubscribeAllFunc(f func(topic string, value float64)) func() { - return eb.SubscribeAllFunc(f) - } - - func UnsubscribeAll(channel chan eventbus.EBusMessage) { - eb.UnsubscribeAll(channel) - } -*/ func SubscribeFunc(topic string, f func(float64)) func() { wrapFN := func(v float64) { fyne.Do(func() { @@ -50,23 +41,7 @@ func SubscribeFunc(topic string, f func(float64)) func() { return CONTROLLER.SubscribeFunc(topic, wrapFN) } -func Subscribe(topic string) chan float64 { - return CONTROLLER.Subscribe(topic) -} - -func SubscribeWithContext(ctx context.Context, topic string) (chan float64, error) { - ch := CONTROLLER.Subscribe(topic) - go func() { - <-ctx.Done() - CONTROLLER.Unsubscribe(ch) - }() - return ch, nil -} - -func Unsubscribe(channel chan float64) { - CONTROLLER.Unsubscribe(channel) -} - func SetOnMessage(f func(string, float64)) { - CONTROLLER.SetOnMessage(f) + // CONTROLLER.SetOnMessage(f) + // noop for now, the bus doesn't support this and we don't need it yet. If we do, we can add it to the bus package and call it here. } diff --git a/pkg/ebus/ebus.old b/pkg/ebus/ebus.old new file mode 100644 index 00000000..32e091f4 --- /dev/null +++ b/pkg/ebus/ebus.old @@ -0,0 +1,72 @@ +package ebus + +import ( + "context" + "sync" + + "fyne.io/fyne/v2" + "github.com/roffe/txlogger/pkg/eventbus" +) + +var ( + once sync.Once + CONTROLLER *eventbus.Controller +) + +const ( + TOPIC_COLORBLINDMODE = "color_blind_mode" + TOPIC_ECU = "selected_ecu" +) + +func init() { + once.Do(func() { + CONTROLLER = eventbus.New(eventbus.DefaultConfig) + }) +} + +func Publish(topic string, data float64) { + CONTROLLER.Publish(topic, data) +} + +/* + func SubscribeAll() chan eventbus.EBusMessage { + return eb.SubscribeAll() + } + + func SubscribeAllFunc(f func(topic string, value float64)) func() { + return eb.SubscribeAllFunc(f) + } + + func UnsubscribeAll(channel chan eventbus.EBusMessage) { + eb.UnsubscribeAll(channel) + } +*/ +func SubscribeFunc(topic string, f func(float64)) func() { + wrapFN := func(v float64) { + fyne.Do(func() { + f(v) + }) + } + return CONTROLLER.SubscribeFunc(topic, wrapFN) +} + +func Subscribe(topic string) chan float64 { + return CONTROLLER.Subscribe(topic) +} + +func SubscribeWithContext(ctx context.Context, topic string) (chan float64, error) { + ch := CONTROLLER.Subscribe(topic) + go func() { + <-ctx.Done() + CONTROLLER.Unsubscribe(ch) + }() + return ch, nil +} + +func Unsubscribe(channel chan float64) { + CONTROLLER.Unsubscribe(channel) +} + +func SetOnMessage(f func(string, float64)) { + CONTROLLER.SetOnMessage(f) +} From 62e07bbeba743a5daf3fdbe4a62867ded98dac58 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 20:57:38 +0200 Subject: [PATCH 007/102] remove ebusmonitor --- pkg/windows/mainWindow.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/windows/mainWindow.go b/pkg/windows/mainWindow.go index 1d3106d7..a5636b4a 100644 --- a/pkg/windows/mainWindow.go +++ b/pkg/windows/mainWindow.go @@ -286,7 +286,6 @@ func (mw *MainWindow) render() { mw.counters.errorCounterLabel, mw.counters.fpsCounterLabel, ), - widget.NewButtonWithIcon("", theme.ComputerIcon(), mw.openEBUSMonitor), mw.buttons.debugBtn, ), mw.statusText, From 8f74ee350aaf87f5c53d2c96c75217ba1f7b06f2 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 20:57:59 +0200 Subject: [PATCH 008/102] remove ebusmonitor --- pkg/windows/mainWindow_internal.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/pkg/windows/mainWindow_internal.go b/pkg/windows/mainWindow_internal.go index a1711202..894addf4 100644 --- a/pkg/windows/mainWindow_internal.go +++ b/pkg/windows/mainWindow_internal.go @@ -16,8 +16,6 @@ import ( xwidget "fyne.io/x/fyne/widget" "github.com/roffe/gocan/proto" "github.com/roffe/txlogger/pkg/common" - "github.com/roffe/txlogger/pkg/ebus" - "github.com/roffe/txlogger/pkg/widgets/ebusmonitor" "github.com/roffe/txlogger/pkg/widgets/multiwindow" ) @@ -126,27 +124,6 @@ func listLayouts() []string { return opts } -func (mw *MainWindow) openEBUSMonitor() { - if w := mw.wm.HasWindow("EBUS Monitor"); w != nil { - mw.wm.Raise(w) - return - } - mon := ebusmonitor.New() - eb := multiwindow.NewSystemWindow("EBUS Monitor", mon) - eb.Icon = theme.ComputerIcon() - ebus.SetOnMessage( - func(topic string, data float64) { - fyne.Do(func() { - mon.SetText(topic, data) - }) - }, - ) - eb.OnClose = func() { - ebus.SetOnMessage(nil) - } - mw.wm.Add(eb) -} - func (mw *MainWindow) openSettings() { if w := mw.wm.HasWindow("Settings"); w != nil { mw.wm.Raise(w) From 1cf5892e05f2d388922d87e623e55305ab63f52c Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 20:58:19 +0200 Subject: [PATCH 009/102] some concepts for t7logger --- pkg/datalogger/t7logger.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pkg/datalogger/t7logger.go b/pkg/datalogger/t7logger.go index ff8309fd..d238c6ca 100644 --- a/pkg/datalogger/t7logger.go +++ b/pkg/datalogger/t7logger.go @@ -175,6 +175,9 @@ func (c *T7Client) Start() error { } } } + // In.v_Vehicle Left front wheel speed + // In.v_Vehicle2 Vehicle speed, measured on the rear wheel + // In.v_Vehicle3 Right front wheel speed specialFN := map[string]func(string, float64){ "In.v_Vehicle": wheelSlipFN, @@ -212,6 +215,17 @@ func (c *T7Client) Start() error { } } + /* + if c.lamb != nil { + lambdbaChan := newFunctionChannel(EXTERNALWBLSYM, func() float64 { + lambda := c.lamb.GetLambda() + ebus.Publish(EXTERNALWBLSYM, lambda) + return lambda + }) + channels = append(channels, lambdbaChan) + } + */ + go func() { defer cl.Close() defer func() { @@ -219,10 +233,6 @@ func (c *T7Client) Start() error { time.Sleep(50 * time.Millisecond) }() - // In.v_Vehicle Left front wheel speed - // In.v_Vehicle2 Vehicle speed, measured on the rear wheel - // In.v_Vehicle3 Right front wheel speed - for { select { case <-ctx.Done(): From 3dbc4fac6a586ab1eacbec8ad5f3f82f0d1dba90 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 20:58:31 +0200 Subject: [PATCH 010/102] use new bus --- pkg/widgets/combinedlogplayer/combinedlogplayer.go | 8 ++++---- pkg/widgets/logplayer/logplayer.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/widgets/combinedlogplayer/combinedlogplayer.go b/pkg/widgets/combinedlogplayer/combinedlogplayer.go index 127ebd85..da9e95c0 100644 --- a/pkg/widgets/combinedlogplayer/combinedlogplayer.go +++ b/pkg/widgets/combinedlogplayer/combinedlogplayer.go @@ -6,7 +6,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" - "github.com/roffe/txlogger/pkg/eventbus" + "github.com/roffe/txlogger/pkg/bus" "github.com/roffe/txlogger/pkg/logfile" "github.com/roffe/txlogger/pkg/widgets/dashboard" "github.com/roffe/txlogger/pkg/widgets/logplayer" @@ -36,12 +36,12 @@ func New(cfg *CombinedLogplayerConfig) *Widget { return "Undefined" } } - bus := eventbus.New(eventbus.DefaultConfig) + buz := bus.NewBus[string, float64]() db := dashboard.NewDashboard(cfg.DBcfg) for _, name := range db.GetMetricNames() { - cancel := bus.SubscribeFunc(name, func(f float64) { + cancel := buz.SubscribeFunc(name, func(f float64) { fyne.Do(func() { db.SetValue(name, f) }) @@ -51,7 +51,7 @@ func New(cfg *CombinedLogplayerConfig) *Widget { cp.db = db cp.lp = logplayer.New(&logplayer.Config{ - EBus: bus, + EBus: buz, Logfile: cfg.Logfile, TimeSetter: db.SetTime, }) diff --git a/pkg/widgets/logplayer/logplayer.go b/pkg/widgets/logplayer/logplayer.go index 0a6d55c0..da7f558a 100644 --- a/pkg/widgets/logplayer/logplayer.go +++ b/pkg/widgets/logplayer/logplayer.go @@ -9,8 +9,8 @@ import ( "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/bus" "github.com/roffe/txlogger/pkg/capture" - "github.com/roffe/txlogger/pkg/eventbus" "github.com/roffe/txlogger/pkg/layout" "github.com/roffe/txlogger/pkg/logfile" "github.com/roffe/txlogger/pkg/widgets/plotter" @@ -73,7 +73,7 @@ type logplayerObjects struct { } type Config struct { - EBus *eventbus.Controller + EBus *bus.Controller[string, float64] Logfile logfile.Logfile TimeSetter func(time.Time) } From b31da7cbc1ced095c4ae714d15e3d76ae45c8361 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 20:58:46 +0200 Subject: [PATCH 011/102] update multiplewindows --- pkg/widgets/multiwindow/arrange.go | 3 +++ pkg/widgets/multiwindow/innerwindow.go | 7 ++++-- pkg/widgets/multiwindow/multiplewindows.go | 29 ++++++++++++++++++---- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/pkg/widgets/multiwindow/arrange.go b/pkg/widgets/multiwindow/arrange.go index 9d2e1584..60c74f51 100644 --- a/pkg/widgets/multiwindow/arrange.go +++ b/pkg/widgets/multiwindow/arrange.go @@ -211,6 +211,9 @@ func (p *PackArranger) expandWindows(spaces []windowSpace, maxSize fyne.Size) { } func (p *PackArranger) findSpace(node *packNode, size fyne.Size) *packNode { + if node == nil { + return nil + } if node.used { if right := p.findSpace(node.right, size); right != nil { return right diff --git a/pkg/widgets/multiwindow/innerwindow.go b/pkg/widgets/multiwindow/innerwindow.go index be0b03ba..d01185dc 100644 --- a/pkg/widgets/multiwindow/innerwindow.go +++ b/pkg/widgets/multiwindow/innerwindow.go @@ -255,6 +255,7 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { ShadowingRenderer: NewShadowingRenderer(objects, SubmergedContentLevel), win: w, bar: bar, + title: title, buttons: []*borderButton{min, max, close}, bg: w.bg, topBorder: topBorder, @@ -299,6 +300,7 @@ var _ fyne.WidgetRenderer = (*innerWindowRenderer)(nil) type innerWindowRenderer struct { win *InnerWindow bar *fyne.Container + title *draggableLabel buttons []*borderButton bg, contentBG *canvas.Rectangle @@ -399,8 +401,9 @@ func (i *innerWindowRenderer) Refresh() { b.setTheme(th, i.win.active) } i.bar.Refresh() - title := i.bar.Objects[0].(*fyne.Container).Objects[0].(*draggableLabel) - title.SetText(i.win.title) + if i.title.Text != i.win.title { + i.title.SetText(i.win.title) + } i.ShadowingRenderer.RefreshShadow() } diff --git a/pkg/widgets/multiwindow/multiplewindows.go b/pkg/widgets/multiwindow/multiplewindows.go index 460300a6..201d3e33 100644 --- a/pkg/widgets/multiwindow/multiplewindows.go +++ b/pkg/widgets/multiwindow/multiplewindows.go @@ -2,6 +2,7 @@ package multiwindow import ( "encoding/json" + "errors" "time" "fyne.io/fyne/v2" @@ -32,7 +33,8 @@ type MultipleWindows struct { windows []*InnerWindow - content *fyne.Container + content *fyne.Container + childBuf []fyne.CanvasObject // reused backing slice for content.Objects openOffset fyne.Position @@ -193,11 +195,15 @@ func (m *MultipleWindows) refreshChildren() { if m.content == nil { return } - objs := make([]fyne.CanvasObject, len(m.windows)) + if cap(m.childBuf) < len(m.windows) { + m.childBuf = make([]fyne.CanvasObject, len(m.windows)) + } else { + m.childBuf = m.childBuf[:len(m.windows)] + } for i, w := range m.windows { - objs[i] = w + m.childBuf[i] = w } - m.content.Objects = objs + m.content.Objects = m.childBuf m.content.Refresh() } @@ -205,8 +211,13 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { w.OnDragged = func(ev *fyne.DragEvent) { if w.maximized { w.maximized = false - w.Move(ev.AbsolutePosition.SubtractXY(w.preMaximizedSize.Width*0.5, 78)) w.Resize(w.preMaximizedSize) + // Convert the cursor's canvas-relative position into a position + // relative to our container, so this works regardless of where the + // container sits on the canvas (e.g. below a toolbar/menu). + local := ev.AbsolutePosition.Subtract(fyne.CurrentApp().Driver().AbsolutePositionForObject(m.content)) + barHeight := w.Theme().Size(theme.SizeNameWindowTitleBarHeight) + w.Move(local.SubtractXY(w.preMaximizedSize.Width*0.5, barHeight*0.5)) return } @@ -354,6 +365,10 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { func (m *MultipleWindows) LoadLayout(windows []WindowProperties) error { viewportSize := m.Size() + if viewportSize.Width == 0 || viewportSize.Height == 0 { + // Viewport not laid out yet; positions/sizes would all collapse to zero. + return nil + } for _, h := range windows { for _, w := range m.windows { if w.Title() == h.Title { @@ -388,6 +403,10 @@ func (m *MultipleWindows) LoadLayout(windows []WindowProperties) error { func (wm *MultipleWindows) JsonLayout() ([]byte, error) { var history []WindowProperties viewportSize := wm.Size() + if viewportSize.Width == 0 || viewportSize.Height == 0 { + // Avoid Inf/NaN ratios (which json.Marshal rejects) when not yet sized. + return nil, errors.New("multiwindow: cannot save layout before viewport is sized") + } for _, w := range wm.windows { if w.IgnoreSave { From f79537c21d82babe5b46de68e6979d2059569b9b Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 20:58:51 +0200 Subject: [PATCH 012/102] deps and whatsnew --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/assets/WHATSNEW.md | 6 +++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index ed150596..2d126f8f 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.7.5-0.20260602200529-2bc01b09a210 + fyne.io/fyne/v2 v2.7.5-0.20260610120109-657f1cc54c35 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 @@ -37,7 +37,7 @@ require ( ) require ( - fyne.io/systray v1.12.1 // indirect + fyne.io/systray v1.12.2 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/FyshOS/fancyfs v0.0.1 // indirect github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 // indirect diff --git a/go.sum b/go.sum index 4020c764..a780694d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -fyne.io/fyne/v2 v2.7.5-0.20260602200529-2bc01b09a210 h1:V+6+GZq6jtDw2AzRfKhbBP7Yg7oCQPQKOl+oEhbSGLs= -fyne.io/fyne/v2 v2.7.5-0.20260602200529-2bc01b09a210/go.mod h1:MEWwWTgsffhd9B79f31GXZLPfnrlXJVavETgNPzrsG0= -fyne.io/systray v1.12.1 h1:ygBD6aZXwiOmZoY5N+ukbH9pih0Kq6fYgVeMYbr5skQ= -fyne.io/systray v1.12.1/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= +fyne.io/fyne/v2 v2.7.5-0.20260610120109-657f1cc54c35 h1:SRutBcy6SbGtnaKk3VqAYnyoOlwp9JDE6wHZKMZfHiU= +fyne.io/fyne/v2 v2.7.5-0.20260610120109-657f1cc54c35/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= +fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= +fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e/go.mod h1:TyPwb4pDTB8+btHM20AJpPUNAF8FqEq136+vcGQhcI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index 981218fe..860fad62 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -2,7 +2,11 @@ - Performance optimization for the log plotter and meshgrid - force layouts to be loaded and saved in users home directory under the txlogger folder - update ecusymbol to be able to read T5 versions -- Added new config widget for AD scanner WBL settings inspired by T7's DisplAdap.LamScannerTab +- added new config widget for AD scanner WBL settings inspired by T7's DisplAdap.LamScannerTab +- refactored the bus implementation to use less CPU and have less allocations +- added support for BPL files ( binary packed logfile ) +- removed support for creating legacy TXL log files. (you can still load them but might cause crashes) +- removed ebusmonitor, it has served it's purpose # 2.1.9 - Updated default T7 preset to include MAF.m_AirFromp_AirInlet From 76d6ba2372ffe40d9876a0a6bacc245ba0a3ff58 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 10 Jun 2026 21:01:55 +0200 Subject: [PATCH 013/102] update ecusymbol --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2d126f8f..48633a61 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 - github.com/roffe/ecusymbol v1.1.7 + github.com/roffe/ecusymbol v1.1.8 github.com/roffe/gocan v1.4.0 go.bug.st/serial v1.6.4 golang.org/x/image v0.40.0 diff --git a/go.sum b/go.sum index a780694d..c152d11b 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/roffe/ecusymbol v1.1.7 h1:Ub6Dax1V19Jl5y3iXbuhPsPM/XCxEk77Bnal6QU43H4= -github.com/roffe/ecusymbol v1.1.7/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= +github.com/roffe/ecusymbol v1.1.8 h1:c145IldWHTnzy4y2cInpHRDYYvehnqFxsKQZ2YPyzNw= +github.com/roffe/ecusymbol v1.1.8/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= github.com/roffe/gocan v1.4.0 h1:OSs//lr4vy/ozyMPUbgZaNFVZWMeXzOsXhCujpA4WRs= github.com/roffe/gocan v1.4.0/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= From fd7312b86cb1542cca0f9ced2c966fa6437dbcf8 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 11 Jun 2026 21:22:18 +0200 Subject: [PATCH 014/102] WBL refactor to reconnect port if it's dropped mid logging --- pkg/wbl/aem/aem_uego.go | 109 +++++++++++++++++++--- pkg/wbl/innovate/innovate.go | 138 +++++++++++++++++++++++----- pkg/wbl/stag/stag.go | 160 ++++++++++++++++++++++++++------- pkg/wbl/zeitronix/zeitronix.go | 115 +++++++++++++++++++++--- 4 files changed, 445 insertions(+), 77 deletions(-) diff --git a/pkg/wbl/aem/aem_uego.go b/pkg/wbl/aem/aem_uego.go index 98379771..30e88324 100644 --- a/pkg/wbl/aem/aem_uego.go +++ b/pkg/wbl/aem/aem_uego.go @@ -7,6 +7,7 @@ import ( "fmt" "strconv" "sync" + "sync/atomic" "time" "go.bug.st/serial" @@ -14,6 +15,10 @@ import ( const ProductString = "AEM Uego" +// reconnectDelay is how long run() waits between reconnect attempts after the +// serial port drops (a common occurrence on Windows). +const reconnectDelay = time.Second + type AEMuego struct { port string sp serial.Port @@ -25,6 +30,8 @@ type AEMuego struct { log func(string) closeOnce sync.Once + closed atomic.Bool + done chan struct{} mu sync.Mutex dataBuff []byte @@ -41,13 +48,26 @@ func NewAEMuegoClient(port string, logFunc func(string)) (*AEMuego, error) { return &AEMuego{ port: port, log: logFunc, + done: make(chan struct{}), dataBuff: make([]byte, 8), //debugLog: f, }, nil } func (a *AEMuego) Start(ctx context.Context) error { + // Fail fast if we can't open the port at all on the initial attempt so the + // caller gets immediate feedback. Later drops are handled by run(). + if err := a.openPort(); err != nil { + return err + } + go a.run(ctx) + + return nil +} + +// openPort opens the serial port and stores it on the client. +func (a *AEMuego) openPort() error { mode := &serial.Mode{ BaudRate: 9600, } @@ -55,13 +75,59 @@ func (a *AEMuego) Start(ctx context.Context) error { if err != nil { return err } + sp.SetReadTimeout(5 * time.Millisecond) + + a.mu.Lock() a.sp = sp + a.mu.Unlock() + return nil +} - a.sp.SetReadTimeout(5 * time.Millisecond) +// getPort returns the current serial port, or nil if none is open. +func (a *AEMuego) getPort() serial.Port { + a.mu.Lock() + defer a.mu.Unlock() + return a.sp +} - go a.run(ctx) +// closePort closes the current serial port if open. It is safe to call +// multiple times. +func (a *AEMuego) closePort() { + a.mu.Lock() + sp := a.sp + a.sp = nil + a.mu.Unlock() + if sp != nil { + if err := sp.Close(); err != nil { + a.log("AEM: " + err.Error()) + } + } +} - return nil +// reconnect keeps trying to reopen the serial port until it succeeds or the +// client is stopped. It returns true when the port was reopened, false when +// the client is shutting down. +func (a *AEMuego) reconnect(ctx context.Context) bool { + for { + if ctx.Err() != nil || a.closed.Load() { + return false + } + a.log("AEM: reconnecting to " + a.port) + if err := a.openPort(); err != nil { + a.log("AEM: reconnect failed: " + err.Error()) + select { + case <-ctx.Done(): + return false + case <-a.done: + return false + case <-time.After(reconnectDelay): + } + continue + } + a.log("AEM: reconnected to " + a.port) + a.dataPos = 0 + return true + } } func (a *AEMuego) GetLambda() float64 { @@ -77,16 +143,35 @@ func (a *AEMuego) SetLambda(value float64) { } func (a *AEMuego) run(ctx context.Context) { + // Make sure any port we (re)opened gets cleaned up when run() exits. + defer a.closePort() + buf := make([]byte, 8) for { + if ctx.Err() != nil || a.closed.Load() { + return + } + + sp := a.getPort() + if sp == nil { + if !a.reconnect(ctx) { + return + } + continue + } + // read from serial - n, err := a.sp.Read(buf) - if ctx.Err() != nil { + n, err := sp.Read(buf) + if ctx.Err() != nil || a.closed.Load() { return } if err != nil { a.log("AEM: " + err.Error()) - return + a.closePort() + if !a.reconnect(ctx) { + return + } + continue } if n == 0 { continue @@ -126,12 +211,12 @@ func (a *AEMuego) run(ctx context.Context) { func (a *AEMuego) Stop() { a.closeOnce.Do(func() { - if a.sp != nil { - a.log("Stopping AEM serial client") - if err := a.sp.Close(); err != nil { - a.log(err.Error()) - } - } + a.log("Stopping AEM serial client") + // Signal run()/reconnect() to stop before closing the port so a port + // drop isn't mistaken for a reconnect opportunity. + a.closed.Store(true) + close(a.done) + a.closePort() //if a.debugLog != nil { // a.debugLog.Sync() // a.debugLog.Close() diff --git a/pkg/wbl/innovate/innovate.go b/pkg/wbl/innovate/innovate.go index 56cf884e..8d336ad8 100644 --- a/pkg/wbl/innovate/innovate.go +++ b/pkg/wbl/innovate/innovate.go @@ -7,6 +7,7 @@ import ( "fmt" "log" "sync" + "sync/atomic" "time" "go.bug.st/serial" @@ -16,6 +17,10 @@ const ( ProductString = "Innovate Serial Protocol v2" ) +// reconnectDelay is how long run() waits between reconnect attempts after the +// serial port drops (a common occurrence on Windows). +const reconnectDelay = time.Second + const ( ISP2_NORMAL uint8 = iota ISP2_O2 @@ -57,7 +62,10 @@ type ISP2Client struct { syncBuffer []byte // New field for synchronization - mu sync.Mutex + closeOnce sync.Once + closed atomic.Bool + done chan struct{} + mu sync.Mutex log func(string) } @@ -66,39 +74,104 @@ func NewISP2Client(port string, logFunc func(string)) (*ISP2Client, error) { return &ISP2Client{ port: port, buff: bytes.NewBuffer(nil), + done: make(chan struct{}), log: logFunc, }, nil } func (c *ISP2Client) Start(ctx context.Context) error { - if c.port != "txbridge" { - c.log("Starting ISP2 client") - mode := &serial.Mode{ - BaudRate: 9600, - } - sp, err := serial.Open(c.port, mode) - if err != nil { - return err - } - c.sp = sp - - c.sp.SetReadTimeout(20 * time.Millisecond) + if c.port == "txbridge" { + return nil + } + c.log("Starting ISP2 client") + // Fail fast if we can't open the port at all on the initial attempt so the + // caller gets immediate feedback. Later drops are handled by run(). + if err := c.openPort(); err != nil { + return err + } + go c.run(ctx) + return nil +} - // c.syncBuffer = make([]byte, 4) // Initialize syncBuffer - go c.run(ctx) +// openPort opens the serial port and stores it on the client. +func (c *ISP2Client) openPort() error { + mode := &serial.Mode{ + BaudRate: 9600, } + sp, err := serial.Open(c.port, mode) + if err != nil { + return err + } + sp.SetReadTimeout(20 * time.Millisecond) + + c.mu.Lock() + c.sp = sp + c.mu.Unlock() return nil } -func (c *ISP2Client) Stop() { - if c.sp != nil { - c.log("Stopping ISP2 client") - if err := c.sp.Close(); err != nil { - c.log(err.Error()) +// getPort returns the current serial port, or nil if none is open. +func (c *ISP2Client) getPort() serial.Port { + c.mu.Lock() + defer c.mu.Unlock() + return c.sp +} + +// closePort closes the current serial port if open. It is safe to call +// multiple times. +func (c *ISP2Client) closePort() { + c.mu.Lock() + sp := c.sp + c.sp = nil + c.mu.Unlock() + if sp != nil { + if err := sp.Close(); err != nil { + c.log("isp2: " + err.Error()) } } } +// reconnect keeps trying to reopen the serial port until it succeeds or the +// client is stopped. It returns true when the port was reopened, false when +// the client is shutting down. +func (c *ISP2Client) reconnect(ctx context.Context) bool { + for { + if ctx.Err() != nil || c.closed.Load() { + return false + } + c.log("isp2: reconnecting to " + c.port) + if err := c.openPort(); err != nil { + c.log("isp2: reconnect failed: " + err.Error()) + select { + case <-ctx.Done(): + return false + case <-c.done: + return false + case <-time.After(reconnectDelay): + } + continue + } + c.log("isp2: reconnected to " + c.port) + // Drop any partially-parsed frame from before the drop. + c.mu.Lock() + c.syncBuffer = nil + c.wordIndex = 0 + c.mu.Unlock() + return true + } +} + +func (c *ISP2Client) Stop() { + c.closeOnce.Do(func() { + c.log("Stopping ISP2 client") + // Signal run()/reconnect() to stop before closing the port so a port + // drop isn't mistaken for a reconnect opportunity. + c.closed.Store(true) + close(c.done) + c.closePort() + }) +} + func (c *ISP2Client) SetData(data []byte) { c.processBytes(data) } @@ -163,16 +236,35 @@ func (c *ISP2Client) String() string { } func (c *ISP2Client) run(ctx context.Context) { + // Make sure any port we (re)opened gets cleaned up when run() exits. + defer c.closePort() + buf := make([]byte, 16) for { + if ctx.Err() != nil || c.closed.Load() { + return + } + + sp := c.getPort() + if sp == nil { + if !c.reconnect(ctx) { + return + } + continue + } + // read from serial - n, err := c.sp.Read(buf) - if ctx.Err() != nil { + n, err := sp.Read(buf) + if ctx.Err() != nil || c.closed.Load() { return } if err != nil { c.log("isp2: " + err.Error()) - return + c.closePort() + if !c.reconnect(ctx) { + return + } + continue } if n == 0 { continue diff --git a/pkg/wbl/stag/stag.go b/pkg/wbl/stag/stag.go index 2ba85287..04169696 100644 --- a/pkg/wbl/stag/stag.go +++ b/pkg/wbl/stag/stag.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sync" + "sync/atomic" "time" "go.bug.st/serial" @@ -11,6 +12,10 @@ import ( const ProductString = "Stag AFR" +// reconnectDelay is how long run() waits between reconnect attempts after the +// serial port drops (a common occurrence on Windows). +const reconnectDelay = time.Second + type STAG struct { port string sp serial.Port @@ -21,19 +26,33 @@ type STAG struct { log func(string) closeOnce sync.Once + closed atomic.Bool + done chan struct{} mu sync.Mutex - worker *workerInfo } func NewSTAGClient(port string, logFunc func(string)) (*STAG, error) { return &STAG{ port: port, + done: make(chan struct{}), log: logFunc, }, nil } func (a *STAG) Start(ctx context.Context) error { + // Fail fast if we can't open the port at all on the initial attempt so the + // caller gets immediate feedback. Later drops are handled by run(). + if err := a.openPort(); err != nil { + return err + } + + go a.run(ctx) + return nil +} + +// openPort opens the serial port and stores it on the client. +func (a *STAG) openPort() error { mode := &serial.Mode{ BaudRate: 57600, } @@ -41,21 +60,58 @@ func (a *STAG) Start(ctx context.Context) error { if err != nil { return err } + sp.SetReadTimeout(5 * time.Millisecond) + + a.mu.Lock() a.sp = sp + a.mu.Unlock() + return nil +} - a.sp.SetReadTimeout(5 * time.Millisecond) +// getPort returns the current serial port, or nil if none is open. +func (a *STAG) getPort() serial.Port { + a.mu.Lock() + defer a.mu.Unlock() + return a.sp +} - ctx, cancel := context.WithCancel(context.Background()) - a.worker = &workerInfo{ - cancel: cancel, - done: make(chan struct{}), +// closePort closes the current serial port if open. It is safe to call +// multiple times. +func (a *STAG) closePort() { + a.mu.Lock() + sp := a.sp + a.sp = nil + a.mu.Unlock() + if sp != nil { + if err := sp.Close(); err != nil { + a.log(err.Error()) + } } - go func() { - a.run(ctx) - close(a.worker.done) - }() +} - return nil +// reconnect keeps trying to reopen the serial port until it succeeds or the +// client is stopped. It returns true when the port was reopened, false when +// the client is shutting down. +func (a *STAG) reconnect(ctx context.Context) bool { + for { + if ctx.Err() != nil || a.closed.Load() { + return false + } + a.log("Stag: reconnecting to " + a.port) + if err := a.openPort(); err != nil { + a.log("Stag: reconnect failed: " + err.Error()) + select { + case <-ctx.Done(): + return false + case <-a.done: + return false + case <-time.After(reconnectDelay): + } + continue + } + a.log("Stag: reconnected to " + a.port) + return true + } } func (a *STAG) GetLambda() float64 { @@ -65,6 +121,42 @@ func (a *STAG) GetLambda() float64 { } func (a *STAG) run(ctx context.Context) { + // Make sure any port we (re)opened gets cleaned up when run() exits. + defer a.closePort() + + for { + if ctx.Err() != nil || a.closed.Load() { + return + } + + sp := a.getPort() + if sp == nil { + if !a.reconnect(ctx) { + return + } + continue + } + + // session() runs until the port errors or the client is stopped. + a.session(ctx, sp) + + if ctx.Err() != nil || a.closed.Load() { + return + } + a.closePort() + if !a.reconnect(ctx) { + return + } + } +} + +// session drives one connection: it reads from sp and parses packets until the +// port errors or the client is stopped. +func (a *STAG) session(ctx context.Context, sp serial.Port) { + // sessionCtx tears down the reader goroutine when this session ends. + sessionCtx, cancel := context.WithCancel(ctx) + defer cancel() + packetContentBuffer := make([]byte, 0, 64) buf := make([]byte, 8) packetStarted := false @@ -80,19 +172,26 @@ func (a *STAG) run(ctx context.Context) { go func() { for { // read from serial - n, err := a.sp.Read(buf) - if ctx.Err() != nil { + n, err := sp.Read(buf) + if sessionCtx.Err() != nil { + return + } + if err != nil { + select { + case errChan <- err: + case <-sessionCtx.Done(): + } return } if n == 0 { continue } - - if err != nil { - errChan <- err - } for _, b := range buf[:n] { - byteChan <- b + select { + case byteChan <- b: + case <-sessionCtx.Done(): + return + } } } }() @@ -101,9 +200,11 @@ func (a *STAG) run(ctx context.Context) { select { case <-ctx.Done(): return + case <-a.done: + return case err := <-errChan: a.log(err.Error()) - // Handle errorfunc + // Port dropped; let run() handle reconnection. return case aByte := <-byteChan: if !packetStarted && aByte == 0x32 { @@ -128,12 +229,12 @@ func (a *STAG) run(ctx context.Context) { func (a *STAG) Stop() { a.closeOnce.Do(func() { - if a.sp != nil { - a.log("Stopping Stag serial client") - if err := a.sp.Close(); err != nil { - a.log(err.Error()) - } - } + a.log("Stopping Stag serial client") + // Signal run()/session()/reconnect() to stop before closing the port so + // a port drop isn't mistaken for a reconnect opportunity. + a.closed.Store(true) + close(a.done) + a.closePort() }) } func (a *STAG) processPacket(packetContentBuffer []byte) { @@ -158,7 +259,9 @@ func (a *STAG) processPacket(packetContentBuffer []byte) { func (a *STAG) sendRequest(data []byte) { time.Sleep(100 * time.Millisecond) - a.sp.Write(data) + if sp := a.getPort(); sp != nil { + sp.Write(data) + } } func (a *STAG) SetData(data []byte) error { @@ -184,8 +287,3 @@ func (a *STAG) SetData(data []byte) error { func (a *STAG) String() string { return fmt.Sprintf("Lambda: %.4f, Oxygen: %.3f", a.lambda, a.oxygen) } - -type workerInfo struct { - cancel context.CancelFunc - done chan struct{} -} diff --git a/pkg/wbl/zeitronix/zeitronix.go b/pkg/wbl/zeitronix/zeitronix.go index 7cbb53de..c979d1de 100644 --- a/pkg/wbl/zeitronix/zeitronix.go +++ b/pkg/wbl/zeitronix/zeitronix.go @@ -4,8 +4,8 @@ import ( "context" "errors" "fmt" - "log" "sync" + "sync/atomic" "time" "go.bug.st/serial" @@ -13,6 +13,10 @@ import ( const ProductString = "Zeitronix ZT-2" +// reconnectDelay is how long the serial handler waits between reconnect +// attempts after the serial port drops (a common occurrence on Windows). +const reconnectDelay = time.Second + /* Zeitronix Packet format, []byte [0] always 0 @@ -41,18 +45,34 @@ type Zeitronix struct { p serial.Port closeOnce sync.Once + closed atomic.Bool + done chan struct{} + mu sync.Mutex logFunc func(string) } func NewZeitronixClient(port string, logFunc func(string)) (*Zeitronix, error) { z := &Zeitronix{ Port: port, + done: make(chan struct{}), logFunc: logFunc, } return z, nil } func (z *Zeitronix) Start(ctx context.Context) error { + // Fail fast if we can't open the port at all on the initial attempt so the + // caller gets immediate feedback. Later drops are handled by serialHandler(). + if err := z.openPort(); err != nil { + return err + } + go z.serialHandler(ctx) + + return nil +} + +// openPort opens the serial port and stores it on the client. +func (z *Zeitronix) openPort() error { mode := &serial.Mode{ BaudRate: 9600, Parity: serial.NoParity, @@ -63,23 +83,94 @@ func (z *Zeitronix) Start(ctx context.Context) error { if err != nil { return err } - z.p = sp - z.p.SetReadTimeout(500 * time.Millisecond) - go z.serialHandler() + sp.SetReadTimeout(5 * time.Millisecond) + z.mu.Lock() + z.p = sp + z.mu.Unlock() return nil } -func (z *Zeitronix) serialHandler() { +// getPort returns the current serial port, or nil if none is open. +func (z *Zeitronix) getPort() serial.Port { + z.mu.Lock() + defer z.mu.Unlock() + return z.p +} + +// closePort closes the current serial port if open. It is safe to call +// multiple times. +func (z *Zeitronix) closePort() { + z.mu.Lock() + sp := z.p + z.p = nil + z.mu.Unlock() + if sp != nil { + if err := sp.Close(); err != nil { + z.logFunc("Zeitronix: " + err.Error()) + } + } +} + +// reconnect keeps trying to reopen the serial port until it succeeds or the +// client is stopped. It returns true when the port was reopened, false when +// the client is shutting down. +func (z *Zeitronix) reconnect(ctx context.Context) bool { + for { + if ctx.Err() != nil || z.closed.Load() { + return false + } + z.logFunc("Zeitronix: reconnecting to " + z.Port) + if err := z.openPort(); err != nil { + z.logFunc("Zeitronix: reconnect failed: " + err.Error()) + select { + case <-ctx.Done(): + return false + case <-z.done: + return false + case <-time.After(reconnectDelay): + } + continue + } + z.logFunc("Zeitronix: reconnected to " + z.Port) + return true + } +} + +func (z *Zeitronix) serialHandler(ctx context.Context) { + // Make sure any port we (re)opened gets cleaned up when this returns. + defer z.closePort() + buff := make([]byte, 14) cmd := make([]byte, 14) step := 0 for { - n, err := z.p.Read(buff) - if err != nil { - log.Println("Zeitronix read error:", err) + if ctx.Err() != nil || z.closed.Load() { + return + } + + sp := z.getPort() + if sp == nil { + if !z.reconnect(ctx) { + return + } + step = 0 + continue + } + + n, err := sp.Read(buff) + if ctx.Err() != nil || z.closed.Load() { return } + if err != nil { + z.logFunc("Zeitronix read error: " + err.Error()) + z.closePort() + step = 0 + if !z.reconnect(ctx) { + return + } + continue + } if n == 0 { continue } @@ -128,9 +219,11 @@ func (z *Zeitronix) SetData(data []byte) error { func (z *Zeitronix) Stop() { z.closeOnce.Do(func() { z.logFunc("Closing Zeitronix client") - if z.p != nil { - z.p.Close() - } + // Signal serialHandler()/reconnect() to stop before closing the port so + // a port drop isn't mistaken for a reconnect opportunity. + z.closed.Store(true) + close(z.done) + z.closePort() }) } From 28efe96347203b68f6cba12ad372f16526ba0a48 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 11 Jun 2026 22:49:59 +0200 Subject: [PATCH 015/102] widget improvements --- pkg/widgets/cbar/cbar.go | 30 +++++---- pkg/widgets/dashboard/dashboard.go | 38 +++++------- pkg/widgets/dashboard/setters.go | 15 ++++- pkg/widgets/hbar/hbar.go | 59 +++++++++--------- pkg/widgets/icon/icon.go | 21 ++++--- pkg/widgets/symbollist/symbollist.go | 91 ++++++++++++++++++++++------ pkg/widgets/vbar/vbar.go | 16 +++-- pkg/windows/mainWindow.go | 4 +- 8 files changed, 173 insertions(+), 101 deletions(-) diff --git a/pkg/widgets/cbar/cbar.go b/pkg/widgets/cbar/cbar.go index b51acb9b..774530f1 100644 --- a/pkg/widgets/cbar/cbar.go +++ b/pkg/widgets/cbar/cbar.go @@ -43,6 +43,9 @@ type CBar struct { // Fast float formatting fmtPrec int buf []byte + + // Cached monospace glyph width for the current display TextSize + charWidth float32 } func New(cfg *widgets.GaugeConfig) *CBar { @@ -122,10 +125,11 @@ func (s *CBar) initializeVisualElements() { } func (s *CBar) SetValue(value float64) { + value = max(s.cfg.Min, min(s.cfg.Max, value)) if value == s.value { return } - s.value = max(s.cfg.Min, min(s.cfg.Max, value)) + s.value = value barPosition := s.center var pxWidth float32 @@ -170,7 +174,6 @@ func (s *CBar) updateDisplayTextPosition() { if len(text) == 0 { return } - minSize := s.displayText.MinSize() dotIdx := -1 for i := 0; i < len(text); i++ { @@ -182,10 +185,9 @@ func (s *CBar) updateDisplayTextPosition() { var x float32 if dotIdx >= 0 { - charWidth := minSize.Width / float32(len(text)) - x = s.lastSize.Width*0.5 - charWidth*(float32(dotIdx)+0.5) + x = s.lastSize.Width*0.5 - s.charWidth*(float32(dotIdx)+0.5) } else { - x = s.lastSize.Width*0.5 - minSize.Width*0.5 + x = s.lastSize.Width*0.5 - s.charWidth*float32(len(text))*0.5 } s.displayText.Move(fyne.Position{X: x, Y: s.displayY}) @@ -198,11 +200,12 @@ func (s *CBar) SetValue2(value float64) { func (s *CBar) CreateRenderer() fyne.WidgetRenderer { // Initialize visual elements s.initializeVisualElements() - return &CBarRenderer{s} + return &CBarRenderer{CBar: s} } type CBarRenderer struct { *CBar + objects []fyne.CanvasObject } func (r *CBarRenderer) MinSize() fyne.Size { @@ -265,6 +268,7 @@ func (r *CBarRenderer) Layout(space fyne.Size) { r.bar.Resize(fyne.Size{Width: r.barWidth * r.widthFactor, Height: r.barHeight}) r.displayText.TextSize = r.bar.Size().Height - 8 + r.charWidth = fyne.MeasureText("0", r.displayText.TextSize, r.displayText.TextStyle).Width var y float32 switch r.cfg.TextPosition { @@ -282,11 +286,13 @@ func (r *CBarRenderer) Layout(space fyne.Size) { } func (r *CBarRenderer) Objects() []fyne.CanvasObject { - objs := []fyne.CanvasObject{} - for _, line := range r.bars { - objs = append(objs, line) + if r.objects == nil { + objs := make([]fyne.CanvasObject, 0, len(r.bars)+4) + for _, line := range r.bars { + objs = append(objs, line) + } + objs = append(objs, r.bar, r.face, r.titleText, r.displayText) + r.objects = objs } - - objs = append(objs, r.bar, r.face, r.titleText, r.displayText) - return objs + return r.objects } diff --git a/pkg/widgets/dashboard/dashboard.go b/pkg/widgets/dashboard/dashboard.go index 91c7e9c4..cc82947e 100644 --- a/pkg/widgets/dashboard/dashboard.go +++ b/pkg/widgets/dashboard/dashboard.go @@ -6,8 +6,6 @@ import ( "log" "time" - _ "embed" - "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/theme" @@ -80,11 +78,11 @@ type Config struct { AirDemToString func(float64) string UseMPH bool SwapRPMandSpeed bool - Low float64 - High float64 - WidebandSymbol string - MetricRouter map[string]func(float64) - FullscreenFunc func(bool) + // Low/High set the wideband lambda bar display range (defaults 0.5–1.5). + Low float64 + High float64 + WidebandSymbol string + FullscreenFunc func(bool) } func NewDashboard(cfg *Config) *Dashboard { @@ -99,6 +97,11 @@ func NewDashboard(cfg *Config) *Dashboard { speedometerText = "mph" } + wbLow, wbHigh := cfg.Low, cfg.High + if wbLow >= wbHigh { + wbLow, wbHigh = 0.5, 1.5 + } + db := &Dashboard{ cfg: cfg, logplayer: cfg.Logplayer, @@ -167,9 +170,9 @@ func NewDashboard(cfg *Config) *Dashboard { }), wblambda: cbar.New(&widgets.GaugeConfig{ Title: "", - Min: 0.50, - Center: 1, - Max: 1.50, + Min: wbLow, + Center: (wbLow + wbHigh) * 0.5, + Max: wbHigh, Steps: 20, MinSize: fyne.NewSize(50, 35), DisplayString: "λ %.2f", @@ -242,10 +245,6 @@ func NewDashboard(cfg *Config) *Dashboard { } db.ExtendBaseWidget(db) - db.text.cruise.Hide() - db.image.checkEngine.Hide() - db.image.limpMode.Hide() - db.metricRouter = db.createRouter() var isFullscreen bool @@ -414,10 +413,8 @@ type dims struct { sixthWidth float32 thirdHeight float32 tenthHeight float32 - halfHeight float32 centerX float32 centerY float32 - bottomY float32 textSize float32 smallTextSize float32 } @@ -642,12 +639,8 @@ func (dr *DashboardRenderer) Layout(space fyne.Size) { sixthWidth: space.Width * common.OneSixth, thirdHeight: (space.Height - 50) * .33, tenthHeight: (space.Height - 50) * .1, - halfHeight: (space.Height - 50) * .5, centerX: space.Width * 0.5, centerY: space.Height * 0.5, - bottomY: space.Height - 55, - - // textSize: max(min(space.Height, space.Width)*0.07, 20), } // Layout horizontal bars dr.db.layoutBars(dims) @@ -661,10 +654,7 @@ func (dr *DashboardRenderer) Layout(space fyne.Size) { dr.db.fullscreenBtn.Resize(fyne.NewSize(btnWidth, btnHeigh)) dr.db.fullscreenBtn.Move(fyne.NewPos(space.Width-btnWidth, space.Height-btnHeigh)) - dims.textSize = dr.db.gauges.nblambda.Size().Height - 2 - dims.smallTextSize = dims.textSize * 0.5 - - // Layout text elements + // Layout text elements (computes its own textSize/smallTextSize) dr.db.layoutTexts(dims) // Layout icons diff --git a/pkg/widgets/dashboard/setters.go b/pkg/widgets/dashboard/setters.go index 57f1b3fb..0b211280 100644 --- a/pkg/widgets/dashboard/setters.go +++ b/pkg/widgets/dashboard/setters.go @@ -1,8 +1,8 @@ package dashboard import ( - "fmt" "image/color" + "math" "strconv" "time" @@ -139,13 +139,24 @@ func textSetter(obj *canvas.Text, text, unit string, precision int) func(float64 } func idcSetter(obj *canvas.Text, text string) func(float64) { + var buf []byte oldValue := -1.0 return func(value float64) { if value == oldValue { return } oldValue = value - obj.Text = fmt.Sprintf(text+": %02.0f%%", value) + buf = buf[:0] + buf = append(buf, text...) + buf = append(buf, ": "...) + // Matches the old "%02.0f%%" format: rounded, zero-padded to two digits. + iv := int64(math.Round(value)) + if iv >= 0 && iv < 10 { + buf = append(buf, '0') + } + buf = strconv.AppendInt(buf, iv, 10) + buf = append(buf, '%') + obj.Text = string(buf) switch { case value > 60 && value < 85: obj.Color = color.RGBA{R: 0xFF, G: 0xA5, B: 0, A: 0xFF} diff --git a/pkg/widgets/hbar/hbar.go b/pkg/widgets/hbar/hbar.go index 64add695..633f733e 100644 --- a/pkg/widgets/hbar/hbar.go +++ b/pkg/widgets/hbar/hbar.go @@ -188,7 +188,7 @@ func (s *HBar) CreateRenderer() fyne.WidgetRenderer { s.lines[i] = line } - return &HBarRenderer{s} + return &HBarRenderer{HBar: s} } // getColorForValue returns fill & stroke color for an arbitrary gauge value. @@ -229,11 +229,12 @@ func (s *HBar) getColorForValue(value float64) (fillColor, strokeColor color.RGB } type HBarRenderer struct { - *HBar + HBar *HBar + objects []fyne.CanvasObject } func (r *HBarRenderer) MinSize() fyne.Size { - return r.cfg.MinSize + return r.HBar.cfg.MinSize } func (r *HBarRenderer) Refresh() { @@ -245,41 +246,41 @@ func (r *HBarRenderer) Destroy() { } func (r *HBarRenderer) Layout(space fyne.Size) { - if r.size == space { + if r.HBar.size == space { return } - r.size = space + r.HBar.size = space - r.layoutValues.middle = space.Height * 0.5 + r.HBar.layoutValues.middle = space.Height * 0.5 - stepFactor := float32(space.Width) / float32(r.cfg.Steps) + stepFactor := float32(space.Width) / float32(r.HBar.cfg.Steps) // Face layout - r.face.Move(fyne.Position{X: -2, Y: 0}) - r.face.Resize(space.AddWidthHeight(3, 1)) + r.HBar.face.Move(fyne.Position{X: -2, Y: 0}) + r.HBar.face.Resize(space.AddWidthHeight(3, 1)) // Title centered horizontally, just below bar - titleMinSize := r.titleText.MinSize() - r.titleText.Resize(fyne.Size{Width: space.Width, Height: titleMinSize.Height}) - r.titleText.Move(fyne.Position{ + titleMinSize := r.HBar.titleText.MinSize() + r.HBar.titleText.Resize(fyne.Size{Width: space.Width, Height: titleMinSize.Height}) + r.HBar.titleText.Move(fyne.Position{ X: 0, Y: space.Height - titleMinSize.Height, }) // Display text in the middle, centered vertically - displayMinSize := r.displayText.MinSize() - r.displayText.Resize(fyne.Size{Width: space.Width, Height: displayMinSize.Height}) - r.displayText.Move(fyne.Position{ + displayMinSize := r.HBar.displayText.MinSize() + r.HBar.displayText.Resize(fyne.Size{Width: space.Width, Height: displayMinSize.Height}) + r.HBar.displayText.Move(fyne.Position{ X: 0, - Y: r.layoutValues.middle - displayMinSize.Height*0.5, + Y: r.HBar.layoutValues.middle - displayMinSize.Height*0.5, }) // Tick lines layout (vertical lines across width) oneThird := space.Height * common.OneThird oneSeventh := space.Height * common.OneSeventh - middle := r.layoutValues.middle + middle := r.HBar.layoutValues.middle - for i, line := range r.lines { + for i, line := range r.HBar.lines { x := float32(i) * stepFactor if i%2 == 0 { line.Position1 = fyne.Position{X: x, Y: middle - oneThird} @@ -291,19 +292,23 @@ func (r *HBarRenderer) Layout(space fyne.Size) { } // Bar position is fixed at origin; set once here so SetValue can skip it. - r.bar.Move(fyne.Position{X: 0, Y: 0}) + r.HBar.bar.Move(fyne.Position{X: 0, Y: 0}) // Recompute bar geometry for current value using new size - norm := r.clampNorm(r.value) - barWidth := norm * float32(r.size.Width) - r.bar.Resize(fyne.Size{Width: barWidth, Height: r.size.Height}) + norm := r.HBar.clampNorm(r.HBar.value) + barWidth := norm * float32(r.HBar.size.Width) + r.HBar.bar.Resize(fyne.Size{Width: barWidth, Height: r.HBar.size.Height}) } func (r *HBarRenderer) Objects() []fyne.CanvasObject { - objs := make([]fyne.CanvasObject, 0, len(r.lines)+4) - for _, line := range r.lines { - objs = append(objs, line) + if r.objects == nil { + + objs := make([]fyne.CanvasObject, 0, len(r.HBar.lines)+4) + for _, line := range r.HBar.lines { + objs = append(objs, line) + } + objs = append(objs, r.HBar.bar, r.HBar.face, r.HBar.titleText, r.HBar.displayText) + r.objects = objs } - objs = append(objs, r.bar, r.face, r.titleText, r.displayText) - return objs + return r.objects } diff --git a/pkg/widgets/icon/icon.go b/pkg/widgets/icon/icon.go index d55fc347..0debccb7 100644 --- a/pkg/widgets/icon/icon.go +++ b/pkg/widgets/icon/icon.go @@ -46,22 +46,23 @@ func (ic *Icon) SetText(text string) { } func (ic *Icon) CreateRenderer() fyne.WidgetRenderer { - return &IconRenderer{ic} + return &IconRenderer{IC: ic} } type IconRenderer struct { - *Icon + IC *Icon + objects []fyne.CanvasObject } func (ic *IconRenderer) Layout(size fyne.Size) { - ic.cfg.Image.Move(fyne.NewPos(0, 0)) - ic.cfg.Image.Resize(ic.cfg.Minsize) - ic.text.Resize(fyne.NewSize(size.Width, 30)) - ic.text.Move(fyne.NewPos(14, 87)) + ic.IC.cfg.Image.Move(fyne.NewPos(0, 0)) + ic.IC.cfg.Image.Resize(ic.IC.cfg.Minsize) + ic.IC.text.Resize(fyne.NewSize(size.Width, 30)) + ic.IC.text.Move(fyne.NewPos(14, 87)) } func (ic *IconRenderer) MinSize() fyne.Size { - return ic.cfg.Minsize + return ic.IC.cfg.Minsize } func (ic *IconRenderer) Refresh() { @@ -71,5 +72,9 @@ func (ic *IconRenderer) Destroy() { } func (ic *IconRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{ic.cfg.Image, ic.text} + if ic.objects == nil { + ic.objects = []fyne.CanvasObject{ic.IC.cfg.Image, ic.IC.text} + } + return ic.objects + // return []fyne.CanvasObject{ic.IC.cfg.Image, ic.IC.text} --- IGNORE --- } diff --git a/pkg/widgets/symbollist/symbollist.go b/pkg/widgets/symbollist/symbollist.go index 590c9020..e524328a 100644 --- a/pkg/widgets/symbollist/symbollist.go +++ b/pkg/widgets/symbollist/symbollist.go @@ -2,9 +2,9 @@ package symbollist import ( "image/color" - "sort" + "math" + "slices" "strconv" - "strings" "sync" "fyne.io/fyne/v2" @@ -65,22 +65,50 @@ func (s *Widget) SetColorBlindMode(mode colors.ColorBlindMode) { } func (s *Widget) UpdateBars(enabled bool) { + s.mu.Lock() + defer s.mu.Unlock() s.updateBars = enabled } func (s *Widget) Names() []string { - names := make([]string, len(s.cfg.Symbols)+1) - for i, s := range s.cfg.Symbols { - names[i] = s.Name + s.mu.Lock() + defer s.mu.Unlock() + names := make([]string, 0, len(s.cfg.Symbols)+1) + hasWBL := false + for _, sym := range s.cfg.Symbols { + if sym.Name == datalogger.EXTERNALWBLSYM { + hasWBL = true + } + names = append(names, sym.Name) } - names[len(names)-1] = datalogger.EXTERNALWBLSYM - sort.Slice(names, func(i, j int) bool { - return strings.ToLower(names[i]) < strings.ToLower(names[j]) - }) + if !hasWBL { + names = append(names, datalogger.EXTERNALWBLSYM) + } + slices.SortFunc(names, compareFold) return names } +// compareFold orders ASCII strings case-insensitively without the per-comparison +// allocations of strings.ToLower. +func compareFold(a, b string) int { + for i := 0; i < len(a) && i < len(b); i++ { + ca, cb := a[i], b[i] + if 'A' <= ca && ca <= 'Z' { + ca += 'a' - 'A' + } + if 'A' <= cb && cb <= 'Z' { + cb += 'a' - 'A' + } + if ca != cb { + return int(ca) - int(cb) + } + } + return len(a) - len(b) +} + func (s *Widget) SetValue(name string, value float64) { + s.mu.Lock() + defer s.mu.Unlock() val, found := s.entryMap[name] if found { if value == val.value { @@ -93,22 +121,28 @@ func (s *Widget) SetValue(name string, value float64) { val.max = value } if s.updateBars { - val.valueBarFactor = float32((value - val.min) / (val.max - val.min)) + if span := val.max - val.min; span > 0 { + val.valueBarFactor = float32((value - val.min) / span) + } else { + val.valueBarFactor = 0 + } col := colors.GetColorInterpolation(val.min, val.max, value, s.cfg.ColorBlindMode) col.A = barAlpha val.valueBar.FillColor = col totalWidth := val.symbolName.Size().Width val.valueBar.Resize(fyne.Size{Width: val.valueBarFactor * totalWidth, Height: 26}) } - textValue := strconv.FormatFloat(value, 'f', val.prec, 64) - if textValue != val.lastText { - val.lastText = textValue - val.symbolValue.SetText(textValue) + val.buf = strconv.AppendFloat(val.buf[:0], value, 'f', val.prec, 64) + if string(val.buf) != val.lastText { + val.lastText = string(val.buf) + val.symbolValue.SetText(val.lastText) } } } func (s *Widget) Disable() { + s.mu.Lock() + defer s.mu.Unlock() for _, e := range s.entries { // e.symbolCorrectionfactor.Disable() e.deleteBTN.Disable() @@ -116,6 +150,8 @@ func (s *Widget) Disable() { } func (s *Widget) Enable() { + s.mu.Lock() + defer s.mu.Unlock() for _, e := range s.entries { // e.symbolCorrectionfactor.Enable() e.deleteBTN.Enable() @@ -126,6 +162,7 @@ func (s *Widget) Add(symbols ...*symbol.Symbol) { s.mu.Lock() defer s.mu.Unlock() + added := false for _, sym := range symbols { if _, found := s.entryMap[sym.Name]; found { continue @@ -156,13 +193,26 @@ func (s *Widget) Add(symbols ...*symbol.Symbol) { entry := s.newSymbolWidgetEntry(sym, deleteFunc) s.cfg.Symbols = append(s.cfg.Symbols, sym) s.entries = append(s.entries, entry) - s.container.Add(entry) + // append directly instead of container.Add to avoid a layout+refresh per entry + s.container.Objects = append(s.container.Objects, entry) s.entryMap[sym.Name] = entry + added = true + } + if added { + s.container.Refresh() } } func (s *Widget) Clear() { + s.mu.Lock() + defer s.mu.Unlock() for _, e := range s.entries { + // NaN sentinel so the next sample always renders, even if it equals + // the last value seen before the clear + e.value = math.NaN() + e.min = 0 + e.max = 0 + e.valueBarFactor = 0 e.lastText = "---" e.symbolValue.SetText("---") } @@ -241,6 +291,9 @@ func (s *Widget) newSymbolWidgetEntry(sym *symbol.Symbol, deleteFunc func(*Symbo symbol: sym, prec: symbol.GetPrecision(sym.Correctionfactor), deleteFunc: deleteFunc, + // NaN compares unequal to everything, so the first sample always + // renders — including an initial value of exactly 0 + value: math.NaN(), } sw.ExtendBaseWidget(sw) sw.symbolName = widget.NewLabel(sw.symbol.Name) @@ -306,6 +359,7 @@ type SymbolWidgetEntry struct { min, max float64 prec int lastText string + buf []byte // scratch for AppendFloat, avoids an allocation per update oldSize fyne.Size @@ -357,15 +411,12 @@ func (s *symbolWidgetEntryRenderer) MinSize() fyne.Size { } func (s *symbolWidgetEntryRenderer) Refresh() { - s.e.symbolName.Refresh() - s.e.symbolValue.Refresh() - // s.e.symbolNumber.Refresh() - // s.e.symbolCorrectionfactor.Refresh() col := colors.GetColorInterpolation(s.e.min, s.e.max, s.e.value, s.e.w.cfg.ColorBlindMode) col.A = barAlpha s.e.valueBar.FillColor = col s.e.valueBar.StrokeColor = col - s.e.valueBar.Refresh() + // cascades to the labels, delete button and value bar + s.e.container.Refresh() } func (s *symbolWidgetEntryRenderer) Objects() []fyne.CanvasObject { diff --git a/pkg/widgets/vbar/vbar.go b/pkg/widgets/vbar/vbar.go index 3df1563c..93bb2057 100644 --- a/pkg/widgets/vbar/vbar.go +++ b/pkg/widgets/vbar/vbar.go @@ -195,7 +195,7 @@ func (s *VBar) CreateRenderer() fyne.WidgetRenderer { s.lines[maxSteps-i] = line } - return &VBarRenderer{s} + return &VBarRenderer{VBar: s} } // getColorForValue returns fill & stroke color for an arbitrary gauge value. @@ -237,6 +237,7 @@ func (s *VBar) getColorForValue(value float64) (fillColor, strokeColor color.RGB type VBarRenderer struct { *VBar + objects []fyne.CanvasObject } func (r *VBarRenderer) MinSize() fyne.Size { @@ -302,10 +303,13 @@ func (r *VBarRenderer) Layout(space fyne.Size) { } func (r *VBarRenderer) Objects() []fyne.CanvasObject { - objs := make([]fyne.CanvasObject, 0, len(r.lines)+4) - for _, line := range r.lines { - objs = append(objs, line) + if r.objects == nil { + objs := make([]fyne.CanvasObject, 0, len(r.lines)+4) + for _, line := range r.lines { + objs = append(objs, line) + } + objs = append(objs, r.bar, r.face, r.titleText, r.displayText) + r.objects = objs } - objs = append(objs, r.bar, r.face, r.titleText, r.displayText) - return objs + return r.objects } diff --git a/pkg/windows/mainWindow.go b/pkg/windows/mainWindow.go index a5636b4a..f4d44b50 100644 --- a/pkg/windows/mainWindow.go +++ b/pkg/windows/mainWindow.go @@ -313,8 +313,8 @@ func (mw *MainWindow) LoadLogfileCombined(filename string, reader io.ReadCloser, Logplayer: true, UseMPH: mw.settings.GetUseMPH(), SwapRPMandSpeed: mw.settings.GetSwapRPMandSpeed(), - High: 0.5, - Low: 1.5, + High: 1.5, + Low: 0.5, WidebandSymbol: mw.settings.GetWidebandSymbolName(), } From 8ec418f012b427d160f379117cb60edd69648182 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 11 Jun 2026 22:51:51 +0200 Subject: [PATCH 016/102] mapviewer optimization --- pkg/widgets/mapviewer/mapviewer.go | 73 ++++++++++--------- pkg/widgets/mapviewer/mapviewer_keyhandler.go | 18 +++-- pkg/widgets/mapviewer/mapviewer_mouse.go | 15 ++-- 3 files changed, 59 insertions(+), 47 deletions(-) diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index 0641660c..a157296a 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -75,6 +75,9 @@ type MapViewer struct { inputBuffer strings.Builder restoreValues bool + // scratch buffer for formatting cell values without allocating + scratch []byte + popup *widget.PopUpMenu widthFactor float32 @@ -272,10 +275,11 @@ func (mv *MapViewer) SetY(yValue float64) { } func (mv *MapViewer) setCellText(idx int, value float64) { - textValue := strconv.FormatFloat(value, 'f', mv.cfg.ZPrecision, 64) - if mv.textValues[idx].Text != textValue { - mv.textValues[idx].Text = textValue - mv.textValues[idx].Refresh() + mv.scratch = strconv.AppendFloat(mv.scratch[:0], value, 'f', mv.cfg.ZPrecision, 64) + text := mv.textValues[idx] + if string(mv.scratch) != text.Text { + text.Text = string(mv.scratch) + text.Refresh() } } @@ -312,39 +316,38 @@ func (mv *MapViewer) Refresh() { } func (mv *MapViewer) createYAxis() { - mv.yAxisLabelContainer = container.New(&layout.Vertical{}) - if mv.numRows >= 1 { - for i := mv.numRows - 1; i >= 0; i-- { - text := &canvas.Text{ - Alignment: fyne.TextAlignCenter, - Text: strconv.FormatFloat(mv.cfg.YData[i], 'f', mv.cfg.YPrecision, 64), - TextSize: minTextSize + 2, - } - mv.yAxisTexts = append(mv.yAxisTexts, text) - mv.yAxisLabelContainer.Add(text) + mv.yAxisTexts = make([]*canvas.Text, 0, mv.numRows) + objs := make([]fyne.CanvasObject, 0, mv.numRows) + for i := mv.numRows - 1; i >= 0; i-- { + text := &canvas.Text{ + Alignment: fyne.TextAlignCenter, + Text: strconv.FormatFloat(mv.cfg.YData[i], 'f', mv.cfg.YPrecision, 64), + TextSize: minTextSize + 2, } - return + mv.yAxisTexts = append(mv.yAxisTexts, text) + objs = append(objs, text) } + mv.yAxisLabelContainer = container.New(&layout.Vertical{}, objs...) } func (mv *MapViewer) createXAxis() { - mv.xAxisLabelContainer = container.New(&layout.Horizontal{Offset: mv.yAxisLabelContainer}) - if mv.numColumns >= 1 { - for i := 0; i < mv.numColumns; i++ { - text := &canvas.Text{ - Alignment: fyne.TextAlignCenter, - Text: strconv.FormatFloat(mv.cfg.XData[i], 'f', mv.cfg.XPrecision, 64), - TextSize: minTextSize + 2, - } - mv.xAxisTexts = append(mv.xAxisTexts, text) - mv.xAxisLabelContainer.Add(text) + mv.xAxisTexts = make([]*canvas.Text, 0, mv.numColumns) + objs := make([]fyne.CanvasObject, 0, mv.numColumns) + for i := 0; i < mv.numColumns; i++ { + text := &canvas.Text{ + Alignment: fyne.TextAlignCenter, + Text: strconv.FormatFloat(mv.cfg.XData[i], 'f', mv.cfg.XPrecision, 64), + TextSize: minTextSize + 2, } - return + mv.xAxisTexts = append(mv.xAxisTexts, text) + objs = append(objs, text) } + mv.xAxisLabelContainer = container.New(&layout.Horizontal{Offset: mv.yAxisLabelContainer}, objs...) } func (mv *MapViewer) createTextValues() { - mv.valueTexts = container.New(layout.NewGrid(mv.numColumns, mv.numRows, 1.32)) + mv.textValues = make([]*canvas.Text, 0, mv.numData) + objs := make([]fyne.CanvasObject, 0, mv.numData) for _, v := range mv.cfg.ZData { text := &canvas.Text{ Text: strconv.FormatFloat(v, 'f', mv.cfg.ZPrecision, 64), @@ -353,19 +356,22 @@ func (mv *MapViewer) createTextValues() { Alignment: fyne.TextAlignCenter, } mv.textValues = append(mv.textValues, text) - mv.valueTexts.Add(text) + objs = append(objs, text) } + mv.valueTexts = container.New(layout.NewGrid(mv.numColumns, mv.numRows, 1.32), objs...) } func (mv *MapViewer) createZdata() { - mv.valueRects = container.New(layout.NewGrid(mv.numColumns, mv.numRows, 1.32)) + mv.zDataRects = make([]*canvas.Rectangle, 0, mv.numData) + objs := make([]fyne.CanvasObject, 0, mv.numData) for _, value := range mv.cfg.ZData { color := colors.GetColorInterpolation(mv.zMin, mv.zMax, value, mv.colorMode) rect := &canvas.Rectangle{FillColor: color, StrokeColor: color, StrokeWidth: 0} rect.SetMinSize(fyne.NewSize(34, 14)) mv.zDataRects = append(mv.zDataRects, rect) - mv.valueRects.Add(rect) + objs = append(objs, rect) } + mv.valueRects = container.New(layout.NewGrid(mv.numColumns, mv.numRows, 1.32), objs...) } func (mv *MapViewer) setXY() error { @@ -426,21 +432,18 @@ func (mv *MapViewer) resizeSelectionRect() { // Handle multiple cell selection if len(mv.selectedCells) > 1 { - // Pre-calculate divisor to avoid repeated division operations - colDivisor := float32(mv.numColumns) - // Initialize bounds using first cell to avoid unnecessary comparisons firstCell := mv.selectedCells[0] minX := firstCell % mv.numColumns maxX := minX - minY := int(float32(firstCell) / colDivisor) + minY := firstCell / mv.numColumns maxY := minY // Find bounds in a single pass for i := 1; i < len(mv.selectedCells); i++ { cell := mv.selectedCells[i] x := cell % mv.numColumns - y := int(float32(cell) / colDivisor) + y := cell / mv.numColumns if x < minX { minX = x diff --git a/pkg/widgets/mapviewer/mapviewer_keyhandler.go b/pkg/widgets/mapviewer/mapviewer_keyhandler.go index f52a1a3e..27363c11 100644 --- a/pkg/widgets/mapviewer/mapviewer_keyhandler.go +++ b/pkg/widgets/mapviewer/mapviewer_keyhandler.go @@ -137,19 +137,27 @@ func (mv *MapViewer) smooth() { } func (mv *MapViewer) updateCursor(goroutine bool) { - mv.selectedCells = []int{mv.SelectedY*mv.numColumns + mv.selectedX} + cell := mv.SelectedY*mv.numColumns + mv.selectedX + if len(mv.selectedCells) != 1 || mv.selectedCells[0] != cell { + mv.selectedCells = append(mv.selectedCells[:0], cell) + } xPosFactor := float32(mv.selectedX) yPosFactor := float32(float64(mv.numRows-1) - float64(mv.SelectedY)) xPos := xPosFactor * mv.widthFactor yPos := yPosFactor * mv.heightFactor + size := fyne.Size{Width: mv.widthFactor + 1, Height: mv.heightFactor + 1} + pos := fyne.Position{X: xPos - 1, Y: yPos - 1} + if mv.selectionRect.Size() == size && mv.selectionRect.Position() == pos { + return + } if goroutine { fyne.Do(func() { - mv.selectionRect.Resize(fyne.Size{Width: mv.widthFactor + 1, Height: mv.heightFactor + 1}) - mv.selectionRect.Move(fyne.Position{X: xPos - 1, Y: yPos - 1}) + mv.selectionRect.Resize(size) + mv.selectionRect.Move(pos) }) } else { - mv.selectionRect.Resize(fyne.Size{Width: mv.widthFactor + 1, Height: mv.heightFactor + 1}) - mv.selectionRect.Move(fyne.Position{X: xPos - 1, Y: yPos - 1}) + mv.selectionRect.Resize(size) + mv.selectionRect.Move(pos) } } diff --git a/pkg/widgets/mapviewer/mapviewer_mouse.go b/pkg/widgets/mapviewer/mapviewer_mouse.go index 929c57b2..c667cf70 100644 --- a/pkg/widgets/mapviewer/mapviewer_mouse.go +++ b/pkg/widgets/mapviewer/mapviewer_mouse.go @@ -206,16 +206,17 @@ func (mv *MapViewer) finalizeSelection(eventPos fyne.Position) { nselectedX, nSelectedY := mv.calculateSelectionBounds(eventPos) mv.updateSelection(nselectedX, nSelectedY) - // For Ctrl selections, we don't want to clear existing selections - if mv.lastModifier != fyne.KeyModifierControl { - mv.selectedCells = make([]int, 0) - } - topLeftX := min(mv.selectedX, nselectedX) bottomRightX := max(mv.selectedX, nselectedX) topLeftY := min(mv.SelectedY, nSelectedY) bottomRightY := max(mv.SelectedY, nSelectedY) + // For Ctrl selections, we don't want to clear existing selections + if mv.lastModifier != fyne.KeyModifierControl { + mv.selectedCells = make([]int, 0, (bottomRightX-topLeftX+1)*(bottomRightY-topLeftY+1)) + } + + selectedColor := theme.Color(theme.ColorNameForegroundOnPrimary) for y := topLeftY; y <= bottomRightY; y++ { for x := topLeftX; x <= bottomRightX; x++ { zIndex := y*mv.numColumns + x @@ -226,11 +227,11 @@ func (mv *MapViewer) finalizeSelection(eventPos fyne.Position) { mv.zDataRects[zIndex].FillColor = mv.zDataRects[zIndex].StrokeColor } else { mv.selectedCells = append(mv.selectedCells, zIndex) - mv.zDataRects[zIndex].FillColor = theme.Color(theme.ColorNameForegroundOnPrimary) + mv.zDataRects[zIndex].FillColor = selectedColor } } else { mv.selectedCells = append(mv.selectedCells, zIndex) - mv.zDataRects[zIndex].FillColor = theme.Color(theme.ColorNameForegroundOnPrimary) + mv.zDataRects[zIndex].FillColor = selectedColor } mv.zDataRects[zIndex].Refresh() } From c27591a273b10dfa330d826954816cb82270dc94 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 11 Jun 2026 22:52:03 +0200 Subject: [PATCH 017/102] optimize meshgrid --- pkg/widgets/meshgrid/meshgrid_axis.go | 19 -------- pkg/widgets/meshgrid/meshgrid_draw.go | 25 +++++------ pkg/widgets/meshgrid/meshgrid_widget.go | 58 ++++++++++++------------- 3 files changed, 39 insertions(+), 63 deletions(-) diff --git a/pkg/widgets/meshgrid/meshgrid_axis.go b/pkg/widgets/meshgrid/meshgrid_axis.go index 71537489..7c79d19d 100644 --- a/pkg/widgets/meshgrid/meshgrid_axis.go +++ b/pkg/widgets/meshgrid/meshgrid_axis.go @@ -9,25 +9,6 @@ import ( "golang.org/x/image/math/fixed" ) -// Add new struct for axis indicator -type AxisIndicator struct { - origin Vertex - xAxis Vertex - yAxis Vertex - zAxis Vertex - axisScale float64 -} - -func NewAxisIndicator(scale float64) AxisIndicator { - return AxisIndicator{ - origin: Vertex{X: 0, Y: 0, Z: 0}, - xAxis: Vertex{X: scale, Y: 0, Z: 0}, - yAxis: Vertex{X: 0, Y: scale, Z: 0}, - zAxis: Vertex{X: 0, Y: 0, Z: scale}, - axisScale: scale, - } -} - func (m *Meshgrid) drawAxisIndicator(img *image.RGBA) { cornerOffset := 60.0 indicatorScale := 60.0 diff --git a/pkg/widgets/meshgrid/meshgrid_draw.go b/pkg/widgets/meshgrid/meshgrid_draw.go index d4f023ae..c41ea6c5 100644 --- a/pkg/widgets/meshgrid/meshgrid_draw.go +++ b/pkg/widgets/meshgrid/meshgrid_draw.go @@ -33,7 +33,7 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { img = image.NewRGBA(image.Rect(0, 0, w, h)) m.scratchImg = img } else { - clearPix(img.Pix) + clear(img.Pix) } // Find min/max of the view-space Z for depth shading. @@ -143,12 +143,6 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { return img } -func clearPix(p []uint8) { - for i := range p { - p[i] = 0 - } -} - // getColorWithDepth combines color interpolation and depth enhancement in one step func (m *Meshgrid) getColorWithDepth(value, depthFactor float64) color.RGBA { // Get base color from value @@ -184,13 +178,15 @@ func (m *Meshgrid) getColorWithDepth(value, depthFactor float64) color.RGBA { } } -// Fade a color by a factor (used for diagonals) +// Fade a color by a factor (used for diagonals). Alpha is left untouched: +// the buffer uses straight alpha, so dimming RGB and A together would fade +// the line twice over once composited. func fadeColor(c color.RGBA, factor float64) color.RGBA { return color.RGBA{ R: uint8(float64(c.R) * factor), G: uint8(float64(c.G) * factor), B: uint8(float64(c.B) * factor), - A: uint8(float64(c.A) * factor), + A: c.A, } } @@ -201,8 +197,11 @@ func drawBresenhamLine(img *image.RGBA, x0, y0, x1, y1 int, c1, c2 color.RGBA) { return // fully outside } - // Translate to image origin for indexing - ox, oy := r.Min.X, r.Min.Y + // Translate to image origin once so the pixel loop indexes directly. + x0 -= r.Min.X + x1 -= r.Min.X + y0 -= r.Min.Y + y1 -= r.Min.Y stride := img.Stride pix := img.Pix @@ -225,7 +224,7 @@ func drawBresenhamLine(img *image.RGBA, x0, y0, x1, y1 int, c1, c2 color.RGBA) { total = -dy } if total == 0 { - setPix(pix, stride, x0-ox, y0-oy, c1) + setPix(pix, stride, x0, y0, c1) return } @@ -242,7 +241,7 @@ func drawBresenhamLine(img *image.RGBA, x0, y0, x1, y1 int, c1, c2 color.RGBA) { // Draw for i := 0; ; i++ { - setPixRGBAFixed(pix, stride, x0-ox, y0-oy, accR, accG, accB, accA) + setPixRGBAFixed(pix, stride, x0, y0, accR, accG, accB, accA) if x0 == x1 && y0 == y1 { break diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index 63b6f4d2..549e409f 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -230,19 +230,12 @@ func (m *Meshgrid) updateVertexPositions() { } } +// SetFloat64 updates a single cell value. The whole mesh is rebuilt since a +// new value can shift zmin/zmax and with it every vertex's normalized height. func (m *Meshgrid) SetFloat64(idx int, value float64) { - log.Println("SetFloat64", idx, value) - m.values[idx] = value - m.zmin, m.zmax, m.zrange = findMinMaxRange(m.values) - zrange := m.zrange - if zrange == 0 { - zrange = 1 + if idx < 0 || idx >= len(m.values) { + return } - m.vertices[idx/m.cols][idx%m.cols].Z = ((value - m.zmin) / zrange) * m.depth - m.refresh() -} - -func (m *Meshgrid) SetFloat642(idx int, value float64) { m.values[idx] = value m.zmin, m.zmax, m.zrange = findMinMaxRange(m.values) m.createVertices(fyne.Max(float32(m.cols), 1), fyne.Max(float32(m.rows), 1)) @@ -252,14 +245,15 @@ func (m *Meshgrid) SetFloat642(idx int, value float64) { // Update LoadFloat64s to use the new vertex position update method func (m *Meshgrid) LoadFloat64s(min, max float64, floats []float64) { + if len(floats) != m.rows*m.cols { + log.Printf("meshgrid: LoadFloat64s got %d values, want %d (%dx%d)", len(floats), m.rows*m.cols, m.cols, m.rows) + return + } m.zmin = min m.zmax = max m.zrange = m.zmax - m.zmin m.values = floats - if len(floats) == 0 { - return - } m.createVertices(fyne.Max(float32(m.cols), 1), fyne.Max(float32(m.rows), 1)) m.updateVertexPositions() @@ -280,14 +274,6 @@ func findMinMaxRange(values []float64) (float64, float64, float64) { return minZ, maxZ, maxZ - minZ } -func (m *Meshgrid) project(v Vertex) (int, int) { - centerX := float64(m.size.Width) * 0.5 - centerY := float64(m.size.Height) * 0.5 - screenX := centerX + v.X - screenY := centerY + v.Y - return int(screenX), int(screenY) -} - func (m *Meshgrid) Refresh() { m.refresh() } @@ -304,24 +290,31 @@ func (m *Meshgrid) throttledRefresh() { } m.refreshPending = true time.AfterFunc(10*time.Millisecond, func() { // ~100fps - m.refresh() - m.refreshPending = false + // AfterFunc fires on a timer goroutine; hop back to the fyne thread. + // refreshPending is then only ever touched on the fyne thread. + fyne.Do(func() { + m.refresh() + m.refreshPending = false + }) }) } + func (m *Meshgrid) CreateRenderer() fyne.WidgetRenderer { - return &meshgridRenderer{m} + return &meshgridRenderer{MG: m} } type meshgridRenderer struct { - *Meshgrid + MG *Meshgrid + objects []fyne.CanvasObject } func (m *meshgridRenderer) Layout(size fyne.Size) { - if size == m.size { + if size == m.MG.size { return } - m.size = size - m.throttledRefresh() + m.MG.size = size + m.MG.image.Resize(size) + m.MG.throttledRefresh() } func (m *meshgridRenderer) MinSize() fyne.Size { @@ -329,12 +322,15 @@ func (m *meshgridRenderer) MinSize() fyne.Size { } func (m *meshgridRenderer) Refresh() { - m.Meshgrid.refresh() + m.MG.refresh() } func (m *meshgridRenderer) Destroy() { } func (m *meshgridRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{m.image} + if m.objects == nil { + m.objects = []fyne.CanvasObject{m.MG.image} + } + return m.objects } From 72ccc102d76c31ba1c07a2dddbdf3e5ebd6056a4 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 11 Jun 2026 22:56:26 +0200 Subject: [PATCH 018/102] update deps --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 48633a61..e208d982 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.7.5-0.20260610120109-657f1cc54c35 + fyne.io/fyne/v2 v2.7.5-0.20260611121725-ccd9d3f45998 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 diff --git a/go.sum b/go.sum index c152d11b..b9f4f322 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -fyne.io/fyne/v2 v2.7.5-0.20260610120109-657f1cc54c35 h1:SRutBcy6SbGtnaKk3VqAYnyoOlwp9JDE6wHZKMZfHiU= -fyne.io/fyne/v2 v2.7.5-0.20260610120109-657f1cc54c35/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= +fyne.io/fyne/v2 v2.7.5-0.20260611121725-ccd9d3f45998 h1:t9rEs4rXZMTSt80TC4saVmfeSxyiUw3eMeu5kBYaF68= +fyne.io/fyne/v2 v2.7.5-0.20260611121725-ccd9d3f45998/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= From 7a96bd1e60f2335b95451ec9f93d63a59e4016c8 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 00:39:54 +0200 Subject: [PATCH 019/102] better meshgrid --- pkg/assets/WHATSNEW.md | 18 +- pkg/widgets/mapviewer/mapviewer.go | 20 +- pkg/widgets/meshgrid/meshgrid_draw.go | 14 +- pkg/widgets/meshgrid/meshgrid_render_test.go | 104 +++++++++++ pkg/widgets/meshgrid/meshgrid_surface.go | 187 +++++++++++++++++++ pkg/widgets/meshgrid/meshgrid_widget.go | 22 +++ 6 files changed, 355 insertions(+), 10 deletions(-) create mode 100644 pkg/widgets/meshgrid/meshgrid_render_test.go create mode 100644 pkg/widgets/meshgrid/meshgrid_surface.go diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index 860fad62..1930bcd6 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -1,12 +1,16 @@ # 2.1.10 - Performance optimization for the log plotter and meshgrid -- force layouts to be loaded and saved in users home directory under the txlogger folder -- update ecusymbol to be able to read T5 versions -- added new config widget for AD scanner WBL settings inspired by T7's DisplAdap.LamScannerTab -- refactored the bus implementation to use less CPU and have less allocations -- added support for BPL files ( binary packed logfile ) -- removed support for creating legacy TXL log files. (you can still load them but might cause crashes) -- removed ebusmonitor, it has served it's purpose +- Force layouts to be loaded and saved in users home directory under the txlogger folder +- Update ecusymbol to be able to read T5 versions +- Added new config widget for AD scanner WBL settings inspired by T7's DisplAdap.LamScannerTab +- Refactored the bus implementation to use less CPU and have less allocations +- Added support for BPL files ( binary packed logfile ) +- Removed support for creating legacy TXL log files. (you can still load them but might cause crashes) +- Removed ebusmonitor, it has served it's purpose +- Improved drag handler in logplayer, when zoomed in we drag fewer frames increasing as we zoom out +- We now have 3 render modes for viewing 3d maps, Solid Wireframe, Solid & Wireframe. Press the little square icon in the mesh viewer to switch between them +- WBL reconnect COM port while logging. If the COM port dies for a reason during logging it will try to re-connect +- Many widget performance improvements to allow slower computers to run txlogger better # 2.1.9 - Updated default T7 preset to include MAF.m_AirFromp_AirInlet diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index a157296a..0b192295 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -14,6 +14,8 @@ import ( "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/driver/desktop" + fynelayout "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/roffe/txlogger/pkg/colors" "github.com/roffe/txlogger/pkg/interpolate" @@ -114,7 +116,9 @@ func (mv *MapViewer) SetColorBlindMode(mode colors.ColorBlindMode) { if mv.colorMode != mode { mv.colorMode = mode mv.Refresh() - mv.mesh.SetColorBlindMode(mode) + if mv.mesh != nil { + mv.mesh.SetColorBlindMode(mode) + } } } @@ -229,6 +233,8 @@ func (mv *MapViewer) render() fyne.CanvasObject { } if err == nil { + meshModeBtn := widget.NewButtonWithIcon("", theme.GridIcon(), mv.mesh.CycleRenderMode) + meshModeBtn.Importance = widget.LowImportance split := container.NewVSplit( mapview, container.NewBorder( @@ -236,7 +242,12 @@ func (mv *MapViewer) render() fyne.CanvasObject { buttons, nil, nil, - mv.mesh, + container.NewStack( + mv.mesh, + container.NewVBox( + container.NewHBox(fynelayout.NewSpacer(), meshModeBtn), + ), + ), ), ) split.Offset = 0.2 @@ -295,6 +306,11 @@ func (mv *MapViewer) SetZData(zData []float64) error { func (mv *MapViewer) Refresh() { mv.zMin, mv.zMax = widgets.FindMinMax(mv.cfg.ZData) + if len(mv.textValues) == 0 { + // renderer not created yet; createTextValues/createZdata pick up + // the current ZData and color mode when it is + return + } for idx, value := range mv.cfg.ZData { mv.setCellText(idx, value) col := colors.GetColorInterpolation( diff --git a/pkg/widgets/meshgrid/meshgrid_draw.go b/pkg/widgets/meshgrid/meshgrid_draw.go index c41ea6c5..64ae4b0c 100644 --- a/pkg/widgets/meshgrid/meshgrid_draw.go +++ b/pkg/widgets/meshgrid/meshgrid_draw.go @@ -81,6 +81,18 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { } } + mode := m.renderMode + if m.rows < 2 || m.cols < 2 { + // A 1D mesh has no cells to fill; lines are all we can draw. + mode = RenderModeWireframe + } + + if mode != RenderModeWireframe { + m.drawSurface(img, projX, projY, vertCol, mode == RenderModeSolidWireframe) + m.drawAxisIndicator(img) + return img + } + // Collect line segments using cached projections. segs := m.scratchLines[:0] for i := 0; i < m.rows; i++ { @@ -105,7 +117,7 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { y1: y1, x2: x2, y2: y2, - depth: -(m.vertices[i][j].Z + m.vertices[ni][nj].Z) * 0.5, + depth: (m.vertices[i][j].Z + m.vertices[ni][nj].Z) * 0.5, diagonal: x1 != x2 && y1 != y2, }) } diff --git a/pkg/widgets/meshgrid/meshgrid_render_test.go b/pkg/widgets/meshgrid/meshgrid_render_test.go new file mode 100644 index 00000000..39948738 --- /dev/null +++ b/pkg/widgets/meshgrid/meshgrid_render_test.go @@ -0,0 +1,104 @@ +package meshgrid + +import ( + "image/png" + "os" + "testing" + + "fyne.io/fyne/v2" + "github.com/roffe/txlogger/pkg/colors" +) + +func testGrid(t testing.TB) *Meshgrid { + t.Helper() + cols, rows := 16, 16 + values := make([]float64, cols*rows) + for i := 0; i < rows; i++ { + for j := 0; j < cols; j++ { + x := float64(j-cols/2) / 3 + y := float64(i-rows/2) / 3 + values[i*cols+j] = 100 / (1 + x*x + y*y) // central hump + } + } + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal) + if err != nil { + t.Fatal(err) + } + m.size = fyne.NewSize(800, 500) + return m +} + +// TestRenderRotated renders an asymmetric surface (tall corner spike) from +// four yaw angles so painter's-order mistakes show up as the spike being +// overdrawn by cells that are behind it. +func TestRenderRotated(t *testing.T) { + if os.Getenv("MESHGRID_DUMP") == "" { + t.Skip("set MESHGRID_DUMP=1 to dump rotation PNGs") + } + cols, rows := 16, 16 + for n, yaw := range []float64{0, 90, 180, 270} { + values := make([]float64, cols*rows) + for i := 0; i < rows; i++ { + for j := 0; j < cols; j++ { + x := float64(j-2) / 1.5 + y := float64(i-2) / 1.5 + values[i*cols+j] = 10 + 100/(1+x*x+y*y) // spike near one corner + } + } + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal) + if err != nil { + t.Fatal(err) + } + m.size = fyne.NewSize(800, 500) + m.rotateMeshgrid(0, yaw, 0) + img := m.drawMeshgridLines() + f, err := os.Create("/tmp/meshgrid_rot_" + string(rune('0'+n)) + ".png") + if err != nil { + t.Fatal(err) + } + if err := png.Encode(f, img); err != nil { + t.Fatal(err) + } + f.Close() + } +} + +func BenchmarkDrawSurface(b *testing.B) { + m := testGrid(b) + m.renderMode = RenderModeSolidWireframe + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + m.drawMeshgridLines() + } +} + +func TestRenderModes(t *testing.T) { + for _, tc := range []struct { + name string + mode RenderMode + }{ + {"solidwire", RenderModeSolidWireframe}, + {"solid", RenderModeSolid}, + {"wireframe", RenderModeWireframe}, + } { + t.Run(tc.name, func(t *testing.T) { + m := testGrid(t) + m.renderMode = tc.mode + img := m.drawMeshgridLines() + if img.Bounds().Dx() != 800 || img.Bounds().Dy() != 500 { + t.Fatalf("unexpected bounds %v", img.Bounds()) + } + if os.Getenv("MESHGRID_DUMP") != "" { + f, err := os.Create("/tmp/meshgrid_" + tc.name + ".png") + if err != nil { + t.Fatal(err) + } + defer f.Close() + if err := png.Encode(f, img); err != nil { + t.Fatal(err) + } + } + }) + } +} diff --git a/pkg/widgets/meshgrid/meshgrid_surface.go b/pkg/widgets/meshgrid/meshgrid_surface.go new file mode 100644 index 00000000..3b722f75 --- /dev/null +++ b/pkg/widgets/meshgrid/meshgrid_surface.go @@ -0,0 +1,187 @@ +package meshgrid + +import ( + "image" + "image/color" + "math" + "slices" +) + +// RenderMode selects how the mesh is drawn. +type RenderMode int + +const ( + // RenderModeSolidWireframe draws the filled surface with grid lines on top. + RenderModeSolidWireframe RenderMode = iota + // RenderModeSolid draws only the filled surface. + RenderModeSolid + // RenderModeWireframe draws the classic line mesh. + RenderModeWireframe + + renderModeCount +) + +// quadRef identifies one grid cell for the painter's-order surface pass. +type quadRef struct { + i, j int + depth float64 +} + +// Grid lines drawn on top of the filled surface are dimmed so they read as a +// grid instead of blending into the identically colored fill. +const surfaceEdgeFade = 0.45 + +// drawSurface fills each grid cell with two Gouraud-shaded triangles, +// back-to-front. A fixed directional light flat-shades each quad so the +// surface relief stays visible even where the value color barely changes. +// When edges is true the cell outline is drawn right after its fill, which +// keeps lines on hidden faces correctly occluded by nearer quads. +func (m *Meshgrid) drawSurface(img *image.RGBA, projX, projY []int, vertCol []color.RGBA, edges bool) { + quads := m.scratchQuads[:0] + for i := 0; i < m.rows-1; i++ { + for j := 0; j < m.cols-1; j++ { + z := m.vertices[i][j].Z + m.vertices[i][j+1].Z + m.vertices[i+1][j].Z + m.vertices[i+1][j+1].Z + quads = append(quads, quadRef{i: i, j: j, depth: z * 0.25}) + } + } + m.scratchQuads = quads + + // Back-to-front: larger view-space Z is nearer the viewer (the depth + // shading brightens large Z), so draw small-Z quads first. + slices.SortFunc(quads, func(a, b quadRef) int { + switch { + case a.depth < b.depth: + return -1 + case a.depth > b.depth: + return 1 + default: + return 0 + } + }) + + // Fixed light direction in view space, normalized once per frame. + lx, ly, lz := 0.3, -0.5, 0.8 + il := 1 / math.Sqrt(lx*lx+ly*ly+lz*lz) + lx, ly, lz = lx*il, ly*il, lz*il + + for _, q := range quads { + ai := q.i*m.cols + q.j // top-left + bi := ai + 1 // top-right + di := ai + m.cols // bottom-left + ci := di + 1 // bottom-right + + shade := m.quadShade(q.i, q.j, lx, ly, lz) + ca := fadeColor(vertCol[ai], shade) + cb := fadeColor(vertCol[bi], shade) + cc := fadeColor(vertCol[ci], shade) + cd := fadeColor(vertCol[di], shade) + + fillTriangle(img, projX[ai], projY[ai], projX[bi], projY[bi], projX[ci], projY[ci], ca, cb, cc) + fillTriangle(img, projX[ai], projY[ai], projX[ci], projY[ci], projX[di], projY[di], ca, cc, cd) + + if edges { + ea := fadeColor(vertCol[ai], surfaceEdgeFade) + eb := fadeColor(vertCol[bi], surfaceEdgeFade) + ec := fadeColor(vertCol[ci], surfaceEdgeFade) + ed := fadeColor(vertCol[di], surfaceEdgeFade) + drawBresenhamLine(img, projX[ai], projY[ai], projX[bi], projY[bi], ea, eb) + drawBresenhamLine(img, projX[bi], projY[bi], projX[ci], projY[ci], eb, ec) + drawBresenhamLine(img, projX[ci], projY[ci], projX[di], projY[di], ec, ed) + drawBresenhamLine(img, projX[di], projY[di], projX[ai], projY[ai], ed, ea) + } + } +} + +// quadShade computes a flat Lambert term for the cell at (i,j) from its +// view-space normal (cross product of the diagonals, which is robust for +// non-planar quads). The absolute dot product is used since the surface is +// single-sided and may be viewed from below. +func (m *Meshgrid) quadShade(i, j int, lx, ly, lz float64) float64 { + a := m.vertices[i][j] + b := m.vertices[i][j+1] + c := m.vertices[i+1][j+1] + d := m.vertices[i+1][j] + + ux, uy, uz := c.X-a.X, c.Y-a.Y, c.Z-a.Z + vx, vy, vz := d.X-b.X, d.Y-b.Y, d.Z-b.Z + + nx := uy*vz - uz*vy + ny := uz*vx - ux*vz + nz := ux*vy - uy*vx + + nl := math.Sqrt(nx*nx + ny*ny + nz*nz) + if nl == 0 { + return 1 + } + dot := (nx*lx + ny*ly + nz*lz) / nl + if dot < 0 { + dot = -dot + } + return 0.6 + 0.4*dot +} + +// fillTriangle rasterizes a triangle with per-vertex (Gouraud) color +// interpolation using incremental integer edge functions, clipped to the +// image bounds via the bounding box. +func fillTriangle(img *image.RGBA, x0, y0, x1, y1, x2, y2 int, c0, c1, c2 color.RGBA) { + area := (x1-x0)*(y2-y0) - (y1-y0)*(x2-x0) + if area == 0 { + return + } + if area < 0 { + x1, y1, x2, y2 = x2, y2, x1, y1 + c1, c2 = c2, c1 + area = -area + } + + r := img.Rect + minX := max(min(x0, min(x1, x2)), r.Min.X) + maxX := min(max(x0, max(x1, x2)), r.Max.X-1) + minY := max(min(y0, min(y1, y2)), r.Min.Y) + maxY := min(max(y0, max(y1, y2)), r.Max.Y-1) + if minX > maxX || minY > maxY { + return + } + + // Edge function values at (minX, minY); stepping +1 in x adds the A term, + // +1 in y adds the B term. + ef := func(ax, ay, bx, by, px, py int) int { + return (bx-ax)*(py-ay) - (by-ay)*(px-ax) + } + w0Row := ef(x1, y1, x2, y2, minX, minY) + w1Row := ef(x2, y2, x0, y0, minX, minY) + w2Row := ef(x0, y0, x1, y1, minX, minY) + a0, b0 := y1-y2, x2-x1 + a1, b1 := y2-y0, x0-x2 + a2, b2 := y0-y1, x1-x0 + + invArea := 1.0 / float64(area) + r0, g0, bl0 := float64(c0.R), float64(c0.G), float64(c0.B) + r1, g1, bl1 := float64(c1.R), float64(c1.G), float64(c1.B) + r2, g2, bl2 := float64(c2.R), float64(c2.G), float64(c2.B) + + stride := img.Stride + pix := img.Pix + for y := minY; y <= maxY; y++ { + w0, w1, w2 := w0Row, w1Row, w2Row + idx := (y-r.Min.Y)*stride + (minX-r.Min.X)*4 + for x := minX; x <= maxX; x++ { + if w0 >= 0 && w1 >= 0 && w2 >= 0 { + fw0 := float64(w0) * invArea + fw1 := float64(w1) * invArea + fw2 := float64(w2) * invArea + pix[idx+0] = uint8(fw0*r0 + fw1*r1 + fw2*r2) + pix[idx+1] = uint8(fw0*g0 + fw1*g1 + fw2*g2) + pix[idx+2] = uint8(fw0*bl0 + fw1*bl1 + fw2*bl2) + pix[idx+3] = 255 + } + w0 += a0 + w1 += a1 + w2 += a2 + idx += 4 + } + w0Row += b0 + w1Row += b1 + w2Row += b2 + } +} diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index 549e409f..27e9bad5 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -40,6 +40,9 @@ type Meshgrid struct { scratchProjY []int scratchColors []color.RGBA scratchLines []lineSegment + scratchQuads []quadRef + + renderMode RenderMode lastMouseX, lastMouseY float32 @@ -121,6 +124,25 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int return m, nil } +// SetRenderMode switches between solid surface, solid+wireframe and pure +// wireframe rendering. +func (m *Meshgrid) SetRenderMode(mode RenderMode) { + if m.renderMode != mode { + m.renderMode = mode + m.refresh() + } +} + +func (m *Meshgrid) RenderMode() RenderMode { + return m.renderMode +} + +// CycleRenderMode steps to the next render mode (surface → solid → wireframe). +func (m *Meshgrid) CycleRenderMode() { + m.renderMode = (m.renderMode + 1) % renderModeCount + m.refresh() +} + func (m *Meshgrid) SetColorBlindMode(mode colors.ColorBlindMode) { if m.colorMode != mode { m.colorMode = mode From ca275e2ebdb514a260003eddaa9a602bb51acd45 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 01:11:12 +0200 Subject: [PATCH 020/102] update plotter --- pkg/widgets/plotter/bresenham.go | 25 +++++ pkg/widgets/plotter/plotter.go | 107 ++++++++++++++++++---- pkg/widgets/plotter/plotter_bench_test.go | 63 +++++++++++++ pkg/widgets/plotter/plotter_render.go | 21 +++-- 4 files changed, 192 insertions(+), 24 deletions(-) create mode 100644 pkg/widgets/plotter/plotter_bench_test.go diff --git a/pkg/widgets/plotter/bresenham.go b/pkg/widgets/plotter/bresenham.go index 3404b207..a237bdbc 100644 --- a/pkg/widgets/plotter/bresenham.go +++ b/pkg/widgets/plotter/bresenham.go @@ -91,6 +91,31 @@ func bresenhamCore(pix []uint8, stride, w, h, x1, y1, x2, y2 int, col color.RGBA } } +// fillVRun draws a vertical run of pixels in column x from y0 to y1 (inclusive, +// in either order), clipped to the image, with the same max-blend as +// bresenhamCore. +func fillVRun(pix []uint8, stride, w, h, x, y0, y1 int, col color.RGBA) { + if uint(x) >= uint(w) { + return + } + if y0 > y1 { + y0, y1 = y1, y0 + } + if y1 < 0 || y0 >= h { + return + } + y0 = max(y0, 0) + y1 = min(y1, h-1) + i := y0*stride + x*4 + for y := y0; y <= y1; y++ { + pix[i+0] = max(pix[i+0], col.R) + pix[i+1] = max(pix[i+1], col.G) + pix[i+2] = max(pix[i+2], col.B) + pix[i+3] = max(pix[i+3], col.A) + i += stride + } +} + func fillCircle(pix []uint8, stride, w, h, centerX, centerY, radius int, col color.RGBA) { rr := radius * radius for y := -radius; y <= radius; y++ { diff --git a/pkg/widgets/plotter/plotter.go b/pkg/widgets/plotter/plotter.go index 975e3a13..a25c92e8 100644 --- a/pkg/widgets/plotter/plotter.go +++ b/pkg/widgets/plotter/plotter.go @@ -6,6 +6,8 @@ import ( "image/color" "log" "sort" + "sync" + "sync/atomic" "unicode" "unicode/utf8" @@ -60,6 +62,12 @@ type Plotter struct { hilightLine int + // seekPos is the latest position requested via Seek, which is called from + // the playback goroutine; the pending UI refresh reads it back out. + seekMu sync.Mutex + seekPos int + refreshPending atomic.Bool + OnDragged func(event *fyne.DragEvent) OnTapped func(event *fyne.PointEvent) } @@ -232,10 +240,38 @@ func lessCaseInsensitive(s, t string) bool { } func (p *Plotter) CreateRenderer() fyne.WidgetRenderer { - return &plotterRenderer{p} + return &plotterRenderer{PL: p} } +// Seek records the new playback position and schedules a redraw on the UI +// goroutine. Calls are coalesced: while a refresh is pending, further Seeks +// only overwrite seekPos and the pending refresh renders the latest position. +// This bounds drawing to the rate the UI goroutine can keep up with instead of +// the log record rate, so a fast log can never back up the event queue. func (p *Plotter) Seek(pos int) { + p.seekMu.Lock() + p.seekPos = pos + p.seekMu.Unlock() + + if !p.refreshPending.CompareAndSwap(false, true) { + return + } + fyne.Do(func() { + // Clear the flag before reading seekPos: a Seek arriving mid-draw then + // queues a new refresh rather than being dropped, so the final + // position is always rendered. + p.refreshPending.Store(false) + p.seekMu.Lock() + pos := p.seekPos + p.seekMu.Unlock() + p.seekTo(pos) + }) +} + +// seekTo applies a seek on the UI goroutine: it recomputes the view window, +// updates the legend values, redraws the plot and refreshes the changed +// canvas objects. +func (p *Plotter) seekTo(pos int) { halfDataPointsToShow := int(float64(p.dataPointsToShow) * .5) offsetPosition := pos - halfDataPointsToShow if pos <= p.dataLength-halfDataPointsToShow { @@ -246,10 +282,7 @@ func (p *Plotter) Seek(pos int) { } p.cursorPos = pos - // Update legend values, collecting the ones that actually changed so we - // can refresh them together in a single fyne.Do below. valueIndex := min(p.dataLength, p.cursorPos) - var changed []*TappableText for i, v := range p.valueOrder { obj := p.legendTexts[i] newValue := fmt.Sprintf("%.4g", p.values[v][valueIndex]) @@ -258,21 +291,13 @@ func (p *Plotter) Seek(pos int) { continue } obj.value.Text = newValue - changed = append(changed, obj) + obj.Refresh() } p.drawImage() p.layoutCursor() - - // Collapse all refreshes for this frame into one dispatch onto the main - // goroutine instead of one per changed legend item plus cursor plus image. - fyne.Do(func() { - for _, obj := range changed { - obj.Refresh() - } - p.cursor.Refresh() - p.canvasImage.Refresh() - }) + p.cursor.Refresh() + p.canvasImage.Refresh() } // drawImage renders the enabled time series into the (reused) plot buffer and @@ -395,10 +420,16 @@ func (ts *TimeSeries) PlotImage(img *image.RGBA, values map[string][]float64, st heightFactor := float64(hh) / ts.valueRange widthFactor := float64(w) / float64(dataLen) - // start at 1 since we need to draw a line from the previous point data := values[ts.Name][startN:endN] + + if dataLen > w { + ts.plotImageDecimated(img, data, thickness) + return + } + dle := dataLen - 1 + // start at 1 since we need to draw a line from the previous point for x := 1; x < dataLen; x++ { fx := float64(x) x0 := int(((fx - 1) * widthFactor)) @@ -412,6 +443,50 @@ func (ts *TimeSeries) PlotImage(img *image.RGBA, values map[string][]float64, st } } +// plotImageDecimated renders the series when there are more visible points +// than pixel columns. Connecting consecutive points with Bresenham lines would +// overdraw each column once per point landing on it; instead each column gets +// a single vertical run spanning the min/max of its points, capping the work +// at O(width) regardless of how far out the view is zoomed. +func (ts *TimeSeries) plotImageDecimated(img *image.RGBA, data []float64, thickness int) { + pix := img.Pix + stride := img.Stride + s := img.Bounds().Size() + w := s.X + h := s.Y + hh := h - 1 + heightFactor := float64(hh) / ts.valueRange + dataLen := len(data) + + halfThick := 0 + if thickness > 1 { + halfThick = thickness / 2 + } + + lo := 0 + for x := 0; x < w; x++ { + hi := (x + 1) * dataLen / w + // Re-scan the previous column's last point so adjacent runs always + // overlap vertically and the plot stays gap-free. + scanFrom := max(lo-1, 0) + minV, maxV := data[scanFrom], data[scanFrom] + for _, v := range data[scanFrom+1 : hi] { + if v < minV { + minV = v + } + if v > maxV { + maxV = v + } + } + y0 := int(float64(hh) - (maxV-ts.Min)*heightFactor) + y1 := int(float64(hh) - (minV-ts.Min)*heightFactor) + for t := -halfThick; t <= halfThick; t++ { + fillVRun(pix, stride, w, h, x+t, y0-halfThick, y1+halfThick, ts.Color) + } + lo = hi + } +} + // layoutCursor recomputes the cursor line position for the current view. It // does not trigger a Refresh. func (p *Plotter) layoutCursor() { diff --git a/pkg/widgets/plotter/plotter_bench_test.go b/pkg/widgets/plotter/plotter_bench_test.go new file mode 100644 index 00000000..9c974aa3 --- /dev/null +++ b/pkg/widgets/plotter/plotter_bench_test.go @@ -0,0 +1,63 @@ +package plotter + +import ( + "image" + "image/color" + "math/rand" + "testing" +) + +func benchValues(numSeries, numPoints int) map[string][]float64 { + r := rand.New(rand.NewSource(1)) + values := make(map[string][]float64, numSeries) + for i := 0; i < numSeries; i++ { + name := string(rune('A'+i)) + "series" + data := make([]float64, numPoints) + v := 0.0 + for j := range data { + v += r.Float64()*2 - 1 + data[j] = v + } + values[name] = data + } + return values +} + +func benchPlot(b *testing.B, w, h, numSeries, pointsShown int) { + values := benchValues(numSeries, pointsShown+10) + img := image.NewRGBA(image.Rect(0, 0, w, h)) + series := make([]*TimeSeries, 0, numSeries) + for name := range values { + series = append(series, NewTimeSeries(name, values)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + clear(img.Pix) + for _, ts := range series { + ts.PlotImage(img, values, 0, pointsShown, 1) + } + } +} + +// Default zoom: 250 points visible +func BenchmarkPlot_1080p_10series_250pts(b *testing.B) { benchPlot(b, 1920, 1080, 10, 250) } +func BenchmarkPlot_1080p_30series_250pts(b *testing.B) { benchPlot(b, 1920, 1080, 30, 250) } + +// Zoomed out: 10000 points visible (zoom slider at max = 25*400) +func BenchmarkPlot_1080p_10series_10kpts(b *testing.B) { benchPlot(b, 1920, 1080, 10, 10000) } +func BenchmarkPlot_1080p_30series_10kpts(b *testing.B) { benchPlot(b, 1920, 1080, 30, 10000) } + +func BenchmarkClearOnly_1080p(b *testing.B) { + img := image.NewRGBA(image.Rect(0, 0, 1920, 1080)) + for i := 0; i < b.N; i++ { + clear(img.Pix) + } +} + +func BenchmarkBresenhamSingleLine(b *testing.B) { + img := image.NewRGBA(image.Rect(0, 0, 1920, 1080)) + col := color.RGBA{255, 0, 0, 255} + for i := 0; i < b.N; i++ { + BresenhamThick(img, 0, 0, 1919, 1079, 1, col) + } +} diff --git a/pkg/widgets/plotter/plotter_render.go b/pkg/widgets/plotter/plotter_render.go index d70a70e5..a228b12e 100644 --- a/pkg/widgets/plotter/plotter_render.go +++ b/pkg/widgets/plotter/plotter_render.go @@ -5,20 +5,21 @@ import ( ) type plotterRenderer struct { - *Plotter + PL *Plotter + objects []fyne.CanvasObject } func (p *plotterRenderer) MinSize() fyne.Size { - return p.split.MinSize() + return p.PL.split.MinSize() } func (p *plotterRenderer) Layout(size fyne.Size) { - if p.size == size { + if p.PL.size == size { return } - p.size = size + p.PL.size = size - p.split.Resize(size) + p.PL.split.Resize(size) } func (p *plotterRenderer) Refresh() { @@ -28,10 +29,14 @@ func (p *plotterRenderer) Destroy() { } func (p *plotterRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{p.split, - p.overlayText, - p.cursor, + if p.objects == nil { + p.objects = []fyne.CanvasObject{ + p.PL.split, + p.PL.overlayText, + p.PL.cursor, + } } + return p.objects } type plotLayout struct { From d68d95514441348af1ff92bddb2722bece83f76e Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 01:11:52 +0200 Subject: [PATCH 021/102] cache objects --- pkg/widgets/grid/grid.go | 33 +++++++++++++++------------- pkg/widgets/icon/icon.go | 1 - pkg/widgets/ledicon/ledicon.go | 9 +++++--- pkg/widgets/logplayer/logplayer.go | 8 +++++-- pkg/widgets/settings/wbleditor.go | 33 ++++++++++++++++------------ pkg/widgets/symbollist/symbollist.go | 10 ++++++--- 6 files changed, 56 insertions(+), 38 deletions(-) diff --git a/pkg/widgets/grid/grid.go b/pkg/widgets/grid/grid.go index 674ddf4c..55126139 100644 --- a/pkg/widgets/grid/grid.go +++ b/pkg/widgets/grid/grid.go @@ -35,11 +35,12 @@ func New(cols, rows int) *Grid { } func (g *Grid) CreateRenderer() fyne.WidgetRenderer { - return &gridRenderer{g} + return &gridRenderer{G: g} } type gridRenderer struct { - *Grid + G *Grid + objects []fyne.CanvasObject } func (g *gridRenderer) MinSize() fyne.Size { @@ -47,17 +48,17 @@ func (g *gridRenderer) MinSize() fyne.Size { } func (g *gridRenderer) Layout(size fyne.Size) { - if size == g.lastSize { + if size == g.G.lastSize { return } - g.lastSize = size + g.G.lastSize = size - cellWidth := size.Width / float32(g.cols) - cellHeight := size.Height / float32(g.rows) + cellWidth := size.Width / float32(g.G.cols) + cellHeight := size.Height / float32(g.G.rows) // update vertical lines - for i := 0; i < g.cols; i++ { - l := g.lines[i] + for i := 0; i < g.G.cols; i++ { + l := g.G.lines[i] x := float32(i) * cellWidth l.Position1 = fyne.NewPos(x, 0) l.Position2 = fyne.NewPos(x, size.Height) @@ -65,9 +66,9 @@ func (g *gridRenderer) Layout(size fyne.Size) { } // update horizontal lines - offset := g.cols - for i := 0; i < g.rows; i++ { - l := g.lines[offset+i] + offset := g.G.cols + for i := 0; i < g.G.rows; i++ { + l := g.G.lines[offset+i] y := float32(i) * cellHeight l.Position1 = fyne.NewPos(0, y) l.Position2 = fyne.NewPos(size.Width, y) @@ -82,9 +83,11 @@ func (g *gridRenderer) Destroy() { } func (g *gridRenderer) Objects() []fyne.CanvasObject { - objs := make([]fyne.CanvasObject, len(g.lines)) - for i, l := range g.lines { - objs[i] = l + if g.objects == nil { + g.objects = make([]fyne.CanvasObject, len(g.G.lines)) + for i, l := range g.G.lines { + g.objects[i] = l + } } - return objs + return g.objects } diff --git a/pkg/widgets/icon/icon.go b/pkg/widgets/icon/icon.go index 0debccb7..81704f88 100644 --- a/pkg/widgets/icon/icon.go +++ b/pkg/widgets/icon/icon.go @@ -76,5 +76,4 @@ func (ic *IconRenderer) Objects() []fyne.CanvasObject { ic.objects = []fyne.CanvasObject{ic.IC.cfg.Image, ic.IC.text} } return ic.objects - // return []fyne.CanvasObject{ic.IC.cfg.Image, ic.IC.text} --- IGNORE --- } diff --git a/pkg/widgets/ledicon/ledicon.go b/pkg/widgets/ledicon/ledicon.go index f6e5cea6..f3de64a9 100644 --- a/pkg/widgets/ledicon/ledicon.go +++ b/pkg/widgets/ledicon/ledicon.go @@ -53,13 +53,13 @@ func (w *Widget) SetState(state bool) { func (w *Widget) CreateRenderer() fyne.WidgetRenderer { return &iconRenderer{w: w} - } var _ fyne.WidgetRenderer = (*iconRenderer)(nil) type iconRenderer struct { - w *Widget + w *Widget + objects []fyne.CanvasObject } func (r *iconRenderer) MinSize() fyne.Size { @@ -82,5 +82,8 @@ func (r *iconRenderer) Destroy() { } func (r *iconRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{r.w.ledicon, r.w.label} + if r.objects == nil { + r.objects = []fyne.CanvasObject{r.w.ledicon, r.w.label} + } + return r.objects } diff --git a/pkg/widgets/logplayer/logplayer.go b/pkg/widgets/logplayer/logplayer.go index da7f558a..9dfc717e 100644 --- a/pkg/widgets/logplayer/logplayer.go +++ b/pkg/widgets/logplayer/logplayer.go @@ -300,7 +300,8 @@ func (l *Logplayer) CreateRenderer() fyne.WidgetRenderer { } type LogplayerRenderer struct { - l *Logplayer + l *Logplayer + objects []fyne.CanvasObject } func (lr *LogplayerRenderer) Layout(space fyne.Size) { @@ -315,7 +316,10 @@ func (lr *LogplayerRenderer) Refresh() { } func (lr *LogplayerRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{lr.l.container} + if lr.objects == nil { + lr.objects = []fyne.CanvasObject{lr.l.container} + } + return lr.objects } func (lr *LogplayerRenderer) Destroy() { diff --git a/pkg/widgets/settings/wbleditor.go b/pkg/widgets/settings/wbleditor.go index 43672ccf..78a25a8d 100644 --- a/pkg/widgets/settings/wbleditor.go +++ b/pkg/widgets/settings/wbleditor.go @@ -515,6 +515,8 @@ type graphRenderer struct { x0, y0, x1, y1 float32 minYv, maxYv int minZv, maxZv float64 + + objects []fyne.CanvasObject } func (r *graphRenderer) rebuild() { @@ -667,21 +669,24 @@ func (r *graphRenderer) Refresh() { } func (r *graphRenderer) Objects() []fyne.CanvasObject { - objs := make([]fyne.CanvasObject, 0, 1+len(r.gridLines)+len(r.axes)+len(r.dataLines)+len(r.points)) - objs = append(objs, r.bg) - for _, l := range r.gridLines { - objs = append(objs, l) - } - for _, l := range r.axes { - objs = append(objs, l) - } - for _, l := range r.dataLines { - objs = append(objs, l) - } - for _, p := range r.points { - objs = append(objs, p) + if r.objects == nil { + + r.objects = make([]fyne.CanvasObject, 0, 1+len(r.gridLines)+len(r.axes)+len(r.dataLines)+len(r.points)) + r.objects = append(r.objects, r.bg) + for _, l := range r.gridLines { + r.objects = append(r.objects, l) + } + for _, l := range r.axes { + r.objects = append(r.objects, l) + } + for _, l := range r.dataLines { + r.objects = append(r.objects, l) + } + for _, p := range r.points { + r.objects = append(r.objects, p) + } } - return objs + return r.objects } func (r *graphRenderer) MinSize() fyne.Size { diff --git a/pkg/widgets/symbollist/symbollist.go b/pkg/widgets/symbollist/symbollist.go index e524328a..a29b849d 100644 --- a/pkg/widgets/symbollist/symbollist.go +++ b/pkg/widgets/symbollist/symbollist.go @@ -386,12 +386,13 @@ func (sw *SymbolWidgetEntry) SetCorrectionFactor(f float64) { */ func (sw *SymbolWidgetEntry) CreateRenderer() fyne.WidgetRenderer { - return &symbolWidgetEntryRenderer{sw} + return &symbolWidgetEntryRenderer{e: sw} // return widget.NewSimpleRenderer(sw.container) } type symbolWidgetEntryRenderer struct { - e *SymbolWidgetEntry + e *SymbolWidgetEntry + objects []fyne.CanvasObject } func (s *symbolWidgetEntryRenderer) Destroy() { @@ -420,5 +421,8 @@ func (s *symbolWidgetEntryRenderer) Refresh() { } func (s *symbolWidgetEntryRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{s.e.container} + if s.objects == nil { + s.objects = []fyne.CanvasObject{s.e.container} + } + return s.objects } From b63e81737166a0f5ee642e7e5e1ae239aa51812c Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 01:23:04 +0200 Subject: [PATCH 022/102] save --- .github/workflows/windows-release.yml | 2 +- .github/workflows/windows.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index 6036d745..d6f9eb8c 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -24,7 +24,7 @@ jobs: submodules: recursive - name: Install NSIS - uses: repolevedavaj/install-nsis@v1.2.0 + uses: repolevedavaj/install-nsis@v1.2.1 with: nsis-version: '3.11' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 27afe2fc..6bb72a7d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,7 +27,7 @@ jobs: submodules: recursive - name: Install NSIS - uses: repolevedavaj/install-nsis@v1.2.0 + uses: repolevedavaj/install-nsis@v1.2.1 with: nsis-version: '3.11' From a20073e8e01c35056360eb00afec9018c259cac5 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 16:56:33 +0200 Subject: [PATCH 023/102] 2d graph --- pkg/widgets/graph2d/graph2d.go | 578 +++++++++++++++++++++ pkg/widgets/graph2d/graph2d_render_test.go | 101 ++++ pkg/widgets/mapviewer/mapviewer.go | 74 ++- pkg/widgets/meshgrid/meshgrid_mouse.go | 5 +- pkg/widgets/meshgrid/meshgrid_widget.go | 22 +- 5 files changed, 754 insertions(+), 26 deletions(-) create mode 100644 pkg/widgets/graph2d/graph2d.go create mode 100644 pkg/widgets/graph2d/graph2d_render_test.go diff --git a/pkg/widgets/graph2d/graph2d.go b/pkg/widgets/graph2d/graph2d.go new file mode 100644 index 00000000..008a8975 --- /dev/null +++ b/pkg/widgets/graph2d/graph2d.go @@ -0,0 +1,578 @@ +// Package graph2d provides a T7Suite style 2D graph for one dimensional +// maps. The map values are plotted as a line with one marker per cell, +// value callouts above the markers and the axis values along the bottom. +package graph2d + +import ( + "image/color" + "math" + "strconv" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/colors" +) + +const ( + tickTextSize = 12 + calloutTextSize = 11 + + markerRadius = 4 + + padRight = 12 + axisGapX = 6 // gap between the y tick labels and the plot area + + calloutPadX = 4 + calloutPadY = 2 + + // pool size for y gridlines/labels/bands, must fit maxYTicks plus the + // extra ticks added when the range is extended to nice boundaries + gridPool = 16 + + maxYTicks = 8 +) + +var ( + plotBgColor = color.RGBA{0xFF, 0xFF, 0xFF, 0xFF} + bandColor = color.RGBA{0xEF, 0xED, 0xD8, 0xFF} + gridLineColor = color.RGBA{0xC9, 0xC9, 0xC9, 0xFF} + plotLineColor = color.RGBA{0xD8, 0x40, 0x28, 0xFF} + markerStrokeCol = color.RGBA{0x8A, 0x22, 0x12, 0xFF} + calloutBorderCol = color.RGBA{0x99, 0x99, 0x99, 0xFF} + calloutTextColor = color.RGBA{0x00, 0x00, 0x00, 0xFF} + // same color as the mapviewer crosshair so the live cursor is + // recognizable; NRGBA since the value is not alpha-premultiplied + cursorColor = color.NRGBA{165, 55, 253, 180} +) + +var ( + _ fyne.Widget = (*Graph)(nil) + _ desktop.Mouseable = (*Graph)(nil) +) + +type Config struct { + AxisData []float64 // axis value per cell, shown along the x axis + Values []float64 + AxisPrecision int + ValuePrecision int + AxisLabel string // optional axis description shown below the x axis + ColorblindMode colors.ColorBlindMode +} + +type Graph struct { + widget.BaseWidget + + cfg *Config + + axis []float64 + values []float64 + + zMin, zMax float64 + + colorMode colors.ColorBlindMode + + cursorIdx float64 + showCursor bool + + OnMouseDown func() + + renderer *graphRenderer +} + +func New(cfg *Config) *Graph { + g := &Graph{ + cfg: cfg, + axis: cfg.AxisData, + values: cfg.Values, + colorMode: cfg.ColorblindMode, + } + if len(g.axis) != len(g.values) { + g.axis = make([]float64, len(g.values)) + for i := range g.axis { + g.axis[i] = float64(i) + } + } + g.zMin, g.zMax = findMinMax(g.values) + g.ExtendBaseWidget(g) + return g +} + +// SetValues updates the plotted values. The number of values must match the +// map dimensions the graph was created with. +func (g *Graph) SetValues(min, max float64, values []float64) { + if len(values) != len(g.values) { + return + } + g.values = values + g.zMin = min + g.zMax = max + g.Refresh() +} + +func (g *Graph) SetColorBlindMode(mode colors.ColorBlindMode) { + if g.colorMode != mode { + g.colorMode = mode + g.Refresh() + } +} + +// SetCursor positions the live cursor at the (fractional) cell index, +// mirroring the crosshair in the map above. +func (g *Graph) SetCursor(idx float64) { + if idx < 0 { + idx = 0 + } else if max := float64(len(g.values) - 1); idx > max { + idx = max + } + g.cursorIdx = idx + g.showCursor = true + if g.renderer != nil { + g.renderer.positionCursor() + g.renderer.cursor.Refresh() + } +} + +func (g *Graph) MouseDown(_ *desktop.MouseEvent) { + if g.OnMouseDown != nil { + g.OnMouseDown() + } +} + +func (g *Graph) MouseUp(_ *desktop.MouseEvent) {} + +func (g *Graph) CreateRenderer() fyne.WidgetRenderer { + n := len(g.values) + r := &graphRenderer{g: g} + + r.plotBg = &canvas.Rectangle{FillColor: plotBgColor} + + for i := 0; i < gridPool; i++ { + band := &canvas.Rectangle{FillColor: bandColor} + band.Hide() + r.bands = append(r.bands, band) + + line := &canvas.Line{StrokeColor: gridLineColor, StrokeWidth: 1} + line.Hide() + r.gridLines = append(r.gridLines, line) + + label := &canvas.Text{TextSize: tickTextSize} + label.Hide() + r.yLabels = append(r.yLabels, label) + } + + for i := 0; i < n-1; i++ { + r.segments = append(r.segments, &canvas.Line{StrokeColor: plotLineColor, StrokeWidth: 2}) + } + + r.cursor = &canvas.Line{StrokeColor: cursorColor, StrokeWidth: 3} + r.cursor.Hide() + + for i := 0; i < n; i++ { + r.markers = append(r.markers, &canvas.Circle{StrokeColor: markerStrokeCol, StrokeWidth: 1.5}) + r.xLabels = append(r.xLabels, &canvas.Text{TextSize: tickTextSize}) + r.calloutBoxes = append(r.calloutBoxes, &canvas.Rectangle{ + FillColor: plotBgColor, + StrokeColor: calloutBorderCol, + StrokeWidth: 1, + CornerRadius: 2, + }) + r.calloutTexts = append(r.calloutTexts, &canvas.Text{ + TextSize: calloutTextSize, + Color: calloutTextColor, + Alignment: fyne.TextAlignCenter, + }) + } + + if g.cfg.AxisLabel != "" { + r.axisLabel = &canvas.Text{Text: g.cfg.AxisLabel, TextSize: tickTextSize} + } + + // z-order: background, bands, gridlines, cursor, line, markers, + // callouts, labels + r.objects = append(r.objects, r.plotBg) + for _, o := range r.bands { + r.objects = append(r.objects, o) + } + for _, o := range r.gridLines { + r.objects = append(r.objects, o) + } + r.objects = append(r.objects, r.cursor) + for _, o := range r.segments { + r.objects = append(r.objects, o) + } + for _, o := range r.markers { + r.objects = append(r.objects, o) + } + for i := 0; i < n; i++ { + r.objects = append(r.objects, r.calloutBoxes[i], r.calloutTexts[i]) + } + for _, o := range r.yLabels { + r.objects = append(r.objects, o) + } + for _, o := range r.xLabels { + r.objects = append(r.objects, o) + } + if r.axisLabel != nil { + r.objects = append(r.objects, r.axisLabel) + } + + g.renderer = r + return r +} + +var _ fyne.WidgetRenderer = (*graphRenderer)(nil) + +type graphRenderer struct { + g *Graph + + plotBg *canvas.Rectangle + + bands []*canvas.Rectangle + gridLines []*canvas.Line + yLabels []*canvas.Text + + segments []*canvas.Line + markers []*canvas.Circle + + calloutBoxes []*canvas.Rectangle + calloutTexts []*canvas.Text + + xLabels []*canvas.Text + axisLabel *canvas.Text + + cursor *canvas.Line + + objects []fyne.CanvasObject + + size fyne.Size + + // plot geometry, kept so the live cursor can be moved without a relayout + plotTop, plotBottom float32 + plotLeft float32 + xStep float32 +} + +func (r *graphRenderer) Layout(size fyne.Size) { + if size == r.size { + return + } + r.size = size + r.relayout() + // the tick labels can change content with the available size so a plain + // reposition is not enough + r.refreshObjects() +} + +func (r *graphRenderer) MinSize() fyne.Size { + return fyne.NewSize(200, 250) +} + +func (r *graphRenderer) Refresh() { + r.relayout() + r.refreshObjects() +} + +func (r *graphRenderer) Destroy() {} + +func (r *graphRenderer) Objects() []fyne.CanvasObject { + return r.objects +} + +func (r *graphRenderer) refreshObjects() { + for _, o := range r.objects { + if !o.Visible() { + continue + } + o.Refresh() + } +} + +func (r *graphRenderer) relayout() { + g := r.g + n := len(g.values) + size := r.size + if n == 0 || size.Width <= 0 || size.Height <= 0 { + return + } + + style := fyne.TextStyle{} + + // measure the value callouts so headroom can be reserved above the plot + calloutWidths := make([]float32, n) + var maxCalloutW, calloutH float32 + for i, v := range g.values { + text := strconv.FormatFloat(v, 'f', g.cfg.ValuePrecision, 64) + r.calloutTexts[i].Text = text + s := fyne.MeasureText(text, calloutTextSize, style) + calloutWidths[i] = s.Width + 2*calloutPadX + if calloutWidths[i] > maxCalloutW { + maxCalloutW = calloutWidths[i] + } + if h := s.Height + 2*calloutPadY; h > calloutH { + calloutH = h + } + } + + padTop := 2*(calloutH+2) + markerRadius + 4 + + xLabelH := fyne.MeasureText("0", tickTextSize, style).Height + padBottom := xLabelH + 8 + if r.axisLabel != nil { + padBottom += xLabelH + 2 + } + + plotH := size.Height - padTop - padBottom + if plotH < 24 { + plotH = 24 + } + plotTop := padTop + plotBottom := plotTop + plotH + + // y scale with "nice" tick steps, extended to tick boundaries + maxTicks := int(plotH / 36) + if maxTicks < 2 { + maxTicks = 2 + } else if maxTicks > maxYTicks { + maxTicks = maxYTicks + } + rng := g.zMax - g.zMin + if rng <= 0 { + rng = math.Abs(g.zMax) + if rng == 0 { + rng = 1 + } + } + step := niceStep(rng, maxTicks) + yStart := math.Floor(g.zMin/step) * step + yEnd := math.Ceil(g.zMax/step) * step + if yEnd-yStart < step { + yEnd = yStart + step + } + ticks := int(math.Round((yEnd-yStart)/step)) + 1 + if ticks > len(r.gridLines) { + ticks = len(r.gridLines) + } + + decimals := 0 + if e := math.Floor(math.Log10(step)); e < 0 { + decimals = int(-e) + } + + tickTexts := make([]string, ticks) + var maxYLabelW float32 + for i := range tickTexts { + tickTexts[i] = strconv.FormatFloat(yStart+float64(i)*step, 'f', decimals, 64) + if s := fyne.MeasureText(tickTexts[i], tickTextSize, style); s.Width > maxYLabelW { + maxYLabelW = s.Width + } + } + padLeft := maxYLabelW + axisGapX + 4 + plotW := size.Width - padLeft - padRight + if plotW < 10 { + plotW = 10 + } + + scale := plotH / float32(yEnd-yStart) + yFor := func(v float64) float32 { + return plotBottom - float32(v-yStart)*scale + } + + labelColor := theme.Color(theme.ColorNameForeground) + + r.plotBg.Move(fyne.NewPos(padLeft, plotTop)) + r.plotBg.Resize(fyne.NewSize(plotW, plotH)) + + for i := 0; i < len(r.gridLines); i++ { + if i >= ticks { + r.gridLines[i].Hide() + r.yLabels[i].Hide() + r.bands[i].Hide() + continue + } + y := yFor(yStart + float64(i)*step) + + line := r.gridLines[i] + line.Position1 = fyne.NewPos(padLeft, y) + line.Position2 = fyne.NewPos(padLeft+plotW, y) + line.Show() + + label := r.yLabels[i] + label.Text = tickTexts[i] + label.Color = labelColor + s := fyne.MeasureText(label.Text, tickTextSize, style) + label.Resize(s) + label.Move(fyne.NewPos(padLeft-axisGapX-s.Width, y-s.Height/2)) + label.Show() + + // alternating band between this gridline and the one above it + band := r.bands[i] + if i+1 < ticks && i%2 == 0 { + yAbove := yFor(yStart + float64(i+1)*step) + band.Move(fyne.NewPos(padLeft, yAbove)) + band.Resize(fyne.NewSize(plotW, y-yAbove)) + band.Show() + } else { + band.Hide() + } + } + + xStep := plotW / float32(n) + cx := func(i int) float32 { + return padLeft + (float32(i)+0.5)*xStep + } + + cys := make([]float32, n) + for i, v := range g.values { + cys[i] = yFor(v) + } + + for i := 0; i < n; i++ { + marker := r.markers[i] + marker.FillColor = colors.GetColorInterpolation(g.zMin, g.zMax, g.values[i], g.colorMode) + marker.Position1 = fyne.NewPos(cx(i)-markerRadius, cys[i]-markerRadius) + marker.Position2 = fyne.NewPos(cx(i)+markerRadius, cys[i]+markerRadius) + } + + for i := 0; i < n-1; i++ { + segment := r.segments[i] + segment.Position1 = fyne.NewPos(cx(i), cys[i]) + segment.Position2 = fyne.NewPos(cx(i+1), cys[i+1]) + } + + // stagger the callouts on two levels when they would overlap and skip + // some of them when even that is not enough + levels := 1 + if maxCalloutW+4 > xStep { + levels = 2 + } + skip := 1 + if needed := maxCalloutW + 4; needed > xStep*float32(levels) { + skip = int(math.Ceil(float64(needed / (xStep * float32(levels))))) + } + shown := 0 + for i := 0; i < n; i++ { + box, text := r.calloutBoxes[i], r.calloutTexts[i] + if i%skip != 0 { + box.Hide() + text.Hide() + continue + } + level := 0 + if levels == 2 { + level = shown % 2 + } + shown++ + + w := calloutWidths[i] + bottom := cys[i] - markerRadius - 3 - float32(level)*(calloutH+2) + x := cx(i) - w/2 + if x < padLeft+1 { + x = padLeft + 1 + } + if x+w > padLeft+plotW-1 { + x = padLeft + plotW - 1 - w + } + box.Move(fyne.NewPos(x, bottom-calloutH)) + box.Resize(fyne.NewSize(w, calloutH)) + box.Show() + text.Resize(fyne.NewSize(w, calloutH-2*calloutPadY)) + text.Move(fyne.NewPos(x, bottom-calloutH+calloutPadY)) + text.Show() + } + + xLabelTexts := make([]string, n) + var maxXLabelW float32 + for i, v := range g.axis { + xLabelTexts[i] = strconv.FormatFloat(v, 'f', g.cfg.AxisPrecision, 64) + if s := fyne.MeasureText(xLabelTexts[i], tickTextSize, style); s.Width > maxXLabelW { + maxXLabelW = s.Width + } + } + labelSkip := 1 + if maxXLabelW+6 > xStep { + labelSkip = int(math.Ceil(float64((maxXLabelW + 6) / xStep))) + } + for i := 0; i < n; i++ { + label := r.xLabels[i] + if i%labelSkip != 0 { + label.Hide() + continue + } + label.Text = xLabelTexts[i] + label.Color = labelColor + s := fyne.MeasureText(label.Text, tickTextSize, style) + label.Resize(s) + x := cx(i) - s.Width/2 + if x < 0 { + x = 0 + } else if x+s.Width > size.Width { + x = size.Width - s.Width + } + label.Move(fyne.NewPos(x, plotBottom+4)) + label.Show() + } + + if r.axisLabel != nil { + r.axisLabel.Color = labelColor + s := fyne.MeasureText(r.axisLabel.Text, tickTextSize, style) + r.axisLabel.Resize(s) + r.axisLabel.Move(fyne.NewPos(padLeft+(plotW-s.Width)/2, plotBottom+4+xLabelH+2)) + } + + r.plotTop = plotTop + r.plotBottom = plotBottom + r.plotLeft = padLeft + r.xStep = xStep + r.positionCursor() +} + +func (r *graphRenderer) positionCursor() { + if !r.g.showCursor { + return + } + x := r.plotLeft + (float32(r.g.cursorIdx)+0.5)*r.xStep + r.cursor.Position1 = fyne.NewPos(x, r.plotTop) + r.cursor.Position2 = fyne.NewPos(x, r.plotBottom) + if r.cursor.Hidden { + r.cursor.Show() + } +} + +// niceStep returns a 1/2/5*10^n step so that the range is covered by at most +// maxTicks intervals. +func niceStep(rng float64, maxTicks int) float64 { + if maxTicks < 1 { + maxTicks = 1 + } + raw := rng / float64(maxTicks) + mag := math.Pow(10, math.Floor(math.Log10(raw))) + switch norm := raw / mag; { + case norm <= 1: + return mag + case norm <= 2: + return 2 * mag + case norm <= 5: + return 5 * mag + default: + return 10 * mag + } +} + +func findMinMax(values []float64) (float64, float64) { + if len(values) == 0 { + return 0, 0 + } + min, max := values[0], values[0] + for _, v := range values { + if v < min { + min = v + } + if v > max { + max = v + } + } + return min, max +} diff --git a/pkg/widgets/graph2d/graph2d_render_test.go b/pkg/widgets/graph2d/graph2d_render_test.go new file mode 100644 index 00000000..d207ae66 --- /dev/null +++ b/pkg/widgets/graph2d/graph2d_render_test.go @@ -0,0 +1,101 @@ +package graph2d + +import ( + "image/png" + "os" + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/test" + "github.com/roffe/txlogger/pkg/colors" +) + +// battery correction table from the T7Suite screenshot the widget mimics +func testGraph(t testing.TB) *Graph { + t.Helper() + return New(&Config{ + AxisData: []float64{5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + Values: []float64{4590, 3605, 2785, 2145, 1755, 1495, 1295, 1145, 1025, 925, 845, 765}, + AxisPrecision: 0, + ValuePrecision: 0, + AxisLabel: "Battery voltage", + ColorblindMode: colors.ModeNormal, + }) +} + +func TestRender(t *testing.T) { + test.NewApp() + g := testGraph(t) + w := test.NewWindow(g) + defer w.Close() + w.Resize(fyne.NewSize(640, 420)) + + g.SetCursor(3.5) + g.SetValues(700, 4700, []float64{4700, 3605, 2785, 2145, 1755, 1495, 1295, 1145, 1025, 925, 845, 700}) + + img := w.Canvas().Capture() + if img.Bounds().Dx() == 0 || img.Bounds().Dy() == 0 { + t.Fatal("captured empty image") + } + + if os.Getenv("GRAPH2D_DUMP") != "" { + f, err := os.Create("/tmp/graph2d.png") + if err != nil { + t.Fatal(err) + } + defer f.Close() + if err := png.Encode(f, img); err != nil { + t.Fatal(err) + } + } +} + +// many cells in a small window exercise the callout stagger/skip and the +// x label thinning +func TestRenderCrowded(t *testing.T) { + test.NewApp() + values := make([]float64, 32) + for i := range values { + values[i] = 1000 + 500*float64(i%5) + } + g := New(&Config{ + Values: values, + ValuePrecision: 0, + ColorblindMode: colors.ModeNormal, + }) + w := test.NewWindow(g) + defer w.Close() + w.Resize(fyne.NewSize(400, 300)) + + img := w.Canvas().Capture() + if img.Bounds().Dx() == 0 || img.Bounds().Dy() == 0 { + t.Fatal("captured empty image") + } + + if os.Getenv("GRAPH2D_DUMP") != "" { + f, err := os.Create("/tmp/graph2d_crowded.png") + if err != nil { + t.Fatal(err) + } + defer f.Close() + if err := png.Encode(f, img); err != nil { + t.Fatal(err) + } + } +} + +// a mismatched axis must fall back to index labels and a flat line of equal +// values must not divide by zero +func TestRenderDegenerate(t *testing.T) { + test.NewApp() + g := New(&Config{ + Values: []float64{42, 42, 42, 42}, + ColorblindMode: colors.ModeNormal, + }) + w := test.NewWindow(g) + defer w.Close() + w.Resize(fyne.NewSize(300, 200)) + if img := w.Canvas().Capture(); img.Bounds().Dx() == 0 { + t.Fatal("captured empty image") + } +} diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index 0b192295..ae9ca5ab 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -21,6 +21,7 @@ import ( "github.com/roffe/txlogger/pkg/interpolate" "github.com/roffe/txlogger/pkg/layout" "github.com/roffe/txlogger/pkg/widgets" + "github.com/roffe/txlogger/pkg/widgets/graph2d" "github.com/roffe/txlogger/pkg/widgets/meshgrid" ) @@ -65,7 +66,8 @@ type MapViewer struct { selectedX, SelectedY int - mesh *meshgrid.Meshgrid + mesh *meshgrid.Meshgrid + graph *graph2d.Graph // Mouse mousePos fyne.Position @@ -119,6 +121,9 @@ func (mv *MapViewer) SetColorBlindMode(mode colors.ColorBlindMode) { if mv.mesh != nil { mv.mesh.SetColorBlindMode(mode) } + if mv.graph != nil { + mv.graph.SetColorBlindMode(mode) + } } } @@ -192,30 +197,57 @@ func (mv *MapViewer) render() fyne.CanvasObject { buttons := mv.createButtons() + mapview := container.NewBorder( + mv.xAxisLabelContainer, + nil, + mv.yAxisLabelContainer, + nil, + mv.innerView, + ) + if mv.numColumns == 1 || mv.numRows == 1 { + if mv.cfg.MeshView && mv.numData > 1 { + axisData := mv.cfg.XData + axisPrecision := mv.cfg.XPrecision + axisLabel := mv.cfg.XLabel + if mv.numColumns == 1 { + axisData = mv.cfg.YData + axisPrecision = mv.cfg.YPrecision + axisLabel = mv.cfg.YLabel + } + mv.graph = graph2d.New(&graph2d.Config{ + AxisData: axisData, + Values: mv.cfg.ZData, + AxisPrecision: axisPrecision, + ValuePrecision: mv.cfg.ZPrecision, + AxisLabel: axisLabel, + ColorblindMode: mv.colorMode, + }) + if mv.cfg.OnMouseDown != nil { + mv.graph.OnMouseDown = mv.cfg.OnMouseDown + } + split := container.NewVSplit( + mapview, + container.NewBorder( + nil, + buttons, + nil, + nil, + mv.graph, + ), + ) + split.Offset = 0.2 + return split + } return container.NewBorder( nil, buttons, nil, nil, - container.NewBorder( - mv.xAxisLabelContainer, - nil, - mv.yAxisLabelContainer, - nil, - mv.innerView, - ), + mapview, ) } - mapview := container.NewBorder( - mv.xAxisLabelContainer, - nil, - mv.yAxisLabelContainer, - nil, - mv.innerView, - ) - if mv.cfg.MeshView { var err error mv.mesh, err = meshgrid.NewMeshgrid( @@ -329,6 +361,9 @@ func (mv *MapViewer) Refresh() { if mv.mesh != nil { mv.mesh.LoadFloat64s(mv.zMin, mv.zMax, mv.cfg.ZData) } + if mv.graph != nil { + mv.graph.SetValues(mv.zMin, mv.zMax, mv.cfg.ZData) + } } func (mv *MapViewer) createYAxis() { @@ -414,6 +449,13 @@ func (mv *MapViewer) setXY() error { } mv.crosshair.Move(crosshairPos) + if mv.graph != nil { + if mv.numRows == 1 { + mv.graph.SetCursor(xIdx) + } else { + mv.graph.SetCursor(yIdx) + } + } if mv.cfg.CursorFollowCrosshair { mv.selectedX = int(math.Round(xIdx)) mv.SelectedY = int(math.Round(yIdx)) diff --git a/pkg/widgets/meshgrid/meshgrid_mouse.go b/pkg/widgets/meshgrid/meshgrid_mouse.go index de35a652..9d640a2a 100644 --- a/pkg/widgets/meshgrid/meshgrid_mouse.go +++ b/pkg/widgets/meshgrid/meshgrid_mouse.go @@ -18,8 +18,9 @@ func (m *Meshgrid) MouseMoved(event *desktop.MouseEvent) { dy := float64(event.Position.Y - m.lastMouseY) if m.dragging { if event.Button&desktop.MouseButtonPrimary == desktop.MouseButtonPrimary { - //m.orbit(dx*rotationScale, -dy*rotationScale) - m.rotateMeshgrid(-dy*rotationScale, dx*rotationScale, 0) + // Drag left spins clockwise, drag right counter-clockwise; + // drag up tilts backwards, drag down tilts forward. + m.orbit(-dx*rotationScale, -dy*rotationScale) m.throttledRefresh() } else if event.Button&desktop.MouseButtonSecondary == desktop.MouseButtonSecondary { roll := (dx + dy) * rollScale diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index 27e9bad5..a2ca8793 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -111,7 +111,12 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int if cols == 1 { m.rotateMeshgrid(0, 90, 0) } else { - m.rotateMeshgrid(60, 0, -30) + // T7Suite-style starting view: ~30° elevation (pitch 60° from + // top-down) with the mesh spun 35° around its vertical axis. Starting + // from identity, the RotZ term is a model-space turntable spin (the + // same composition orbit() applies), not a camera roll. + m.cameraRotation = RotationMatrixX(60).Multiply(m.cameraRotation).Multiply(RotationMatrixZ(-35)) + m.updateVertexPositions() } m.ExtendBaseWidget(m) @@ -199,14 +204,15 @@ func (m *Meshgrid) scaleMeshgrid(factor float64) { m.updateVertexPositions() } -// orbit performs a Fusion 360-style "turntable" orbit. Yaw is applied around -// the world Y axis (right-multiplied so it rotates the world before the -// camera), pitch is applied around the camera-local X axis (left-multiplied). -// Composing the two this way prevents roll from sneaking in on diagonal drags. -func (m *Meshgrid) orbit(yawDelta, pitchDelta float64) { +// orbit performs a Fusion 360-style "turntable" orbit. Spin is applied around +// the mesh's own vertical axis — data Z, the height axis (right-multiplied so +// it rotates the model before the camera) — pitch around the camera-local X +// axis (left-multiplied). Composing the two this way prevents roll from +// sneaking in on diagonal drags. +func (m *Meshgrid) orbit(spinDelta, pitchDelta float64) { pitchRot := RotationMatrixX(pitchDelta) - yawRot := RotationMatrixY(yawDelta) - m.cameraRotation = pitchRot.Multiply(m.cameraRotation).Multiply(yawRot) + spinRot := RotationMatrixZ(spinDelta) + m.cameraRotation = pitchRot.Multiply(m.cameraRotation).Multiply(spinRot) m.updateVertexPositions() } From 604619088a794953eac8e01b9b4d34a3c170f705 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 16:56:45 +0200 Subject: [PATCH 024/102] build pipeline --- .github/workflows/linux-release.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/windows-release.yml | 2 +- .github/workflows/windows.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-release.yml b/.github/workflows/linux-release.yml index 549f566b..7270da24 100644 --- a/.github/workflows/linux-release.yml +++ b/.github/workflows/linux-release.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6.4.0 with: - go-version: '1.26.3' + go-version: '1.26.4' cache: false - name: Get dependencies run: sudo apt-get update && sudo apt-get install 7zip gcc libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev libusb-1.0-0-dev libgtk-3-dev libasound2-dev libftdi1 libftdi1-dev diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 495e9710..033b7b39 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6.4.0 with: - go-version: '1.26.3' + go-version: '1.26.4' cache: false - name: Get dependencies run: sudo apt-get update && sudo apt-get install 7zip gcc libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev libusb-1.0-0-dev libgtk-3-dev libasound2-dev libftdi1 libftdi1-dev diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index d6f9eb8c..48245c7f 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -31,7 +31,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6.4.0 with: - go-version: '1.26.3' + go-version: '1.26.4' cache: false - name: Install dependencies diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6bb72a7d..0ccd16c9 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -34,7 +34,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6.4.0 with: - go-version: '1.26.3' + go-version: '1.26.4' cache: false - name: Install dependencies From 8ec8166e5832d0fb268495f34ea623b15e7e0dff Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 17:43:25 +0200 Subject: [PATCH 025/102] save meshgrid with setcursor --- pkg/assets/WHATSNEW.md | 10 +- pkg/widgets/mapviewer/mapviewer.go | 3 + pkg/widgets/meshgrid/meshgrid_draw.go | 63 ++++++--- pkg/widgets/meshgrid/meshgrid_render_test.go | 18 +++ pkg/widgets/meshgrid/meshgrid_surface.go | 14 +- pkg/widgets/meshgrid/meshgrid_widget.go | 130 ++++++++++++++++--- 6 files changed, 198 insertions(+), 40 deletions(-) diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index 1930bcd6..d8f250a1 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -1,5 +1,7 @@ # 2.1.10 -- Performance optimization for the log plotter and meshgrid +- Live tracking marker in the 3d mesh viewer showing where the ECU is reading from, mirroring the crosshair in the map above +- Fixed the 3d mesh showing one cell less than the table in each direction; values are now cell-centered so an 18x16 map renders 18x16 cells +- Performance optimization for the meshgrid - Force layouts to be loaded and saved in users home directory under the txlogger folder - Update ecusymbol to be able to read T5 versions - Added new config widget for AD scanner WBL settings inspired by T7's DisplAdap.LamScannerTab @@ -10,7 +12,11 @@ - Improved drag handler in logplayer, when zoomed in we drag fewer frames increasing as we zoom out - We now have 3 render modes for viewing 3d maps, Solid Wireframe, Solid & Wireframe. Press the little square icon in the mesh viewer to switch between them - WBL reconnect COM port while logging. If the COM port dies for a reason during logging it will try to re-connect -- Many widget performance improvements to allow slower computers to run txlogger better +- Performance improvements in many widget to allow slower computers to run txlogger better +- Improved camera handling in the 3d mesh viewer - now behaves like the t5/7/8 suites +- Added 2d graph for viewing flat maps +- Rewrote logplayer plotter to use about 50% less CPU on zoomed out views +- Big refactor of the log writing logic to be simpler to maintain and be more performant # 2.1.9 - Updated default T7 preset to include MAF.m_AirFromp_AirInlet diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index ae9ca5ab..97cd5c56 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -456,6 +456,9 @@ func (mv *MapViewer) setXY() error { mv.graph.SetCursor(yIdx) } } + if mv.mesh != nil { + mv.mesh.SetCursor(xIdx, yIdx) + } if mv.cfg.CursorFollowCrosshair { mv.selectedX = int(math.Round(xIdx)) mv.SelectedY = int(math.Round(yIdx)) diff --git a/pkg/widgets/meshgrid/meshgrid_draw.go b/pkg/widgets/meshgrid/meshgrid_draw.go index 64ae4b0c..47db30fc 100644 --- a/pkg/widgets/meshgrid/meshgrid_draw.go +++ b/pkg/widgets/meshgrid/meshgrid_draw.go @@ -36,11 +36,15 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { clear(img.Pix) } + // Vertices sit on cell corners, so the grid is one larger than the data + // in each direction (one quad per table cell). + vRows, vCols := m.rows+1, m.cols+1 + // Find min/max of the view-space Z for depth shading. minZ, maxZ := math.Inf(1), math.Inf(-1) - for i := 0; i < m.rows; i++ { + for i := 0; i < vRows; i++ { row := m.vertices[i] - for j := 0; j < m.cols; j++ { + for j := 0; j < vCols; j++ { z := row[j].Z if z < minZ { minZ = z @@ -56,7 +60,7 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { } // Precompute screen-space projection and color for each vertex once. - n := m.rows * m.cols + n := vRows * vCols if cap(m.scratchProjX) < n { m.scratchProjX = make([]int, n) m.scratchProjY = make([]int, n) @@ -68,24 +72,20 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { cx := float64(m.size.Width) * 0.5 cy := float64(m.size.Height) * 0.5 - for i := 0; i < m.rows; i++ { + for i := 0; i < vRows; i++ { row := m.vertices[i] - base := i * m.cols - for j := 0; j < m.cols; j++ { + base := i * vCols + for j := 0; j < vCols; j++ { v := row[j] idx := base + j projX[idx] = int(cx + v.X) projY[idx] = int(cy + v.Y) depth := (v.Z - minZ) / zRange - vertCol[idx] = m.getColorWithDepth(m.values[idx], depth) + vertCol[idx] = m.getColorWithDepth(v.V, depth) } } mode := m.renderMode - if m.rows < 2 || m.cols < 2 { - // A 1D mesh has no cells to fill; lines are all we can draw. - mode = RenderModeWireframe - } if mode != RenderModeWireframe { m.drawSurface(img, projX, projY, vertCol, mode == RenderModeSolidWireframe) @@ -95,16 +95,16 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { // Collect line segments using cached projections. segs := m.scratchLines[:0] - for i := 0; i < m.rows; i++ { - for j := 0; j < m.cols; j++ { - idx := i*m.cols + j + for i := 0; i < vRows; i++ { + for j := 0; j < vCols; j++ { + idx := i*vCols + j x1, y1 := projX[idx], projY[idx] // neighbors: (+1,0) down, (0,+1) right, (+1,-1) diagonal tryAddSeg := func(ni, nj int) { - if ni >= m.rows || nj < 0 || nj >= m.cols { + if ni >= vRows || nj < 0 || nj >= vCols { return } - nidx := ni*m.cols + nj + nidx := ni*vCols + nj x2, y2 := projX[nidx], projY[nidx] dx, dy := x2-x1, y2-y1 if dx*dx+dy*dy < 4 { @@ -155,10 +155,39 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { return img } +// cursorScreenPosition projects the tracking-marker cell position set by +// SetCursor onto the screen. The camera transform is linear, so bilinearly +// interpolating the transformed vertex coordinates lands on the same point +// as transforming the interpolated one. +func (m *Meshgrid) cursorScreenPosition() (float32, float32) { + // MapViewer indices are cell-centered while mesh vertices sit on cell + // corners; +0.5 lands the marker mid-cell on the corner grid. SetCursor + // clamps the indices, so sx/sy stay within [0.5, cols-0.5]/[0.5, rows-0.5]. + sx := m.cursorX + 0.5 + sy := m.cursorY + 0.5 + + x0 := int(sx) + y0 := int(sy) + x1 := min(x0+1, m.cols) + y1 := min(y0+1, m.rows) + fx := sx - float64(x0) + fy := sy - float64(y0) + + v00 := m.vertices[y0][x0] + v01 := m.vertices[y0][x1] + v10 := m.vertices[y1][x0] + v11 := m.vertices[y1][x1] + + vx := (1-fy)*((1-fx)*v00.X+fx*v01.X) + fy*((1-fx)*v10.X+fx*v11.X) + vy := (1-fy)*((1-fx)*v00.Y+fx*v01.Y) + fy*((1-fx)*v10.Y+fx*v11.Y) + + return float32(float64(m.size.Width)*0.5 + vx), float32(float64(m.size.Height)*0.5 + vy) +} + // getColorWithDepth combines color interpolation and depth enhancement in one step func (m *Meshgrid) getColorWithDepth(value, depthFactor float64) color.RGBA { // Get base color from value - //baseColor := m.getColorInterpolation(value) + // baseColor := m.getColorInterpolation(value) baseColor := colors.GetColorInterpolation( m.zmin, m.zmax, diff --git a/pkg/widgets/meshgrid/meshgrid_render_test.go b/pkg/widgets/meshgrid/meshgrid_render_test.go index 39948738..992f62a3 100644 --- a/pkg/widgets/meshgrid/meshgrid_render_test.go +++ b/pkg/widgets/meshgrid/meshgrid_render_test.go @@ -63,6 +63,24 @@ func TestRenderRotated(t *testing.T) { } } +// the tracking marker overlay must land on the surface: a cursor centered on +// a vertex (cell 7.5 + the 0.5 corner offset = vertex 8) must project to +// exactly that vertex's screen position +func TestCursorScreenPosition(t *testing.T) { + m := testGrid(t) + m.cursorX, m.cursorY, m.showCursor = 7.5, 7.5, true + px, py := m.cursorScreenPosition() + v := m.vertices[8][8] + wantX := float32(float64(m.size.Width)*0.5 + v.X) + wantY := float32(float64(m.size.Height)*0.5 + v.Y) + if px != wantX || py != wantY { + t.Fatalf("cursor at (%v, %v), want vertex projection (%v, %v)", px, py, wantX, wantY) + } + if px < 0 || px > m.size.Width || py < 0 || py > m.size.Height { + t.Fatalf("cursor (%v, %v) outside widget %v", px, py, m.size) + } +} + func BenchmarkDrawSurface(b *testing.B) { m := testGrid(b) m.renderMode = RenderModeSolidWireframe diff --git a/pkg/widgets/meshgrid/meshgrid_surface.go b/pkg/widgets/meshgrid/meshgrid_surface.go index 3b722f75..ddbb5406 100644 --- a/pkg/widgets/meshgrid/meshgrid_surface.go +++ b/pkg/widgets/meshgrid/meshgrid_surface.go @@ -37,9 +37,10 @@ const surfaceEdgeFade = 0.45 // When edges is true the cell outline is drawn right after its fill, which // keeps lines on hidden faces correctly occluded by nearer quads. func (m *Meshgrid) drawSurface(img *image.RGBA, projX, projY []int, vertCol []color.RGBA, edges bool) { + // One quad per data cell; the corner-vertex grid is (rows+1) x (cols+1). quads := m.scratchQuads[:0] - for i := 0; i < m.rows-1; i++ { - for j := 0; j < m.cols-1; j++ { + for i := 0; i < m.rows; i++ { + for j := 0; j < m.cols; j++ { z := m.vertices[i][j].Z + m.vertices[i][j+1].Z + m.vertices[i+1][j].Z + m.vertices[i+1][j+1].Z quads = append(quads, quadRef{i: i, j: j, depth: z * 0.25}) } @@ -64,11 +65,12 @@ func (m *Meshgrid) drawSurface(img *image.RGBA, projX, projY []int, vertCol []co il := 1 / math.Sqrt(lx*lx+ly*ly+lz*lz) lx, ly, lz = lx*il, ly*il, lz*il + vCols := m.cols + 1 for _, q := range quads { - ai := q.i*m.cols + q.j // top-left - bi := ai + 1 // top-right - di := ai + m.cols // bottom-left - ci := di + 1 // bottom-right + ai := q.i*vCols + q.j // top-left + bi := ai + 1 // top-right + di := ai + vCols // bottom-left + ci := di + 1 // bottom-right shade := m.quadShade(q.i, q.j, lx, ly, lz) ca := fadeColor(vertCol[ai], shade) diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index a2ca8793..5975e969 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -13,9 +13,14 @@ import ( "github.com/roffe/txlogger/pkg/colors" ) +// Vertex is a corner of the mesh. Values are cell-centered (one per table +// cell) while vertices sit on cell corners, so the vertex grid is one larger +// than the data grid in each direction and V holds the average of the +// adjacent cell values. type Vertex struct { Ox, Oy, Oz float64 // Original coordinates X, Y, Z float64 // Transformed coordinates for rendering + V float64 // Data value at this corner (average of adjacent cells) } var _ fyne.Widget = (*Meshgrid)(nil) @@ -59,6 +64,14 @@ type Meshgrid struct { cameraPosition [3]float64 // Camera's position in world space mousePosition image.Point + // Live tracking marker (fractional cell indices), mirroring the + // mapviewer crosshair. Hidden until SetCursor is first called. The marker + // is a canvas primitive overlaid on the mesh image so moving it only + // repaints the scene instead of re-rasterizing the whole mesh. + cursorX, cursorY float64 + showCursor bool + cursor *canvas.Circle + xlabel, ylabel, zlabel string refreshPending bool @@ -70,10 +83,21 @@ type Meshgrid struct { OnMouseDown func() } +// Marker colors match the mapviewer crosshair so the two tracking +// indicators read as the same thing. +var ( + cursorFillColor = color.RGBA{R: 165, G: 55, B: 253, A: 255} + cursorRingColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} +) + +const cursorRadius = 6 + // NewMeshgrid creates a new Meshgrid given width, height, depth and spacing. func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int, colorBlindMode colors.ColorBlindMode) (*Meshgrid, error) { + cols = max(1, cols) + rows = max(1, rows) // Check if the provided values slice has the correct number of elements - if len(values) != max(1, cols)*max(1, rows) { + if len(values) != cols*rows { return nil, fmt.Errorf("the number of Z values does not match the meshgrid dimensions") } // Find min and max Z values for normalization @@ -104,7 +128,7 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int colorMode: colorBlindMode, } - m.createVertices(fyne.Max(float32(m.cols), 1), fyne.Max(float32(m.rows), 1)) + m.createVertices() m.scaleMeshgrid(0.3) @@ -126,6 +150,16 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int m.image.FillMode = canvas.ImageFillOriginal m.image.ScaleMode = canvas.ImageScaleFastest + m.cursor = &canvas.Circle{ + // Position2 sets the bounds directly; Resize would trigger a canvas + // refresh before the widget is even shown. + Position2: fyne.NewPos(cursorRadius*2, cursorRadius*2), + FillColor: cursorFillColor, + StrokeColor: cursorRingColor, + StrokeWidth: 2, + Hidden: true, + } + return m, nil } @@ -155,7 +189,11 @@ func (m *Meshgrid) SetColorBlindMode(mode colors.ColorBlindMode) { m.refresh() } -func (m *Meshgrid) createVertices(width, height float32) { +// createVertices builds the corner-vertex grid: one quad per table cell, so +// the mesh shows exactly cols x rows cells like the map above. The grid is +// (rows+1) x (cols+1); each corner takes the average of the 1-4 cell values +// touching it. +func (m *Meshgrid) createVertices() { // Guard against a zero range (e.g. all values identical / all zero) so we // produce a flat mesh at Z=0 instead of NaN from a div-by-zero. zrange := m.zrange @@ -163,16 +201,19 @@ func (m *Meshgrid) createVertices(width, height float32) { zrange = 1 } - vertices := make([][]Vertex, 0, m.rows) - valueIndex := 0 + vRows, vCols := m.rows+1, m.cols+1 + vertices := make([][]Vertex, 0, vRows) var sumX, sumY, sumZ float64 var count int - for i := m.rows; i > 0; i-- { - row := make([]Vertex, 0, m.cols) - for j := 0; j < m.cols; j++ { - x := -float64(width)*.5 + float64(j)*float64(m.cellWidth) - y := -float64(height)*.5 + float64(i)*float64(m.cellHeight) - z := ((m.values[valueIndex] - m.zmin) / zrange) * m.depth + for i := 0; i < vRows; i++ { + row := make([]Vertex, 0, vCols) + for j := 0; j < vCols; j++ { + value := m.cornerValue(i, j) + x := float64(j) * float64(m.cellWidth) + // Data row 0 is the bottom row in the map; keep it at the high-Y + // end of the mesh like before, so the orientation is unchanged. + y := float64(vRows-i) * float64(m.cellHeight) + z := ((value - m.zmin) / zrange) * m.depth row = append(row, Vertex{ Ox: x, Oy: y, @@ -180,12 +221,12 @@ func (m *Meshgrid) createVertices(width, height float32) { X: x, Y: y, Z: z, + V: value, }) sumX += x sumY += y sumZ += z count++ - valueIndex++ } vertices = append(vertices, row) } @@ -199,6 +240,25 @@ func (m *Meshgrid) createVertices(width, height float32) { } } +// cornerValue averages the cell values adjacent to corner (vi, vj): four in +// the interior, two along edges and one at the outer corners. +func (m *Meshgrid) cornerValue(vi, vj int) float64 { + r0 := max(vi-1, 0) + r1 := min(vi, m.rows-1) + c0 := max(vj-1, 0) + c1 := min(vj, m.cols-1) + + var sum float64 + var n int + for r := r0; r <= r1; r++ { + for c := c0; c <= c1; c++ { + sum += m.values[r*m.cols+c] + n++ + } + } + return sum / float64(n) +} + func (m *Meshgrid) scaleMeshgrid(factor float64) { m.scale = m.scale * factor m.updateVertexPositions() @@ -266,7 +326,7 @@ func (m *Meshgrid) SetFloat64(idx int, value float64) { } m.values[idx] = value m.zmin, m.zmax, m.zrange = findMinMaxRange(m.values) - m.createVertices(fyne.Max(float32(m.cols), 1), fyne.Max(float32(m.rows), 1)) + m.createVertices() m.updateVertexPositions() m.refresh() } @@ -283,11 +343,48 @@ func (m *Meshgrid) LoadFloat64s(min, max float64, floats []float64) { m.values = floats - m.createVertices(fyne.Max(float32(m.cols), 1), fyne.Max(float32(m.rows), 1)) + m.createVertices() m.updateVertexPositions() m.refresh() } +// SetCursor positions the tracking marker at the (fractional) cell index, +// mirroring the crosshair in the map above. The marker rides on the mesh +// surface, interpolated between the four surrounding vertices. +func (m *Meshgrid) SetCursor(xIdx, yIdx float64) { + if xIdx < 0 { + xIdx = 0 + } else if max := float64(m.cols - 1); xIdx > max { + xIdx = max + } + if yIdx < 0 { + yIdx = 0 + } else if max := float64(m.rows - 1); yIdx > max { + yIdx = max + } + if m.showCursor && xIdx == m.cursorX && yIdx == m.cursorY { + return + } + m.cursorX = xIdx + m.cursorY = yIdx + m.showCursor = true + m.moveCursor() +} + +// moveCursor repositions the overlay marker on the projected surface point. +// Moving a canvas primitive only repaints the scene from cached textures, +// so cursor updates don't re-rasterize the mesh. +func (m *Meshgrid) moveCursor() { + if !m.showCursor { + return + } + px, py := m.cursorScreenPosition() + m.cursor.Move(fyne.NewPos(px-cursorRadius, py-cursorRadius)) + if m.cursor.Hidden { + m.cursor.Show() + } +} + // returns the min, max and range across the data func findMinMaxRange(values []float64) (float64, float64, float64) { minZ, maxZ := values[0], values[0] @@ -310,6 +407,9 @@ func (m *Meshgrid) refresh() { m.image.Image = m.drawMeshgridLines() m.image.Resize(m.size) m.image.Refresh() + // Rotation, scale, resize and data changes all move the projected + // surface point under the marker. + m.moveCursor() } func (m *Meshgrid) throttledRefresh() { @@ -358,7 +458,7 @@ func (m *meshgridRenderer) Destroy() { func (m *meshgridRenderer) Objects() []fyne.CanvasObject { if m.objects == nil { - m.objects = []fyne.CanvasObject{m.MG.image} + m.objects = []fyne.CanvasObject{m.MG.image, m.MG.cursor} } return m.objects } From b5153462bcacc32779271d0b8858fc72be4d613c Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 19:11:36 +0200 Subject: [PATCH 026/102] meshgrid poly support --- pkg/widgets/meshgrid/meshgrid_poly.go | 308 +++++++++++++++++++ pkg/widgets/meshgrid/meshgrid_render_test.go | 134 ++++++++ pkg/widgets/meshgrid/meshgrid_widget.go | 40 +++ 3 files changed, 482 insertions(+) create mode 100644 pkg/widgets/meshgrid/meshgrid_poly.go diff --git a/pkg/widgets/meshgrid/meshgrid_poly.go b/pkg/widgets/meshgrid/meshgrid_poly.go new file mode 100644 index 00000000..fc4f69bc --- /dev/null +++ b/pkg/widgets/meshgrid/meshgrid_poly.go @@ -0,0 +1,308 @@ +package meshgrid + +import ( + "image/color" + "math" + "slices" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" +) + +// Polygon-based renderer experiment: instead of rasterizing the mesh into an +// RGBA image (and re-uploading that texture) every frame, each grid cell is a +// reusable canvas.ArbitraryPolygon that fyne's GL painter draws as a single +// SDF shader quad. A frame update only mutates points, colors and the +// painter's order of the object list — nothing is rasterized on the CPU. +// +// Trade-offs vs the image renderer: +// - Flat shading: a polygon has one fill color, so the four corner colors +// are averaged instead of Gouraud-interpolated across the quad. +// - Wireframe mode uses the quad outlines (stroke), so the cell diagonals +// are not drawn. +// - fyne does not clip renderer objects to the widget bounds, so a zoomed +// mesh can spill outside the widget area. + +// polyPad expands each polygon's bounding box so the 1px stroke and the +// shader's antialiased edge aren't clipped at the object bounds (the painter +// clamps points to the object's own size). +const polyPad float32 = 1.5 + +// initPolygons creates the reusable canvas objects: one polygon per data cell +// plus the axis-indicator lines and labels. Geometry and colors are filled in +// by updatePolygons; nothing here needs a driver, so it is safe to call from +// the constructor (and from tests without an app). +func (m *Meshgrid) initPolygons() { + m.polys = make([]*canvas.ArbitraryPolygon, m.rows*m.cols) + for i := range m.polys { + m.polys[i] = &canvas.ArbitraryPolygon{Points: make([]fyne.Position, 4)} + } + + axisColors := [3]color.RGBA{ + {R: 255, A: 255}, // X red + {G: 255, A: 255}, // Y green + {B: 255, A: 255}, // Z blue + } + labels := [3]string{m.xlabel, m.ylabel, m.zlabel} + for i := range m.axisLines { + m.axisLines[i] = &canvas.Line{StrokeColor: axisColors[i], StrokeWidth: 1} + t := canvas.NewText(labels[i], axisColors[i]) + t.TextSize = 11 + m.axisLabels[i] = t + } +} + +// updatePolygons projects the mesh and updates the reusable canvas objects: +// quad geometry, flat fill/stroke colors and the back-to-front painter's +// order of the renderer's object list. +func (m *Meshgrid) updatePolygons() { + w, h := m.size.Width, m.size.Height + if w <= 0 || h <= 0 || len(m.polys) == 0 { + return + } + + vRows, vCols := m.rows+1, m.cols+1 + + // View-space depth range for the depth shading, same as the image path. + minZ, maxZ := math.Inf(1), math.Inf(-1) + for i := 0; i < vRows; i++ { + row := m.vertices[i] + for j := 0; j < vCols; j++ { + z := row[j].Z + if z < minZ { + minZ = z + } + if z > maxZ { + maxZ = z + } + } + } + zRange := maxZ - minZ + if zRange == 0 { + zRange = 1 + } + + // Per-vertex screen projection and color. Float projection keeps subpixel + // precision, which the GPU edges make visible (the image path rounds to + // whole pixels). + n := vRows * vCols + if cap(m.scratchFX) < n { + m.scratchFX = make([]float32, n) + m.scratchFY = make([]float32, n) + } + if cap(m.scratchColors) < n { + m.scratchColors = make([]color.RGBA, n) + } + fxs := m.scratchFX[:n] + fys := m.scratchFY[:n] + vertCol := m.scratchColors[:n] + + cx := float64(w) * 0.5 + cy := float64(h) * 0.5 + for i := 0; i < vRows; i++ { + row := m.vertices[i] + base := i * vCols + for j := 0; j < vCols; j++ { + v := row[j] + idx := base + j + fxs[idx] = float32(cx + v.X) + fys[idx] = float32(cy + v.Y) + depth := (v.Z - minZ) / zRange + vertCol[idx] = m.getColorWithDepth(v.V, depth) + } + } + + // Fixed light direction in view space, normalized once per frame. + lx, ly, lz := 0.3, -0.5, 0.8 + il := 1 / math.Sqrt(lx*lx+ly*ly+lz*lz) + lx, ly, lz = lx*il, ly*il, lz*il + + mode := m.renderMode + + quads := m.scratchQuads[:0] + for i := 0; i < m.rows; i++ { + for j := 0; j < m.cols; j++ { + ai := i*vCols + j // top-left + bi := ai + 1 // top-right + di := ai + vCols // bottom-left + ci := di + 1 // bottom-right + + z := m.vertices[i][j].Z + m.vertices[i][j+1].Z + m.vertices[i+1][j].Z + m.vertices[i+1][j+1].Z + quads = append(quads, quadRef{i: i, j: j, depth: z * 0.25}) + + poly := m.polys[i*m.cols+j] + + ax, ay := fxs[ai], fys[ai] + bx, by := fxs[bi], fys[bi] + ccx, ccy := fxs[ci], fys[ci] + dx, dy := fxs[di], fys[di] + + minx := min(min(ax, bx), min(ccx, dx)) + maxx := max(max(ax, bx), max(ccx, dx)) + miny := min(min(ay, by), min(ccy, dy)) + maxy := max(max(ay, by), max(ccy, dy)) + + x0, y0 := ax-minx+polyPad, ay-miny+polyPad + x1, y1 := bx-minx+polyPad, by-miny+polyPad + x2, y2 := ccx-minx+polyPad, ccy-miny+polyPad + x3, y3 := dx-minx+polyPad, dy-miny+polyPad + + // A quad folding over itself at the mesh silhouette projects to a + // self-intersecting outline, which both fyne painters mangle (the + // software stroker floods the whole bounding box). Swap a corner + // pair to make the polygon simple again. + if segmentsCross(x0, y0, x1, y1, x2, y2, x3, y3) { + x1, y1, x2, y2 = x2, y2, x1, y1 + } else if segmentsCross(x1, y1, x2, y2, x3, y3, x0, y0) { + x2, y2, x3, y3 = x3, y3, x2, y2 + } + + // Corners of an edge-on cell project onto each other, giving the + // polygon zero-length edges. The GL shader normalize()s the edge + // vectors, so those become NaN and flood the bounding box with + // color. Drop collapsed corners (the quad degrades to a triangle); + // under three distinct corners the cell is invisible anyway. + pts := appendDistinct(poly.Points[:0], [4]fyne.Position{ + {X: x0, Y: y0}, {X: x1, Y: y1}, {X: x2, Y: y2}, {X: x3, Y: y3}, + }) + poly.Points = pts + if len(pts) < 3 { + if !poly.Hidden { + poly.Hide() + } + continue + } + if poly.Hidden { + poly.Show() + } + + // Flat shade: average the four corner colors, then apply the same + // Lambert term the image renderer uses per quad. + shade := m.quadShade(i, j, lx, ly, lz) + fill := avgQuadColor(vertCol[ai], vertCol[bi], vertCol[ci], vertCol[di], shade) + + switch mode { + case RenderModeSolidWireframe: + poly.FillColor = fill + poly.StrokeColor = fadeColor(fill, surfaceEdgeFade) + poly.StrokeWidth = 1 + case RenderModeSolid: + poly.FillColor = fill + poly.StrokeColor = color.Transparent + poly.StrokeWidth = 0 + case RenderModeWireframe: + poly.FillColor = color.Transparent + poly.StrokeColor = fill + poly.StrokeWidth = 1 + } + + // The painter clamps points to the object's own bounds, so size + // the object to the quad's padded bbox and make points relative. + poly.Move(fyne.NewPos(minx-polyPad, miny-polyPad)) + poly.Resize(fyne.NewSize(maxx-minx+2*polyPad, maxy-miny+2*polyPad)) + } + } + m.scratchQuads = quads + + // Back-to-front painter's order, applied by reordering the object list. + slices.SortFunc(quads, func(a, b quadRef) int { + switch { + case a.depth < b.depth: + return -1 + case a.depth > b.depth: + return 1 + default: + return 0 + } + }) + + objs := m.polyObjects[:0] + for _, q := range quads { + objs = append(objs, m.polys[q.i*m.cols+q.j]) + } + m.updateAxisObjects() + for i := range m.axisLines { + objs = append(objs, m.axisLines[i], m.axisLabels[i]) + } + objs = append(objs, m.cursor) + m.polyObjects = objs +} + +// updateAxisObjects mirrors drawAxisIndicator with canvas primitives. +func (m *Meshgrid) updateAxisObjects() { + const indicatorScale = 60.0 + origin := fyne.NewPos(60, m.size.Height-m.size.Height/4) + + r := m.cameraRotation + ends := [3][3]float64{ + r.MultiplyVector([3]float64{indicatorScale, 0, 0}), + r.MultiplyVector([3]float64{0, -indicatorScale, 0}), + r.MultiplyVector([3]float64{0, 0, indicatorScale}), + } + for i, e := range ends { + end := fyne.NewPos(origin.X+float32(e[0]), origin.Y+float32(e[1])) + m.axisLines[i].Position1 = origin + m.axisLines[i].Position2 = end + // Text positions by its top-left; the image path draws at the + // baseline, so nudge up to roughly match. + m.axisLabels[i].Move(fyne.NewPos(end.X+5, end.Y-8)) + } +} + +// minEdgeSq is the squared minimum polygon edge length below which two +// corners count as collapsed onto each other. The GL painter rounds every +// point to whole device pixels before the shader sees it, so corners must +// stay far enough apart that they can't land on the same pixel after +// rounding: distance d keeps the larger coordinate delta ≥ d/√2, which +// survives rounding while d·pixScale > √2 — at d=2 that covers any +// pixScale ≥ 0.75. +const minEdgeSq float32 = 4 + +// appendDistinct appends the corners to dst, skipping ones that collapse +// onto the previously kept corner (including last-onto-first wrap-around), +// so every edge of the resulting polygon has a usable direction vector. +func appendDistinct(dst []fyne.Position, corners [4]fyne.Position) []fyne.Position { + for _, p := range corners { + if len(dst) > 0 { + last := dst[len(dst)-1] + dx, dy := p.X-last.X, p.Y-last.Y + if dx*dx+dy*dy < minEdgeSq { + continue + } + } + dst = append(dst, p) + } + for len(dst) >= 2 { + first, last := dst[0], dst[len(dst)-1] + dx, dy := first.X-last.X, first.Y-last.Y + if dx*dx+dy*dy >= minEdgeSq { + break + } + dst = dst[:len(dst)-1] + } + return dst +} + +// segmentsCross reports whether segments ab and cd properly cross (shared or +// collinear endpoints don't count, which is fine for untwisting quads). +func segmentsCross(ax, ay, bx, by, cx, cy, dx, dy float32) bool { + orient := func(px, py, qx, qy, rx, ry float32) float32 { + return (qx-px)*(ry-py) - (qy-py)*(rx-px) + } + d1 := orient(ax, ay, bx, by, cx, cy) + d2 := orient(ax, ay, bx, by, dx, dy) + d3 := orient(cx, cy, dx, dy, ax, ay) + d4 := orient(cx, cy, dx, dy, bx, by) + return (d1 > 0) != (d2 > 0) && (d3 > 0) != (d4 > 0) +} + +// avgQuadColor averages the four corner colors and applies the flat Lambert +// shade. shade is <= 1 so the components can't overflow. +func avgQuadColor(a, b, c, d color.RGBA, shade float64) color.RGBA { + return color.RGBA{ + R: uint8(float64((int(a.R)+int(b.R)+int(c.R)+int(d.R))>>2) * shade), + G: uint8(float64((int(a.G)+int(b.G)+int(c.G)+int(d.G))>>2) * shade), + B: uint8(float64((int(a.B)+int(b.B)+int(c.B)+int(d.B))>>2) * shade), + A: 255, + } +} diff --git a/pkg/widgets/meshgrid/meshgrid_render_test.go b/pkg/widgets/meshgrid/meshgrid_render_test.go index 992f62a3..b28f197f 100644 --- a/pkg/widgets/meshgrid/meshgrid_render_test.go +++ b/pkg/widgets/meshgrid/meshgrid_render_test.go @@ -6,6 +6,7 @@ import ( "testing" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/test" "github.com/roffe/txlogger/pkg/colors" ) @@ -91,6 +92,139 @@ func BenchmarkDrawSurface(b *testing.B) { } } +// CPU-side cost of a polygon-renderer frame (projection, colors, painter's +// sort, object updates). The GPU draw calls aren't measurable here; compare +// against BenchmarkDrawSurface, which also excludes that path's per-frame +// texture upload. +func BenchmarkUpdatePolygons(b *testing.B) { + m := testGrid(b) + m.renderMode = RenderModeSolidWireframe + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + m.updatePolygons() + } +} + +// TestPolygonDegenerateQuads sweeps the camera across many angles over a +// surface with flat plateaus (rows of identical values, like real boost maps) +// and asserts every visible polygon is well-formed: at least three corners, +// no near-zero edges (those become NaN in the GL shader's normalize() and +// flood the bounding box with color) and no self-intersecting outlines. +func TestPolygonDegenerateQuads(t *testing.T) { + cols, rows := 16, 16 + values := make([]float64, cols*rows) + for i := 0; i < rows; i++ { + for j := 0; j < cols; j++ { + // Stepped plateaus: blocks of identical values produce coplanar + // cells that collapse to slivers when viewed edge-on. + values[i*cols+j] = float64((i / 4) * 100) + } + } + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal) + if err != nil { + t.Fatal(err) + } + m.size = fyne.NewSize(800, 500) + m.renderMode = RenderModeSolidWireframe + + for pitch := 0; pitch < 180; pitch += 15 { + for yaw := 0; yaw < 360; yaw += 15 { + m.cameraRotation = RotationMatrixX(float64(pitch)).Multiply(NewMatrix3x3()).Multiply(RotationMatrixZ(float64(yaw))) + m.updateVertexPositions() + m.updatePolygons() + + for idx, p := range m.polys { + if p.Hidden { + continue + } + pts := p.Points + if len(pts) < 3 { + t.Fatalf("pitch=%d yaw=%d cell %d: visible polygon with %d points", pitch, yaw, idx, len(pts)) + } + for k := range pts { + a, b := pts[k], pts[(k+1)%len(pts)] + dx, dy := b.X-a.X, b.Y-a.Y + if dx*dx+dy*dy < minEdgeSq { + t.Fatalf("pitch=%d yaw=%d cell %d: collapsed edge %d: %v", pitch, yaw, idx, k, pts) + } + } + if len(pts) == 4 { + if segmentsCross(pts[0].X, pts[0].Y, pts[1].X, pts[1].Y, pts[2].X, pts[2].Y, pts[3].X, pts[3].Y) || + segmentsCross(pts[1].X, pts[1].Y, pts[2].X, pts[2].Y, pts[3].X, pts[3].Y, pts[0].X, pts[0].Y) { + t.Fatalf("pitch=%d yaw=%d cell %d: self-intersecting polygon: %v", pitch, yaw, idx, pts) + } + } + } + } + } +} + +func TestAppendDistinct(t *testing.T) { + p := func(x, y float32) fyne.Position { return fyne.NewPos(x, y) } + for _, tc := range []struct { + name string + corners [4]fyne.Position + want int + }{ + {"all distinct", [4]fyne.Position{p(0, 0), p(10, 0), p(10, 10), p(0, 10)}, 4}, + {"one collapsed edge", [4]fyne.Position{p(0, 0), p(10, 0), p(10.1, 0.1), p(0, 10)}, 3}, + {"wrap-around collapse", [4]fyne.Position{p(0, 0), p(10, 0), p(10, 10), p(0.1, 0.1)}, 3}, + {"line", [4]fyne.Position{p(0, 0), p(10, 0), p(10.1, 0), p(0.2, 0.1)}, 2}, + {"point", [4]fyne.Position{p(5, 5), p(5.1, 5), p(5, 5.1), p(5.1, 5.1)}, 1}, + } { + t.Run(tc.name, func(t *testing.T) { + got := appendDistinct(make([]fyne.Position, 0, 4), tc.corners) + if len(got) != tc.want { + t.Fatalf("got %d points %v, want %d", len(got), got, tc.want) + } + for k := 0; len(got) >= 2 && k < len(got); k++ { + a, b := got[k], got[(k+1)%len(got)] + dx, dy := b.X-a.X, b.Y-a.Y + if dx*dx+dy*dy < minEdgeSq { + t.Fatalf("result has collapsed edge %d: %v", k, got) + } + } + }) + } +} + +// TestPolygonRender captures the polygon renderer through the software +// painter so the output can be eyeballed without a GL window. +func TestPolygonRender(t *testing.T) { + if os.Getenv("MESHGRID_DUMP") == "" { + t.Skip("set MESHGRID_DUMP=1 to dump polygon render PNGs") + } + test.NewApp() + for _, tc := range []struct { + name string + mode RenderMode + }{ + {"solidwire", RenderModeSolidWireframe}, + {"solid", RenderModeSolid}, + {"wireframe", RenderModeWireframe}, + } { + t.Run(tc.name, func(t *testing.T) { + m := testGrid(t) + m.usePolygons = true + m.renderMode = tc.mode + w := test.NewWindow(m) + defer w.Close() + w.Resize(fyne.NewSize(820, 520)) + m.refresh() + img := w.Canvas().Capture() + f, err := os.Create("/tmp/meshgrid_poly_" + tc.name + ".png") + if err != nil { + t.Fatal(err) + } + defer f.Close() + if err := png.Encode(f, img); err != nil { + t.Fatal(err) + } + }) + } +} + func TestRenderModes(t *testing.T) { for _, tc := range []struct { name string diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index 5975e969..6276c1e1 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -5,6 +5,7 @@ import ( "image" "image/color" "log" + "os" "time" "fyne.io/fyne/v2" @@ -47,6 +48,16 @@ type Meshgrid struct { scratchLines []lineSegment scratchQuads []quadRef + // Polygon-renderer experiment (see meshgrid_poly.go): one reusable + // canvas.ArbitraryPolygon per cell instead of rasterizing into an image. + usePolygons bool + polys []*canvas.ArbitraryPolygon + polyObjects []fyne.CanvasObject + axisLines [3]*canvas.Line + axisLabels [3]*canvas.Text + scratchFX []float32 + scratchFY []float32 + renderMode RenderMode lastMouseX, lastMouseY float32 @@ -126,6 +137,10 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int zlabel: zlabel, colorMode: colorBlindMode, + + // Polygon-renderer experiment toggle: set TXLOGGER_MESH_POLY=0 to + // fall back to the image rasterizer for comparison. + usePolygons: os.Getenv("TXLOGGER_MESH_POLY") != "0", } m.createVertices() @@ -160,6 +175,8 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int Hidden: true, } + m.initPolygons() + return m, nil } @@ -404,6 +421,14 @@ func (m *Meshgrid) Refresh() { } func (m *Meshgrid) refresh() { + if m.usePolygons { + // Geometry/color updates on the reusable polygons; the canvas.Refresh + // marks the scene dirty so color-only changes repaint too. + m.updatePolygons() + m.moveCursor() + canvas.Refresh(m) + return + } m.image.Image = m.drawMeshgridLines() m.image.Resize(m.size) m.image.Refresh() @@ -428,6 +453,13 @@ func (m *Meshgrid) throttledRefresh() { } func (m *Meshgrid) CreateRenderer() fyne.WidgetRenderer { + if m.usePolygons { + // Text measuring needs a driver, so the labels can't be sized in the + // constructor (tests build widgets without an app). + for _, t := range m.axisLabels { + t.Resize(t.MinSize()) + } + } return &meshgridRenderer{MG: m} } @@ -457,6 +489,14 @@ func (m *meshgridRenderer) Destroy() { } func (m *meshgridRenderer) Objects() []fyne.CanvasObject { + if m.MG.usePolygons { + // updatePolygons rebuilds the list in painter's order every frame; + // populate it here for the first paint. + if len(m.MG.polyObjects) == 0 { + m.MG.updatePolygons() + } + return m.MG.polyObjects + } if m.objects == nil { m.objects = []fyne.CanvasObject{m.MG.image, m.MG.cursor} } From 99fcc2112c377d1c7404a66bb6d756f590231444 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 21:34:28 +0200 Subject: [PATCH 027/102] meshgrid shader --- pkg/widgets/meshgrid/meshgrid_axis.go | 41 ++ pkg/widgets/meshgrid/meshgrid_poly.go | 41 +- pkg/widgets/meshgrid/meshgrid_render_test.go | 11 +- pkg/widgets/meshgrid/meshgrid_shader.go | 497 +++++++++++++++++++ pkg/widgets/meshgrid/meshgrid_shader_test.go | 140 ++++++ pkg/widgets/meshgrid/meshgrid_widget.go | 112 ++++- 6 files changed, 780 insertions(+), 62 deletions(-) create mode 100644 pkg/widgets/meshgrid/meshgrid_shader.go create mode 100644 pkg/widgets/meshgrid/meshgrid_shader_test.go diff --git a/pkg/widgets/meshgrid/meshgrid_axis.go b/pkg/widgets/meshgrid/meshgrid_axis.go index 7c79d19d..bcb8c626 100644 --- a/pkg/widgets/meshgrid/meshgrid_axis.go +++ b/pkg/widgets/meshgrid/meshgrid_axis.go @@ -4,11 +4,52 @@ import ( "image" "image/color" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" "golang.org/x/image/font" "golang.org/x/image/font/basicfont" "golang.org/x/image/math/fixed" ) +// initAxisObjects creates the axis-indicator overlay (lines and labels) used +// by the shader and polygon backends; the image backend instead draws the +// indicator into its raster (drawAxisIndicator below). +func (m *Meshgrid) initAxisObjects() { + axisColors := [3]color.RGBA{ + {R: 255, A: 255}, // X red + {G: 255, A: 255}, // Y green + {B: 255, A: 255}, // Z blue + } + labels := [3]string{m.xlabel, m.ylabel, m.zlabel} + for i := range m.axisLines { + m.axisLines[i] = &canvas.Line{StrokeColor: axisColors[i], StrokeWidth: 1} + t := canvas.NewText(labels[i], axisColors[i]) + t.TextSize = 11 + m.axisLabels[i] = t + } +} + +// updateAxisObjects mirrors drawAxisIndicator with canvas primitives. +func (m *Meshgrid) updateAxisObjects() { + const indicatorScale = 60.0 + origin := fyne.NewPos(60, m.size.Height-m.size.Height/4) + + r := m.cameraRotation + ends := [3][3]float64{ + r.MultiplyVector([3]float64{indicatorScale, 0, 0}), + r.MultiplyVector([3]float64{0, -indicatorScale, 0}), + r.MultiplyVector([3]float64{0, 0, indicatorScale}), + } + for i, e := range ends { + end := fyne.NewPos(origin.X+float32(e[0]), origin.Y+float32(e[1])) + m.axisLines[i].Position1 = origin + m.axisLines[i].Position2 = end + // Text positions by its top-left; the image path draws at the + // baseline, so nudge up to roughly match. + m.axisLabels[i].Move(fyne.NewPos(end.X+5, end.Y-8)) + } +} + func (m *Meshgrid) drawAxisIndicator(img *image.RGBA) { cornerOffset := 60.0 indicatorScale := 60.0 diff --git a/pkg/widgets/meshgrid/meshgrid_poly.go b/pkg/widgets/meshgrid/meshgrid_poly.go index fc4f69bc..709d8d95 100644 --- a/pkg/widgets/meshgrid/meshgrid_poly.go +++ b/pkg/widgets/meshgrid/meshgrid_poly.go @@ -28,28 +28,14 @@ import ( // clamps points to the object's own size). const polyPad float32 = 1.5 -// initPolygons creates the reusable canvas objects: one polygon per data cell -// plus the axis-indicator lines and labels. Geometry and colors are filled in -// by updatePolygons; nothing here needs a driver, so it is safe to call from -// the constructor (and from tests without an app). +// initPolygons creates the reusable cell polygons. Geometry and colors are +// filled in by updatePolygons; nothing here needs a driver, so it is safe to +// call from the constructor (and from tests without an app). func (m *Meshgrid) initPolygons() { m.polys = make([]*canvas.ArbitraryPolygon, m.rows*m.cols) for i := range m.polys { m.polys[i] = &canvas.ArbitraryPolygon{Points: make([]fyne.Position, 4)} } - - axisColors := [3]color.RGBA{ - {R: 255, A: 255}, // X red - {G: 255, A: 255}, // Y green - {B: 255, A: 255}, // Z blue - } - labels := [3]string{m.xlabel, m.ylabel, m.zlabel} - for i := range m.axisLines { - m.axisLines[i] = &canvas.Line{StrokeColor: axisColors[i], StrokeWidth: 1} - t := canvas.NewText(labels[i], axisColors[i]) - t.TextSize = 11 - m.axisLabels[i] = t - } } // updatePolygons projects the mesh and updates the reusable canvas objects: @@ -228,27 +214,6 @@ func (m *Meshgrid) updatePolygons() { m.polyObjects = objs } -// updateAxisObjects mirrors drawAxisIndicator with canvas primitives. -func (m *Meshgrid) updateAxisObjects() { - const indicatorScale = 60.0 - origin := fyne.NewPos(60, m.size.Height-m.size.Height/4) - - r := m.cameraRotation - ends := [3][3]float64{ - r.MultiplyVector([3]float64{indicatorScale, 0, 0}), - r.MultiplyVector([3]float64{0, -indicatorScale, 0}), - r.MultiplyVector([3]float64{0, 0, indicatorScale}), - } - for i, e := range ends { - end := fyne.NewPos(origin.X+float32(e[0]), origin.Y+float32(e[1])) - m.axisLines[i].Position1 = origin - m.axisLines[i].Position2 = end - // Text positions by its top-left; the image path draws at the - // baseline, so nudge up to roughly match. - m.axisLabels[i].Move(fyne.NewPos(end.X+5, end.Y-8)) - } -} - // minEdgeSq is the squared minimum polygon edge length below which two // corners count as collapsed onto each other. The GL painter rounds every // point to whole device pixels before the shader sees it, so corners must diff --git a/pkg/widgets/meshgrid/meshgrid_render_test.go b/pkg/widgets/meshgrid/meshgrid_render_test.go index b28f197f..56524fb6 100644 --- a/pkg/widgets/meshgrid/meshgrid_render_test.go +++ b/pkg/widgets/meshgrid/meshgrid_render_test.go @@ -92,12 +92,20 @@ func BenchmarkDrawSurface(b *testing.B) { } } +// usePolyBackend switches a test grid to the polygon renderer (the default +// is the shader backend, whose per-frame work happens on the GPU). +func usePolyBackend(m *Meshgrid) { + m.backend = backendPolygons + m.initPolygons() +} + // CPU-side cost of a polygon-renderer frame (projection, colors, painter's // sort, object updates). The GPU draw calls aren't measurable here; compare // against BenchmarkDrawSurface, which also excludes that path's per-frame // texture upload. func BenchmarkUpdatePolygons(b *testing.B) { m := testGrid(b) + usePolyBackend(m) m.renderMode = RenderModeSolidWireframe b.ReportAllocs() b.ResetTimer() @@ -126,6 +134,7 @@ func TestPolygonDegenerateQuads(t *testing.T) { t.Fatal(err) } m.size = fyne.NewSize(800, 500) + usePolyBackend(m) m.renderMode = RenderModeSolidWireframe for pitch := 0; pitch < 180; pitch += 15 { @@ -206,7 +215,7 @@ func TestPolygonRender(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { m := testGrid(t) - m.usePolygons = true + usePolyBackend(m) m.renderMode = tc.mode w := test.NewWindow(m) defer w.Close() diff --git a/pkg/widgets/meshgrid/meshgrid_shader.go b/pkg/widgets/meshgrid/meshgrid_shader.go new file mode 100644 index 00000000..79f0e7b9 --- /dev/null +++ b/pkg/widgets/meshgrid/meshgrid_shader.go @@ -0,0 +1,497 @@ +package meshgrid + +import ( + "fmt" + "image" + "image/color" + "math" + "sync/atomic" + + "fyne.io/fyne/v2/canvas" + "github.com/roffe/txlogger/pkg/colors" +) + +// GPU renderer: the whole mesh is drawn by a single canvas.Shader. The corner +// values live in a small data texture and the camera in a handful of float +// uniforms; the fragment shader reconstructs each pixel's orthographic view +// ray, walks the grid with a 2D DDA and intersects the two triangles of each +// visited cell. Rotating, zooming and panning therefore cost the CPU nothing +// but a uniform update - no projection, sorting or rasterization per frame - +// and a data edit re-uploads only the (cols+1)x(rows+1) texture. +// +// The grid-space conventions shared between the Go side and the GLSL below: +// - one grid unit = one cell = cellWidth (32) logical px before scaling +// - corner (vertex row i, col j) sits at (j, rows-i); +Y is the low-index +// data rows, exactly like the Oy = (vRows-i)*cellHeight CPU layout +// - corner height = z_off + z_gain * value16, reproducing +// Oz = (V - zmin)/zrange * depth in grid units +// - view = R * ((grid - center) * scale_px) - cam; the viewer sits at +Z +// looking along -Z, so the nearest surface has the largest view Z + +// shaderSeq makes each widget's Shader.Name unique: the painter caches both +// the compiled program and the bound textures per name, so two open map +// windows sharing a name would evict each other's data texture every frame. +var shaderSeq atomic.Int64 + +const meshShaderPreludeGL = "#version 110\n" + +const meshShaderPreludeES = `#version 100 +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +` + +const meshShaderBody = ` +#define MAX_STEPS 160 + +uniform vec2 frame_size; +uniform vec4 rect_coords; + +uniform sampler2D mesh_tex; // (cols+1)x(rows+1) corner values, 16 bit in RG +uniform sampler2D colormap_tex; // 256x1 value -> base color lookup + +// camera rotation R, row major; view = R * model +uniform float r0; +uniform float r1; +uniform float r2; +uniform float r3; +uniform float r4; +uniform float r5; +uniform float r6; +uniform float r7; +uniform float r8; + +uniform float grid_cols; // cells in X +uniform float grid_rows; // cells in Y +uniform float scale_px; // logical px per grid unit +uniform float height_units; // full value range height, grid units +uniform float z_off; // corner height = z_off + z_gain * value16 +uniform float z_gain; +uniform float center_gx; // mesh center, grid units +uniform float center_gy; +uniform float center_gz; +uniform float cam_x; // camera pan, logical px +uniform float cam_y; +uniform float size_w; // widget size, logical px +uniform float size_h; +uniform float view_zmin; // view-space depth extent of the mesh, logical px +uniform float view_zrange; +uniform float render_mode; // 0 solid+wire, 1 solid, 2 wireframe +uniform float light_x; // light direction in model space, unit length +uniform float light_y; +uniform float light_z; + +const float BIG = 100000.0; + +float corner_height(float gx, float gy) { + vec2 uv = vec2((gx + 0.5) / (grid_cols + 1.0), (gy + 0.5) / (grid_rows + 1.0)); + vec4 t = texture2D(mesh_tex, uv); + return z_off + z_gain * (t.r * 65280.0 + t.g * 255.0) / 65535.0; +} + +// narrow the line/AABB overlap [umin, umax] by one slab +void slab(float o, float d, float lo, float hi, inout float umin, inout float umax) { + if (abs(d) < 0.00000001) { + if (o < lo || o > hi) { + umin = BIG; + umax = -BIG; + } + return; + } + float t1 = (lo - o) / d; + float t2 = (hi - o) / d; + if (t1 > t2) { + float tmp = t1; + t1 = t2; + t2 = tmp; + } + umin = max(umin, t1); + umax = min(umax, t2); +} + +// Moeller-Trumbore; on a hit t is the ray parameter and bary the (a, b, c) weights +bool ray_tri(vec3 ro, vec3 rd, vec3 a, vec3 b, vec3 c, out float t, out vec3 bary) { + t = 0.0; + bary = vec3(0.0); + vec3 e1 = b - a; + vec3 e2 = c - a; + vec3 pv = cross(rd, e2); + float det = dot(e1, pv); + if (abs(det) < 0.0000000001) { + return false; + } + float inv_det = 1.0 / det; + vec3 tv = ro - a; + float u = dot(tv, pv) * inv_det; + if (u < -0.0001 || u > 1.0001) { + return false; + } + vec3 qv = cross(tv, e1); + float v = dot(rd, qv) * inv_det; + if (v < -0.0001 || u + v > 1.0001) { + return false; + } + t = dot(e2, qv) * inv_det; + bary = vec3(1.0 - u - v, u, v); + return true; +} + +// grid point -> (device px x, device px y, view-space z in logical px) +vec3 project_grid(mat3 rot, vec3 g, float pix_scale) { + vec3 v = rot * ((g - vec3(center_gx, center_gy, center_gz)) * scale_px) - vec3(cam_x, cam_y, 0.0); + return vec3((v.xy + 0.5 * vec2(size_w, size_h)) * pix_scale, v.z); +} + +// value color with the depth shading of getColorWithDepth; h in grid units +vec3 height_color(float h, float view_z) { + float val = clamp(h / height_units, 0.0, 1.0); + vec4 base = texture2D(colormap_tex, vec2(val * 0.99609375 + 0.001953125, 0.5)); + float df = clamp((view_z - view_zmin) / view_zrange, 0.0, 1.0); + vec3 rgb = base.rgb * (0.6 + 0.4 * df); + rgb.b = min(1.0, rgb.b + (1.0 - df) * 0.05882353); + if (base.r > 0.784 && base.g > 0.784 && base.b < 0.196) { + rgb.r = min(1.0, rgb.r * 1.1); + rgb.g = min(1.0, rgb.g * 1.1); + } + return rgb; +} + +// closest-point parameter of p on segment a-b +float seg_param(vec2 p, vec2 a, vec2 b) { + vec2 e = b - a; + float ee = dot(e, e); + if (ee < 0.000001) { + return 0.0; + } + return clamp(dot(p - a, e) / ee, 0.0, 1.0); +} + +// anti-aliased coverage of a line of the given half width at distance d +float line_mask(float d, float half_w) { + return 1.0 - smoothstep(half_w - 0.6, half_w + 0.6, d); +} + +// front-to-back "under" compositing of one wireframe segment +void wire_seg(vec2 p_dev, mat3 rot, float pix_scale, float half_w, vec3 a, vec3 b, float fade, inout vec3 acc, inout float acc_a) { + vec3 pa = project_grid(rot, a, pix_scale); + vec3 pb = project_grid(rot, b, pix_scale); + float h = seg_param(p_dev, pa.xy, pb.xy); + float d = distance(p_dev, mix(pa.xy, pb.xy, h)); + float mask = line_mask(d, half_w); + if (mask <= 0.0) { + return; + } + vec3 rgb = height_color(mix(a.z, b.z, h), mix(pa.z, pb.z, h)) * fade; + acc += (1.0 - acc_a) * mask * rgb; + acc_a += (1.0 - acc_a) * mask; +} + +// track the nearest cell border for the solid+wireframe grid lines +void edge_check(vec2 p_dev, vec3 pa, vec3 pb, float ha, float hb, inout float best_d, inout float best_h, inout float best_z) { + float t = seg_param(p_dev, pa.xy, pb.xy); + float d = distance(p_dev, mix(pa.xy, pb.xy, t)); + if (d < best_d) { + best_d = d; + best_h = mix(ha, hb, t); + best_z = mix(pa.z, pb.z, t); + } +} + +void main() { + mat3 rot = mat3(r0, r3, r6, r1, r4, r7, r2, r5, r8); + + float pix_scale = (rect_coords.y - rect_coords.x) / max(size_w, 1.0); + vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; + + // the painter expands the quad slightly for edge softness; stay inside + if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > rect_coords.y - rect_coords.x || p_dev.y > rect_coords.w - rect_coords.z) { + discard; + } + + vec2 view_xy = p_dev / pix_scale - 0.5 * vec2(size_w, size_h) + vec2(cam_x, cam_y); + + // pixel ray in grid space: g(u) = g0 + u * dg with u the view-space + // depth; g0 = transpose(R) * (view_xy, 0) / scale + center + vec3 g0 = vec3( + (r0 * view_xy.x + r3 * view_xy.y) / scale_px + center_gx, + (r1 * view_xy.x + r4 * view_xy.y) / scale_px + center_gy, + (r2 * view_xy.x + r5 * view_xy.y) / scale_px + center_gz); + vec3 dg = vec3(r6, r7, r8) / scale_px; + + float z_lo = min(z_off, z_off + z_gain) - 0.05; + float z_hi = max(z_off, z_off + z_gain) + 0.05; + + float umin = -BIG; + float umax = BIG; + slab(g0.x, dg.x, 0.0, grid_cols, umin, umax); + slab(g0.y, dg.y, 0.0, grid_rows, umin, umax); + slab(g0.z, dg.z, z_lo, z_hi, umin, umax); + if (umax <= umin) { + discard; + } + + // march from the near side (largest view z) toward the far side + vec3 ro = g0 + umax * dg; + vec3 rd = -dg; + float tend = umax - umin; + ro += rd * 0.0001; + + float cx = clamp(floor(ro.x), 0.0, grid_cols - 1.0); + float cy = clamp(floor(ro.y), 0.0, grid_rows - 1.0); + + float step_x = rd.x > 0.0 ? 1.0 : -1.0; + float step_y = rd.y > 0.0 ? 1.0 : -1.0; + float td_x = abs(rd.x) < 0.00000001 ? BIG : 1.0 / abs(rd.x); + float td_y = abs(rd.y) < 0.00000001 ? BIG : 1.0 / abs(rd.y); + float tm_x = abs(rd.x) < 0.00000001 ? BIG : (rd.x > 0.0 ? cx + 1.0 - ro.x : ro.x - cx) / abs(rd.x); + float tm_y = abs(rd.y) < 0.00000001 ? BIG : (rd.y > 0.0 ? cy + 1.0 - ro.y : ro.y - cy) / abs(rd.y); + + int mode = int(render_mode + 0.5); + float half_w = 0.5 * pix_scale; + vec3 light = vec3(light_x, light_y, light_z); + + vec3 acc = vec3(0.0); + float acc_a = 0.0; + + for (int i = 0; i < MAX_STEPS; i++) { + float h_bl = corner_height(cx, cy); + float h_br = corner_height(cx + 1.0, cy); + float h_tl = corner_height(cx, cy + 1.0); + float h_tr = corner_height(cx + 1.0, cy + 1.0); + + // cell corners; the fill splits along A-C like the CPU rasterizer + // while the wireframe diagonal runs B-D like the CPU line mesh + vec3 A = vec3(cx, cy + 1.0, h_tl); + vec3 B = vec3(cx + 1.0, cy + 1.0, h_tr); + vec3 C = vec3(cx + 1.0, cy, h_br); + vec3 D = vec3(cx, cy, h_bl); + + if (mode == 2) { + wire_seg(p_dev, rot, pix_scale, half_w, A, B, 1.0, acc, acc_a); + wire_seg(p_dev, rot, pix_scale, half_w, B, C, 1.0, acc, acc_a); + wire_seg(p_dev, rot, pix_scale, half_w, C, D, 1.0, acc, acc_a); + wire_seg(p_dev, rot, pix_scale, half_w, D, A, 1.0, acc, acc_a); + wire_seg(p_dev, rot, pix_scale, half_w, B, D, 0.7, acc, acc_a); + if (acc_a > 0.995) { + break; + } + } else { + float t1; + vec3 bc1; + float t2; + vec3 bc2; + bool hit1 = ray_tri(ro, rd, A, B, C, t1, bc1); + bool hit2 = ray_tri(ro, rd, A, C, D, t2, bc2); + float best_t = BIG; + float hit_h = 0.0; + if (hit1) { + best_t = t1; + hit_h = bc1.x * A.z + bc1.y * B.z + bc1.z * C.z; + } + if (hit2 && t2 < best_t) { + best_t = t2; + hit_h = bc2.x * A.z + bc2.y * C.z + bc2.z * D.z; + } + if (best_t < BIG) { + float view_z = umax - best_t; + vec3 rgb = height_color(hit_h, view_z); + + vec3 n = cross(C - A, D - B); + float nl = length(n); + if (nl > 0.0) { + rgb *= 0.6 + 0.4 * abs(dot(n / nl, light)); + } + + if (mode == 0) { + vec3 pa = project_grid(rot, A, pix_scale); + vec3 pb = project_grid(rot, B, pix_scale); + vec3 pc = project_grid(rot, C, pix_scale); + vec3 pd = project_grid(rot, D, pix_scale); + float best_d = BIG; + float line_h = 0.0; + float line_z = 0.0; + edge_check(p_dev, pa, pb, A.z, B.z, best_d, line_h, line_z); + edge_check(p_dev, pb, pc, B.z, C.z, best_d, line_h, line_z); + edge_check(p_dev, pc, pd, C.z, D.z, best_d, line_h, line_z); + edge_check(p_dev, pd, pa, D.z, A.z, best_d, line_h, line_z); + float lm = line_mask(best_d, half_w); + if (lm > 0.0) { + rgb = mix(rgb, height_color(line_h, line_z) * 0.45, lm); + } + } + + gl_FragColor = vec4(rgb, 1.0); + return; + } + } + + if (min(tm_x, tm_y) >= tend) { + break; + } + if (tm_x < tm_y) { + cx += step_x; + tm_x += td_x; + } else { + cy += step_y; + tm_y += td_y; + } + if (cx < -0.5 || cx > grid_cols - 0.5 || cy < -0.5 || cy > grid_rows - 0.5) { + break; + } + } + + if (mode == 2 && acc_a > 0.003) { + gl_FragColor = vec4(acc / acc_a, acc_a); + return; + } + discard; +} +` + +func (m *Meshgrid) initShader() { + m.shader = canvas.NewShader( + fmt.Sprintf("meshgrid-%d", shaderSeq.Add(1)), + []byte(meshShaderPreludeGL+meshShaderBody), + []byte(meshShaderPreludeES+meshShaderBody), + ) + m.shader.Textures = make(map[string]image.Image, 2) + m.shader.Uniforms = make(map[string]float32, 32) + m.updateShaderData() + m.updateShaderColormap() + m.updateShaderUniforms() +} + +// updateShaderData re-encodes the corner values into the mesh data texture +// and the height-mapping uniforms. A fresh image is allocated on purpose: +// the painter re-uploads a texture only when the map entry points at a new +// image. +func (m *Meshgrid) updateShaderData() { + if m.shader == nil { + return + } + vRows, vCols := m.rows+1, m.cols+1 + + // Values are normalized against the actual data extent so the 16-bit + // quantization keeps full resolution even when zmin/zmax (set by + // LoadFloat64s) span a wider range than the data. + dataMin, dataMax := math.Inf(1), math.Inf(-1) + for i := range m.vertices { + row := m.vertices[i] + for j := range row { + v := row[j].V + if v < dataMin { + dataMin = v + } + if v > dataMax { + dataMax = v + } + } + } + dataRange := dataMax - dataMin + + img := image.NewRGBA(image.Rect(0, 0, vCols, vRows)) + for i := 0; i < vRows; i++ { + row := m.vertices[i] + for j := 0; j < vCols; j++ { + norm := 0.0 + if dataRange > 0 { + norm = (row[j].V - dataMin) / dataRange + } + q := uint16(norm*65535 + 0.5) + // vertex row i sits at grid Y rows-i (data row 0 is the far + // edge), which is also its texture row + img.SetRGBA(j, m.rows-i, color.RGBA{R: uint8(q >> 8), G: uint8(q), A: 0xff}) + } + } + m.shader.Textures["mesh_tex"] = img + + // Heights in grid units: z_off + z_gain*norm reproduces the CPU + // Oz = (V - zmin)/zrange * depth, in units of one cell. + zr := m.zrange + if zr == 0 { + zr = 1 + } + hmax := m.depth / float64(m.cellWidth) + m.shader.Uniforms["z_off"] = float32((dataMin - m.zmin) / zr * hmax) + m.shader.Uniforms["z_gain"] = float32(dataRange / zr * hmax) +} + +// updateShaderColormap rebuilds the 256x1 value-to-color lookup texture. +func (m *Meshgrid) updateShaderColormap() { + if m.shader == nil { + return + } + lut := image.NewRGBA(image.Rect(0, 0, 256, 1)) + for k := 0; k < 256; k++ { + var c color.RGBA + if m.zrange == 0 { + // GetColorInterpolation resolves 0/0 to gray; match it + c = color.RGBA{R: 128, G: 128, B: 128, A: 255} + } else { + c = colors.GetColorInterpolation(0, 255, float64(k), m.colorMode) + } + lut.SetRGBA(k, 0, c) + } + m.shader.Textures["colormap_tex"] = lut +} + +// updateShaderUniforms pushes the camera and shading state; this is the whole +// per-frame CPU cost of the shader backend. +func (m *Meshgrid) updateShaderUniforms() { + if m.shader == nil { + return + } + + // View-space depth extent of the transformed mesh, for the same + // depth-shading ramp the CPU rasterizer applies. + minZ, maxZ := math.Inf(1), math.Inf(-1) + for i := range m.vertices { + row := m.vertices[i] + for j := range row { + z := row[j].Z + if z < minZ { + minZ = z + } + if z > maxZ { + maxZ = z + } + } + } + zRange := maxZ - minZ + if zRange == 0 { + zRange = 1 + } + + // Fixed view-space light from drawSurface, moved to model space so the + // shader can shade with grid-space normals directly. + lx, ly, lz := 0.3, -0.5, 0.8 + il := 1 / math.Sqrt(lx*lx+ly*ly+lz*lz) + lx, ly, lz = lx*il, ly*il, lz*il + + r := m.cameraRotation + cw := float64(m.cellWidth) + + u := m.shader.Uniforms + u["r0"], u["r1"], u["r2"] = float32(r[0][0]), float32(r[0][1]), float32(r[0][2]) + u["r3"], u["r4"], u["r5"] = float32(r[1][0]), float32(r[1][1]), float32(r[1][2]) + u["r6"], u["r7"], u["r8"] = float32(r[2][0]), float32(r[2][1]), float32(r[2][2]) + u["grid_cols"] = float32(m.cols) + u["grid_rows"] = float32(m.rows) + u["scale_px"] = float32(cw * m.scale) + u["height_units"] = float32(m.depth / cw) + u["center_gx"] = float32(m.centerX / cw) + u["center_gy"] = float32(m.centerY/cw - 1) + u["center_gz"] = float32(m.centerZ / cw) + u["cam_x"] = float32(m.cameraPosition[0]) + u["cam_y"] = float32(m.cameraPosition[1]) + u["size_w"] = float32(m.size.Width) + u["size_h"] = float32(m.size.Height) + u["view_zmin"] = float32(minZ) + u["view_zrange"] = float32(zRange) + u["render_mode"] = float32(m.renderMode) + u["light_x"] = float32(r[0][0]*lx + r[1][0]*ly + r[2][0]*lz) + u["light_y"] = float32(r[0][1]*lx + r[1][1]*ly + r[2][1]*lz) + u["light_z"] = float32(r[0][2]*lx + r[1][2]*ly + r[2][2]*lz) +} diff --git a/pkg/widgets/meshgrid/meshgrid_shader_test.go b/pkg/widgets/meshgrid/meshgrid_shader_test.go new file mode 100644 index 00000000..493472fd --- /dev/null +++ b/pkg/widgets/meshgrid/meshgrid_shader_test.go @@ -0,0 +1,140 @@ +package meshgrid + +import ( + "image" + "math" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/roffe/txlogger/pkg/colors" +) + +// The mesh data texture plus z_off/z_gain must reproduce every corner height +// (Oz in grid units) and the colormap index the CPU renderer would use. +func TestShaderDataEncoding(t *testing.T) { + m := testGrid(t) + tex, ok := m.shader.Textures["mesh_tex"].(*image.RGBA) + if !ok { + t.Fatal("mesh_tex missing or not RGBA") + } + if tex.Bounds().Dx() != m.cols+1 || tex.Bounds().Dy() != m.rows+1 { + t.Fatalf("mesh_tex is %v, want %dx%d", tex.Bounds(), m.cols+1, m.rows+1) + } + + zOff := float64(m.shader.Uniforms["z_off"]) + zGain := float64(m.shader.Uniforms["z_gain"]) + cw := float64(m.cellWidth) + + for i := 0; i <= m.rows; i++ { + for j := 0; j <= m.cols; j++ { + // vertex row i lives at texture row rows-i (grid Y up) + c := tex.RGBAAt(j, m.rows-i) + norm := (float64(c.R)*256 + float64(c.G)) / 65535 + gotH := zOff + zGain*norm + wantH := m.vertices[i][j].Oz / cw + // 16-bit quantization of a ~12.5 unit range + if math.Abs(gotH-wantH) > zGain/65535+1e-6 { + t.Fatalf("corner (%d,%d): height %v, want %v", i, j, gotH, wantH) + } + } + } +} + +func TestShaderColormap(t *testing.T) { + m := testGrid(t) + lut, ok := m.shader.Textures["colormap_tex"].(*image.RGBA) + if !ok { + t.Fatal("colormap_tex missing or not RGBA") + } + if lut.Bounds().Dx() != 256 || lut.Bounds().Dy() != 1 { + t.Fatalf("colormap_tex is %v, want 256x1", lut.Bounds()) + } + for k := 0; k < 256; k++ { + want := colors.GetColorInterpolation(0, 255, float64(k), m.colorMode) + if got := lut.RGBAAt(k, 0); got != want { + t.Fatalf("lut[%d] = %v, want %v", k, got, want) + } + } +} + +// The shader projects grid points with +// +// view = R*((g - center)*scale_px) - cam; screen = view.xy + size/2 +// +// which must land on the same screen position as the CPU-transformed +// vertices, for any camera. Otherwise the mesh and the cursor/axis overlays +// drift apart. +func TestShaderProjectionMatchesVertices(t *testing.T) { + m := testGrid(t) + m.orbit(23, -41) + m.panMeshgrid(15, -7) + m.scaleMeshgrid(1.3) + m.updateShaderUniforms() + + u := m.shader.Uniforms + r := [3][3]float64{ + {float64(u["r0"]), float64(u["r1"]), float64(u["r2"])}, + {float64(u["r3"]), float64(u["r4"]), float64(u["r5"])}, + {float64(u["r6"]), float64(u["r7"]), float64(u["r8"])}, + } + cg := [3]float64{float64(u["center_gx"]), float64(u["center_gy"]), float64(u["center_gz"])} + scalePx := float64(u["scale_px"]) + cw := float64(m.cellWidth) + + for i := 0; i <= m.rows; i += 4 { + for j := 0; j <= m.cols; j += 4 { + v := m.vertices[i][j] + g := [3]float64{float64(j), float64(m.rows - i), v.Oz / cw} + + var view [3]float64 + for a := 0; a < 3; a++ { + view[a] = r[a][0]*(g[0]-cg[0])*scalePx + + r[a][1]*(g[1]-cg[1])*scalePx + + r[a][2]*(g[2]-cg[2])*scalePx + } + view[0] -= float64(u["cam_x"]) + view[1] -= float64(u["cam_y"]) + + if math.Abs(view[0]-v.X) > 1e-3 || math.Abs(view[1]-v.Y) > 1e-3 || math.Abs(view[2]-v.Z) > 1e-3 { + t.Fatalf("vertex (%d,%d): shader view (%v,%v,%v), CPU view (%v,%v,%v)", + i, j, view[0], view[1], view[2], v.X, v.Y, v.Z) + } + } + } +} + +// CPU-side cost of a shader-backend frame: this plus the axis/cursor overlay +// moves is everything the CPU does per rotation/zoom frame. Compare against +// BenchmarkDrawSurface (image backend) and BenchmarkUpdatePolygons. +func BenchmarkUpdateShaderUniforms(b *testing.B) { + m := testGrid(b) + m.renderMode = RenderModeSolidWireframe + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + m.updateShaderUniforms() + } +} + +// Both shader variants must at least compile; glslangValidator checks them +// against the GLSL 1.10 (desktop) and GLSL ES 1.00 specs. +func TestShaderSourcesCompile(t *testing.T) { + validator, err := exec.LookPath("glslangValidator") + if err != nil { + t.Skip("glslangValidator not installed") + } + for name, src := range map[string]string{ + "desktop.frag": meshShaderPreludeGL + meshShaderBody, + "es.frag": meshShaderPreludeES + meshShaderBody, + } { + p := filepath.Join(t.TempDir(), name) + if err := os.WriteFile(p, []byte(src), 0o644); err != nil { + t.Fatal(err) + } + if out, err := exec.Command(validator, p).CombinedOutput(); err != nil { + t.Fatalf("%s: %v\n%s", name, err, out) + } + } +} diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index 6276c1e1..2fc4a904 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -26,6 +26,33 @@ type Vertex struct { var _ fyne.Widget = (*Meshgrid)(nil) +// renderBackend selects how the mesh is drawn. The GPU shader is the +// default; TXLOGGER_MESH_RENDERER=poly|image selects the older paths. +type renderBackend int + +const ( + // backendShader ray-casts the whole mesh in one fragment shader + // (meshgrid_shader.go); per-frame CPU cost is a uniform update. + backendShader renderBackend = iota + // backendPolygons draws one canvas.ArbitraryPolygon per cell + // (meshgrid_poly.go). + backendPolygons + // backendImage rasterizes on the CPU into a canvas.Image + // (meshgrid_draw.go). + backendImage +) + +func backendFromEnv() renderBackend { + switch os.Getenv("TXLOGGER_MESH_RENDERER") { + case "poly": + return backendPolygons + case "image": + return backendImage + default: + return backendShader + } +} + type Meshgrid struct { widget.BaseWidget @@ -48,16 +75,23 @@ type Meshgrid struct { scratchLines []lineSegment scratchQuads []quadRef - // Polygon-renderer experiment (see meshgrid_poly.go): one reusable + backend renderBackend + + // Shader backend (meshgrid_shader.go): the whole mesh in one object. + shader *canvas.Shader + + // Polygon backend (meshgrid_poly.go): one reusable // canvas.ArbitraryPolygon per cell instead of rasterizing into an image. - usePolygons bool polys []*canvas.ArbitraryPolygon polyObjects []fyne.CanvasObject - axisLines [3]*canvas.Line - axisLabels [3]*canvas.Text scratchFX []float32 scratchFY []float32 + // Axis-indicator overlay shared by the shader and polygon backends (the + // image backend draws its own into the raster). + axisLines [3]*canvas.Line + axisLabels [3]*canvas.Text + renderMode RenderMode lastMouseX, lastMouseY float32 @@ -138,9 +172,7 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int colorMode: colorBlindMode, - // Polygon-renderer experiment toggle: set TXLOGGER_MESH_POLY=0 to - // fall back to the image rasterizer for comparison. - usePolygons: os.Getenv("TXLOGGER_MESH_POLY") != "0", + backend: backendFromEnv(), } m.createVertices() @@ -175,7 +207,13 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int Hidden: true, } - m.initPolygons() + m.initAxisObjects() + switch m.backend { + case backendShader: + m.initShader() + case backendPolygons: + m.initPolygons() + } return m, nil } @@ -202,6 +240,7 @@ func (m *Meshgrid) CycleRenderMode() { func (m *Meshgrid) SetColorBlindMode(mode colors.ColorBlindMode) { if m.colorMode != mode { m.colorMode = mode + m.updateShaderColormap() } m.refresh() } @@ -345,6 +384,7 @@ func (m *Meshgrid) SetFloat64(idx int, value float64) { m.zmin, m.zmax, m.zrange = findMinMaxRange(m.values) m.createVertices() m.updateVertexPositions() + m.updateShaderData() m.refresh() } @@ -362,6 +402,7 @@ func (m *Meshgrid) LoadFloat64s(min, max float64, floats []float64) { m.createVertices() m.updateVertexPositions() + m.updateShaderData() m.refresh() } @@ -421,20 +462,28 @@ func (m *Meshgrid) Refresh() { } func (m *Meshgrid) refresh() { - if m.usePolygons { + switch m.backend { + case backendShader: + // All per-frame state lives in shader uniforms; the GPU re-renders + // from them on the next paint. Only the overlays move on the CPU. + m.updateShaderUniforms() + m.updateAxisObjects() + m.moveCursor() + canvas.Refresh(m) + case backendPolygons: // Geometry/color updates on the reusable polygons; the canvas.Refresh // marks the scene dirty so color-only changes repaint too. m.updatePolygons() m.moveCursor() canvas.Refresh(m) - return + default: + m.image.Image = m.drawMeshgridLines() + m.image.Resize(m.size) + m.image.Refresh() + // Rotation, scale, resize and data changes all move the projected + // surface point under the marker. + m.moveCursor() } - m.image.Image = m.drawMeshgridLines() - m.image.Resize(m.size) - m.image.Refresh() - // Rotation, scale, resize and data changes all move the projected - // surface point under the marker. - m.moveCursor() } func (m *Meshgrid) throttledRefresh() { @@ -453,7 +502,7 @@ func (m *Meshgrid) throttledRefresh() { } func (m *Meshgrid) CreateRenderer() fyne.WidgetRenderer { - if m.usePolygons { + if m.backend != backendImage { // Text measuring needs a driver, so the labels can't be sized in the // constructor (tests build widgets without an app). for _, t := range m.axisLabels { @@ -473,7 +522,12 @@ func (m *meshgridRenderer) Layout(size fyne.Size) { return } m.MG.size = size - m.MG.image.Resize(size) + switch m.MG.backend { + case backendShader: + m.MG.shader.Resize(size) + case backendImage: + m.MG.image.Resize(size) + } m.MG.throttledRefresh() } @@ -489,16 +543,28 @@ func (m *meshgridRenderer) Destroy() { } func (m *meshgridRenderer) Objects() []fyne.CanvasObject { - if m.MG.usePolygons { + switch m.MG.backend { + case backendShader: + if m.objects == nil { + m.MG.updateAxisObjects() + objs := []fyne.CanvasObject{m.MG.shader} + for i := range m.MG.axisLines { + objs = append(objs, m.MG.axisLines[i], m.MG.axisLabels[i]) + } + m.objects = append(objs, m.MG.cursor) + } + return m.objects + case backendPolygons: // updatePolygons rebuilds the list in painter's order every frame; // populate it here for the first paint. if len(m.MG.polyObjects) == 0 { m.MG.updatePolygons() } return m.MG.polyObjects + default: + if m.objects == nil { + m.objects = []fyne.CanvasObject{m.MG.image, m.MG.cursor} + } + return m.objects } - if m.objects == nil { - m.objects = []fyne.CanvasObject{m.MG.image, m.MG.cursor} - } - return m.objects } From 44243f70a990fdb844e449067242277650a65e41 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 22:29:16 +0200 Subject: [PATCH 028/102] resolve issue with wbledit object cache --- pkg/widgets/settings/wbleditor.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/widgets/settings/wbleditor.go b/pkg/widgets/settings/wbleditor.go index 78a25a8d..ddcf3f31 100644 --- a/pkg/widgets/settings/wbleditor.go +++ b/pkg/widgets/settings/wbleditor.go @@ -555,6 +555,9 @@ func (r *graphRenderer) rebuild() { for i, row := range rows { r.points[i].row = row } + + // dataLines/points may have changed length; force Objects() to rebuild. + r.objects = nil } func (r *graphRenderer) Layout(size fyne.Size) { From 9f78467fd2673240b8a8c36882206652f30029d7 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 22:30:25 +0200 Subject: [PATCH 029/102] remove unused --- pkg/datalogger/log.go | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/pkg/datalogger/log.go b/pkg/datalogger/log.go index d1fc4d00..5efd88cd 100644 --- a/pkg/datalogger/log.go +++ b/pkg/datalogger/log.go @@ -36,7 +36,7 @@ func NewWriter(cfg Config) (string, LogWriter, error) { func createLog(path, prefix, extension string) (*os.File, string, error) { if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.Mkdir(path, 0755); err != nil { + if err := os.Mkdir(path, 0o755); err != nil { if err != os.ErrExist { return nil, "", fmt.Errorf("failed to create logs dir: %w", err) } @@ -48,7 +48,7 @@ func createLog(path, prefix, extension string) (*os.File, string, error) { fullFilename := filepath.Join(path, filename) - file, err := os.OpenFile(fullFilename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + file, err := os.OpenFile(fullFilename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666) if err != nil { return nil, "", fmt.Errorf("failed to open file: %w", err) } @@ -58,17 +58,3 @@ func createLog(path, prefix, extension string) (*os.File, string, error) { func replaceDot(s string) string { return strings.Replace(s, ".", ",", 1) } - -type TXBinWriter struct { - file *os.File -} - -func NewTXBinWriter(f *os.File) *TXBinWriter { - return &TXBinWriter{ - file: f, - } -} - -func (t *TXBinWriter) Write(ts time.Time, channels []Channel) error { - return nil -} From 2cef514be1867502e62118462496dd792996239f Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 22:33:40 +0200 Subject: [PATCH 030/102] update ecusymbol --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e208d982..d90b439f 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 - github.com/roffe/ecusymbol v1.1.8 + github.com/roffe/ecusymbol v1.1.9 github.com/roffe/gocan v1.4.0 go.bug.st/serial v1.6.4 golang.org/x/image v0.40.0 diff --git a/go.sum b/go.sum index b9f4f322..af30dca5 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/roffe/ecusymbol v1.1.8 h1:c145IldWHTnzy4y2cInpHRDYYvehnqFxsKQZ2YPyzNw= -github.com/roffe/ecusymbol v1.1.8/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= +github.com/roffe/ecusymbol v1.1.9 h1:muLJ5ihTK2LTWHrbfB/Dlk3pDAh5d0/UHKX2PE60fcE= +github.com/roffe/ecusymbol v1.1.9/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= github.com/roffe/gocan v1.4.0 h1:OSs//lr4vy/ozyMPUbgZaNFVZWMeXzOsXhCujpA4WRs= github.com/roffe/gocan v1.4.0/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= From aadb98456acc6b6f3d6c8cef0d345108a0ea3167 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 12 Jun 2026 23:25:10 +0200 Subject: [PATCH 031/102] use opengl shader to draw log plotter --- pkg/widgets/plotter/plotter.go | 48 ++- pkg/widgets/plotter/plotter_mouse.go | 2 +- pkg/widgets/plotter/plotter_render.go | 2 +- pkg/widgets/plotter/plotter_shader.go | 356 +++++++++++++++++++++ pkg/widgets/plotter/plotter_shader_test.go | 175 ++++++++++ 5 files changed, 579 insertions(+), 4 deletions(-) create mode 100644 pkg/widgets/plotter/plotter_shader.go create mode 100644 pkg/widgets/plotter/plotter_shader_test.go diff --git a/pkg/widgets/plotter/plotter.go b/pkg/widgets/plotter/plotter.go index a25c92e8..baed1c01 100644 --- a/pkg/widgets/plotter/plotter.go +++ b/pkg/widgets/plotter/plotter.go @@ -5,6 +5,7 @@ import ( "image" "image/color" "log" + "os" "sort" "sync" "sync/atomic" @@ -29,9 +30,25 @@ type PlotterControl interface { Seek(int) } +// plotBackend selects how the plot is drawn. The GPU shader is the default; +// TXLOGGER_PLOT_RENDERER=image selects the CPU rasterizer, which is also the +// automatic fallback when the log does not fit the shader's texture layout. +type plotBackend int + +const ( + plotBackendImage plotBackend = iota + plotBackendShader +) + type Plotter struct { widget.BaseWidget + backend plotBackend + shader *canvas.Shader + // plotObj is the canvas object showing the plot: shader on the GPU + // backend, canvasImage on the image backend. + plotObj fyne.CanvasObject + cursor *canvas.Line canvasImage *canvas.Image split *container.Split @@ -182,12 +199,18 @@ func NewPlotter(values map[string][]float64, opts ...PlotterOpt) *Plotter { p.dataPointsToShow = min(p.dataLength, 250.0) + p.plotObj = p.canvasImage + if os.Getenv("TXLOGGER_PLOT_RENDERER") != "image" && p.initShader() { + p.backend = plotBackendShader + p.plotObj = p.shader + } + leading := container.NewBorder( nil, nil, p.zoom, nil, - container.New(&plotLayout{p: p}, p.canvasImage), + container.New(&plotLayout{p: p}, p.plotObj), ) p.split = container.NewHSplit( leading, @@ -294,6 +317,14 @@ func (p *Plotter) seekTo(pos int) { obj.Refresh() } + if p.backend == plotBackendShader { + // The view window moved; the GPU re-renders from two uniforms. + p.updateShaderView() + p.layoutCursor() + p.cursor.Refresh() + p.shader.Refresh() + return + } p.drawImage() p.layoutCursor() p.cursor.Refresh() @@ -337,6 +368,19 @@ func (p *Plotter) drawImage() { } func (p *Plotter) refreshImage(goroutine bool) { + if p.backend == plotBackendShader { + // Legend toggles/recolors, hover and zoom all funnel through here; + // the metadata texture is 4xN so rebuilding it unconditionally is + // cheap, and the painter uploads it only because it is a new image. + p.updateShaderMeta() + p.updateShaderView() + if goroutine { + fyne.Do(p.shader.Refresh) + } else { + p.shader.Refresh() + } + return + } p.drawImage() if goroutine { fyne.Do(p.canvasImage.Refresh) @@ -492,7 +536,7 @@ func (ts *TimeSeries) plotImageDecimated(img *image.RGBA, data []float64, thickn func (p *Plotter) layoutCursor() { var x float32 halfDataPointsToShow := int(float64(p.dataPointsToShow) * .5) - plotSize := p.canvasImage.Size() + plotSize := p.plotObj.Size() if p.cursorPos >= p.dataLength-halfDataPointsToShow { // Handle cursor position near the end of data diff --git a/pkg/widgets/plotter/plotter_mouse.go b/pkg/widgets/plotter/plotter_mouse.go index 89562091..6ab4c5d3 100644 --- a/pkg/widgets/plotter/plotter_mouse.go +++ b/pkg/widgets/plotter/plotter_mouse.go @@ -58,7 +58,7 @@ func (p *Plotter) onZoom(value float64) { if p.plotStartPos < 0 { p.plotStartPos = 0 } - p.widthFactor = p.canvasImage.Size().Width / float32(p.dataPointsToShow) + p.widthFactor = p.plotObj.Size().Width / float32(p.dataPointsToShow) p.updateCursor(false) p.refreshImage(false) } diff --git a/pkg/widgets/plotter/plotter_render.go b/pkg/widgets/plotter/plotter_render.go index a228b12e..825932c4 100644 --- a/pkg/widgets/plotter/plotter_render.go +++ b/pkg/widgets/plotter/plotter_render.go @@ -52,7 +52,7 @@ func (t *plotLayout) Layout(_ []fyne.CanvasObject, plotSize fyne.Size) { t.p.overlayText.Move(fyne.NewPos(t.p.zoom.Size().Width, 20)) - t.p.canvasImage.Resize(plotSize) // Calculate new plot dimensions + t.p.plotObj.Resize(plotSize) // Calculate new plot dimensions t.p.plotResolution = fyne.NewSize(plotSize.Width*t.p.plotResolutionFactor, plotSize.Height*t.p.plotResolutionFactor) // Update width factor based on the new size t.p.widthFactor = plotSize.Width / float32(t.p.dataPointsToShow) diff --git a/pkg/widgets/plotter/plotter_shader.go b/pkg/widgets/plotter/plotter_shader.go new file mode 100644 index 00000000..de4ef813 --- /dev/null +++ b/pkg/widgets/plotter/plotter_shader.go @@ -0,0 +1,356 @@ +package plotter + +import ( + "fmt" + "image" + "image/color" + "sync/atomic" + + "fyne.io/fyne/v2/canvas" +) + +// GPU renderer: the whole plot is drawn by a single canvas.Shader. The log is +// immutable once loaded, so every sample is uploaded to the GPU exactly once +// (16-bit packed, one texel per sample) together with a small min/max +// decimation level; after that a playback seek, zoom or drag only updates a +// couple of float uniforms and the fragment shader re-renders the view. The +// CPU never rasterizes a plot frame again - compare drawImage, which redraws +// and re-uploads the full plot image on every coalesced Seek. +// +// Per pixel the shader walks the enabled series; when zoomed in it computes +// the anti-aliased distance to the polyline segments crossing the pixel's +// column, when zoomed out it min/maxes the samples covering the column (via +// the 16:1 min/max texture when even that is too many fetches) and draws the +// vertical run, mirroring plotImageDecimated. Series are combined with the +// same order-independent max-blend as bresenhamCore. +// +// Sample encoding: values are normalized to the series' display range +// [Min, Max] with one full range of headroom on each side - texel 0..1 spans +// [Min-range, Max+range] - so overshooting samples keep their slope off +// screen like the Bresenham clipping does instead of flattening at the plot +// edge. 16 bits give the on-screen third ~21800 steps, far below a pixel. + +// plotShaderSeq makes each widget's Shader.Name unique: the painter caches +// the compiled program and its textures per name, so two plotters sharing a +// name would evict each other's data textures every frame. +var plotShaderSeq atomic.Int64 + +const ( + // plotTexW caps the data-texture width; sample index s of series i lives + // at texel (s mod plotTexW, i*rowsPerSeries + s/plotTexW). + plotTexW = 4096 + // plotTexMaxH bounds texture heights to what every desktop GPU handles. + plotTexMaxH = 4096 + // plotMaxSeries matches MAX_SERIES in the shader source. + plotMaxSeries = 64 + // mmGroup is the min/max decimation factor, matching the shader's + // group-16 lookups. + mmGroup = 16 +) + +const plotShaderPreludeGL = "#version 110\n" + +const plotShaderPreludeES = `#version 100 +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +` + +const plotShaderBody = ` +#define MAX_SERIES 64 +#define MAX_SEG 4 +#define MAX_RAW 16 +#define MAX_GROUPS 24 + +uniform vec2 frame_size; +uniform vec4 rect_coords; + +uniform sampler2D data_tex; // one texel per sample, 16-bit value in RG +uniform sampler2D mm_tex; // min/max per 16 samples: RG=min, BA=max +uniform sampler2D meta_tex; // per series: x0 color, x1 enabled, x2 length + +uniform float series_count; +uniform float highlight; // hovered series index, -1 for none +uniform float plot_start; // first visible sample +uniform float points_shown; // visible sample count +uniform float tex_w; // texel columns in data_tex and mm_tex +uniform float rows_raw; // data_tex rows per series +uniform float rows_mm; // mm_tex rows per series +uniform float data_h; // data_tex height in rows +uniform float mm_h; // mm_tex height in rows +uniform float meta_h; // meta_tex height = series count +uniform float size_w; // widget size, logical px +uniform float size_h; + +const float BIG = 100000.0; + +float decode16(float hi, float lo) { + return (hi * 65280.0 + lo * 255.0) / 65535.0; +} + +vec4 meta_at(float x, float si) { + return texture2D(meta_tex, vec2((x + 0.5) / 4.0, (si + 0.5) / meta_h)); +} + +// normalized sample value; idx is clamped to the series +float sample_val(float si, float idx, float len) { + idx = clamp(idx, 0.0, len - 1.0); + float row = floor(idx / tex_w); + float colm = idx - row * tex_w; + vec2 uv = vec2((colm + 0.5) / tex_w, (si * rows_raw + row + 0.5) / data_h); + vec4 t = texture2D(data_tex, uv); + return decode16(t.r, t.g); +} + +// normalized (min, max) of sample group gidx +vec2 sample_mm(float si, float gidx, float glen) { + gidx = clamp(gidx, 0.0, glen - 1.0); + float row = floor(gidx / tex_w); + float colm = gidx - row * tex_w; + vec2 uv = vec2((colm + 0.5) / tex_w, (si * rows_mm + row + 0.5) / mm_h); + vec4 t = texture2D(mm_tex, uv); + return vec2(decode16(t.r, t.g), decode16(t.b, t.a)); +} + +// device y of a normalized value: texel range 0..1 spans one display range +// of headroom on each side of [Min, Max] +float val_y(float v, float h_dev) { + float frac = v * 3.0 - 1.0; + return (1.0 - frac) * (h_dev - 1.0); +} + +float seg_dist(vec2 p, vec2 a, vec2 b) { + vec2 e = b - a; + float ee = dot(e, e); + float h = ee > 0.000001 ? clamp(dot(p - a, e) / ee, 0.0, 1.0) : 0.0; + return distance(p, a + e * h); +} + +void main() { + float pix_scale = (rect_coords.y - rect_coords.x) / max(size_w, 1.0); + vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; + float w_dev = rect_coords.y - rect_coords.x; + float h_dev = rect_coords.w - rect_coords.z; + + // the painter expands the quad slightly for edge softness; stay inside + if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > w_dev || p_dev.y > h_dev) { + discard; + } + + float ppd = points_shown / max(w_dev, 1.0); // samples per device px + float spos = plot_start + p_dev.x / w_dev * points_shown; // sample at this px + float aa = 0.6; + + vec3 acc = vec3(0.0); + float acc_a = 0.0; + + for (int i = 0; i < MAX_SERIES; i++) { + if (i >= int(series_count + 0.5)) { + break; + } + float si = float(i); + if (meta_at(1.0, si).r < 0.5) { + continue; // disabled via the legend + } + vec4 m2 = meta_at(2.0, si); + float len = m2.r * 16711680.0 + m2.g * 65280.0 + m2.b * 255.0; + if (len < 2.0) { + continue; + } + // hovered series renders at 4 logical px like PlotImage thickness 4 + float half_w = (abs(si - highlight) < 0.5 ? 2.0 : 0.5) * pix_scale; + + float mask = 0.0; + if (ppd <= 1.5) { + // zoomed in: true polyline, distance to the segments around + // this pixel's column + float i0 = floor(spos); + float dmin = BIG; + for (int k = -MAX_SEG; k < MAX_SEG; k++) { + float j = i0 + float(k); + float x0 = (j - plot_start) / points_shown * w_dev; + float x1 = (j + 1.0 - plot_start) / points_shown * w_dev; + float y0 = val_y(sample_val(si, j, len), h_dev); + float y1 = val_y(sample_val(si, j + 1.0, len), h_dev); + dmin = min(dmin, seg_dist(p_dev, vec2(x0, y0), vec2(x1, y1))); + } + mask = 1.0 - smoothstep(half_w - aa, half_w + aa, dmin); + } else { + // zoomed out: vertical min/max run per column, like + // plotImageDecimated (including the one-sample overlap into the + // previous column that keeps runs connected) + float s_a = spos - ppd * 0.5 - 1.0; + float s_b = spos + ppd * 0.5; + float lo = BIG; + float hi = -BIG; + if (ppd <= 14.0) { + for (int k = 0; k < MAX_RAW; k++) { + float idx = s_a + float(k); + if (idx > s_b) { + break; + } + float v = sample_val(si, idx, len); + lo = min(lo, v); + hi = max(hi, v); + } + } else { + float g0 = floor(s_a / 16.0); + float glen = ceil(len / 16.0); + for (int k = 0; k < MAX_GROUPS; k++) { + float g = g0 + float(k); + if (g * 16.0 > s_b) { + break; + } + vec2 mm = sample_mm(si, g, glen); + lo = min(lo, mm.x); + hi = max(hi, mm.y); + } + } + float d = max(val_y(hi, h_dev) - p_dev.y, p_dev.y - val_y(lo, h_dev)); + mask = 1.0 - smoothstep(half_w - aa, half_w + aa, d); + } + + // max blend, same as bresenhamCore, so overlap is order independent + vec4 col = meta_at(0.0, si); + acc = max(acc, col.rgb * mask * col.a); + acc_a = max(acc_a, mask * col.a); + } + + if (acc_a < 0.004) { + discard; + } + gl_FragColor = vec4(acc / acc_a, acc_a); +} +` + +// initShader builds the shader object and uploads the immutable log data. +// It reports false when the data does not fit the GPU layout (no series, too +// many series, or a log too long for the texture budget); the caller then +// stays on the image backend. +func (p *Plotter) initShader() bool { + if len(p.ts) == 0 || len(p.ts) > plotMaxSeries { + return false + } + + maxLen := 0 + for _, ts := range p.ts { + if ts == nil { + return false + } + maxLen = max(maxLen, len(p.values[ts.Name])) + } + if maxLen < 2 { + return false + } + + texW := min(maxLen, plotTexW) + rowsRaw := (maxLen + texW - 1) / texW + groups := (maxLen + mmGroup - 1) / mmGroup + rowsMM := (groups + texW - 1) / texW + if len(p.ts)*rowsRaw > plotTexMaxH || len(p.ts)*rowsMM > plotTexMaxH { + return false + } + + p.shader = canvas.NewShader( + fmt.Sprintf("plotter-%d", plotShaderSeq.Add(1)), + []byte(plotShaderPreludeGL+plotShaderBody), + []byte(plotShaderPreludeES+plotShaderBody), + ) + p.shader.Textures = make(map[string]image.Image, 3) + p.shader.Uniforms = make(map[string]float32, 16) + + data := image.NewRGBA(image.Rect(0, 0, texW, len(p.ts)*rowsRaw)) + mm := image.NewRGBA(image.Rect(0, 0, texW, len(p.ts)*rowsMM)) + for i, ts := range p.ts { + encodeSeries(data, mm, texW, i*rowsRaw, i*rowsMM, ts, p.values[ts.Name]) + } + p.shader.Textures["data_tex"] = data + p.shader.Textures["mm_tex"] = mm + + u := p.shader.Uniforms + u["series_count"] = float32(len(p.ts)) + u["tex_w"] = float32(texW) + u["rows_raw"] = float32(rowsRaw) + u["rows_mm"] = float32(rowsMM) + u["data_h"] = float32(len(p.ts) * rowsRaw) + u["mm_h"] = float32(len(p.ts) * rowsMM) + u["meta_h"] = float32(len(p.ts)) + + p.updateShaderMeta() + p.updateShaderView() + return true +} + +// encodeValue maps a sample to the 16-bit texel value: 0..1 spans +// [Min-range, Max+range] so overshoot keeps its slope (clamped a full plot +// height off screen). +func encodeValue(ts *TimeSeries, v float64) uint16 { + r := ts.valueRange + if r <= 0 { + r = 1 + } + norm := ((v-ts.Min)/r + 1) / 3 + norm = min(1, max(0, norm)) + return uint16(norm*65535 + 0.5) +} + +// encodeSeries packs one series' samples (and its 16:1 min/max groups) into +// the texture rows starting at rawRow/mmRow. +func encodeSeries(data, mm *image.RGBA, texW, rawRow, mmRow int, ts *TimeSeries, values []float64) { + for s, v := range values { + q := encodeValue(ts, v) + data.SetRGBA(s%texW, rawRow+s/texW, color.RGBA{R: uint8(q >> 8), G: uint8(q), A: 0xff}) + } + for g := 0; g*mmGroup < len(values); g++ { + end := min((g+1)*mmGroup, len(values)) + lo, hi := values[g*mmGroup], values[g*mmGroup] + for _, v := range values[g*mmGroup+1 : end] { + lo = min(lo, v) + hi = max(hi, v) + } + ql, qh := encodeValue(ts, lo), encodeValue(ts, hi) + mm.SetRGBA(g%texW, mmRow+g/texW, color.RGBA{ + R: uint8(ql >> 8), G: uint8(ql), + B: uint8(qh >> 8), A: uint8(qh), + }) + } +} + +// updateShaderMeta rebuilds the per-series metadata texture (color, enabled, +// length). Called when the legend toggles or recolors a series; a fresh image +// is allocated because the painter re-uploads only when the map entry points +// at a new image. +func (p *Plotter) updateShaderMeta() { + if p.shader == nil { + return + } + meta := image.NewRGBA(image.Rect(0, 0, 4, len(p.ts))) + for i, ts := range p.ts { + meta.SetRGBA(0, i, ts.Color) + var enabled uint8 + if ts.Enabled { + enabled = 0xff + } + meta.SetRGBA(1, i, color.RGBA{R: enabled, A: 0xff}) + n := len(p.values[ts.Name]) + meta.SetRGBA(2, i, color.RGBA{R: uint8(n >> 16), G: uint8(n >> 8), B: uint8(n), A: 0xff}) + } + p.shader.Textures["meta_tex"] = meta +} + +// updateShaderView pushes the view window and hover state; this is the whole +// per-frame CPU cost of a playback seek on the shader backend. +func (p *Plotter) updateShaderView() { + if p.shader == nil { + return + } + size := p.plotObj.Size() + u := p.shader.Uniforms + u["plot_start"] = float32(p.plotStartPos) + u["points_shown"] = float32(p.dataPointsToShow) + u["highlight"] = float32(p.hilightLine) + u["size_w"] = size.Width + u["size_h"] = size.Height +} diff --git a/pkg/widgets/plotter/plotter_shader_test.go b/pkg/widgets/plotter/plotter_shader_test.go new file mode 100644 index 00000000..9d30e17b --- /dev/null +++ b/pkg/widgets/plotter/plotter_shader_test.go @@ -0,0 +1,175 @@ +package plotter + +import ( + "image" + "math" + "os" + "os/exec" + "path/filepath" + "testing" +) + +func shaderPlotter(t testing.TB, numSeries, numPoints int) *Plotter { + t.Helper() + p := NewPlotter(benchValues(numSeries, numPoints)) + if p.backend != plotBackendShader { + t.Fatal("shader backend not selected") + } + return p +} + +// decode16 mirrors the shader's texel decode. +func decode16(hi, lo uint8) float64 { + return (float64(hi)*256 + float64(lo)) / 65535 +} + +// Every sample must decode from the data texture to its display-normalized +// value: texel 0..1 spans [Min-range, Max+range]. +func TestPlotShaderDataEncoding(t *testing.T) { + p := shaderPlotter(t, 3, 5000) + + tex := p.shader.Textures["data_tex"].(*image.RGBA) + texW := int(p.shader.Uniforms["tex_w"]) + rowsRaw := int(p.shader.Uniforms["rows_raw"]) + if texW != 4096 || rowsRaw != 2 { + t.Fatalf("layout texW=%d rowsRaw=%d, want 4096/2", texW, rowsRaw) + } + + for i, ts := range p.ts { + data := p.values[ts.Name] + for s, v := range data { + c := tex.RGBAAt(s%texW, i*rowsRaw+s/texW) + got := decode16(c.R, c.G) + want := ((v-ts.Min)/ts.valueRange + 1) / 3 + if math.Abs(got-want) > 1.0/65535 { + t.Fatalf("series %d sample %d: %v, want %v", i, s, got, want) + } + } + } +} + +// The min/max texture must hold the exact extremes of each 16-sample group. +func TestPlotShaderMinMax(t *testing.T) { + p := shaderPlotter(t, 2, 5000) + + mm := p.shader.Textures["mm_tex"].(*image.RGBA) + texW := int(p.shader.Uniforms["tex_w"]) + rowsMM := int(p.shader.Uniforms["rows_mm"]) + + for i, ts := range p.ts { + data := p.values[ts.Name] + for g := 0; g*mmGroup < len(data); g++ { + end := min((g+1)*mmGroup, len(data)) + lo, hi := data[g*mmGroup], data[g*mmGroup] + for _, v := range data[g*mmGroup+1 : end] { + lo = min(lo, v) + hi = max(hi, v) + } + c := mm.RGBAAt(g%texW, i*rowsMM+g/texW) + wantLo := ((lo-ts.Min)/ts.valueRange + 1) / 3 + wantHi := ((hi-ts.Min)/ts.valueRange + 1) / 3 + if math.Abs(decode16(c.R, c.G)-wantLo) > 1.0/65535 || math.Abs(decode16(c.B, c.A)-wantHi) > 1.0/65535 { + t.Fatalf("series %d group %d: (%v,%v), want (%v,%v)", + i, g, decode16(c.R, c.G), decode16(c.B, c.A), wantLo, wantHi) + } + } + } +} + +// Out-of-range samples must clamp a full display range off screen, not at +// the plot edge, so overshooting lines keep their slope like the Bresenham +// clipping does. +func TestPlotShaderEncodeHeadroom(t *testing.T) { + ts := &TimeSeries{Min: 0, Max: 10, valueRange: 10} + for _, tc := range []struct { + v float64 + want float64 + }{ + {0, 1.0 / 3}, // Min -> bottom of display band + {10, 2.0 / 3}, // Max -> top of display band + {-10, 0}, // one range below -> texel floor + {20, 1}, // one range above -> texel ceil + {-100, 0}, {99, 1}, // far overshoot clamps + } { + got := float64(encodeValue(ts, tc.v)) / 65535 + if math.Abs(got-tc.want) > 1.0/65535 { + t.Fatalf("encode(%v) = %v, want %v", tc.v, got, tc.want) + } + } +} + +// The metadata texture carries color, enabled flag and series length, and a +// legend toggle must produce a fresh image holding the new state. +func TestPlotShaderMeta(t *testing.T) { + p := shaderPlotter(t, 2, 100) + + meta := p.shader.Textures["meta_tex"].(*image.RGBA) + for i, ts := range p.ts { + if c := meta.RGBAAt(0, i); c != ts.Color { + t.Fatalf("series %d color %v, want %v", i, c, ts.Color) + } + if c := meta.RGBAAt(1, i); c.R != 0xff { + t.Fatalf("series %d not flagged enabled", i) + } + n := len(p.values[ts.Name]) + c := meta.RGBAAt(2, i) + if got := int(c.R)<<16 | int(c.G)<<8 | int(c.B); got != n { + t.Fatalf("series %d length %d, want %d", i, got, n) + } + } + + p.ts[1].Enabled = false + p.updateShaderMeta() + meta2 := p.shader.Textures["meta_tex"].(*image.RGBA) + if meta2 == meta { + t.Fatal("meta texture not replaced; painter would not re-upload") + } + if c := meta2.RGBAAt(1, 1); c.R != 0 { + t.Fatal("disabled series still flagged enabled") + } +} + +// Logs that exceed the texture budget must fall back to the image backend. +func TestPlotShaderFallback(t *testing.T) { + p := NewPlotter(benchValues(2, plotTexW*plotTexMaxH/2+1)) + if p.backend != plotBackendImage { + t.Fatal("oversized log did not fall back to the image backend") + } + if p.plotObj != p.canvasImage { + t.Fatal("fallback must keep drawing through canvasImage") + } +} + +// Both shader variants must at least compile; glslangValidator checks them +// against the GLSL 1.10 (desktop) and GLSL ES 1.00 specs. +func TestPlotShaderSourcesCompile(t *testing.T) { + validator, err := exec.LookPath("glslangValidator") + if err != nil { + t.Skip("glslangValidator not installed") + } + for name, src := range map[string]string{ + "desktop.frag": plotShaderPreludeGL + plotShaderBody, + "es.frag": plotShaderPreludeES + plotShaderBody, + } { + p := filepath.Join(t.TempDir(), name) + if err := os.WriteFile(p, []byte(src), 0o644); err != nil { + t.Fatal(err) + } + if out, err := exec.Command(validator, p).CombinedOutput(); err != nil { + t.Fatalf("%s: %v\n%s", name, err, out) + } + } +} + +// CPU-side cost of a playback seek on the shader backend; compare against +// the BenchmarkPlot_* figures for the image backend (which additionally +// re-uploads the whole plot texture every frame). +func BenchmarkUpdateShaderView(b *testing.B) { + p := shaderPlotter(b, 30, 10000) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + p.plotStartPos = i % 1000 + p.updateShaderView() + } +} From 69284e78c1f37a244d55119efeac762cad67bcca Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 00:45:14 +0200 Subject: [PATCH 032/102] shaders in dials --- pkg/widgets/dial/dial.go | 203 ++++++------------ pkg/widgets/dial/dial_shader.go | 126 +++++++++++ pkg/widgets/dial/dial_shader_test.go | 29 +++ pkg/widgets/dualdial/dual_dial.go | 183 ++++++---------- pkg/widgets/dualdial/dual_dial_shader.go | 119 ++++++++++ pkg/widgets/dualdial/dual_dial_shader_test.go | 29 +++ 6 files changed, 424 insertions(+), 265 deletions(-) create mode 100644 pkg/widgets/dial/dial_shader.go create mode 100644 pkg/widgets/dial/dial_shader_test.go create mode 100644 pkg/widgets/dualdial/dual_dial_shader.go create mode 100644 pkg/widgets/dualdial/dual_dial_shader_test.go diff --git a/pkg/widgets/dial/dial.go b/pkg/widgets/dial/dial.go index 45c54f63..acf10f0a 100644 --- a/pkg/widgets/dial/dial.go +++ b/pkg/widgets/dial/dial.go @@ -23,29 +23,26 @@ type Dial struct { value float64 highestObserved float64 - needle *canvas.Line - highestObservedMarker *canvas.Line - lastHighestObserved time.Time + lastHighestObserved time.Time + + // All dial geometry (face, pips, needle, marker, center) in one object + shader *canvas.Shader - pips []*canvas.Line pipLabels []*canvas.Text - face *canvas.Arc - center *canvas.Circle displayText *canvas.Text titleText *canvas.Text size fyne.Size minsize fyne.Size - diameter float32 - radius float32 - middle fyne.Position - needleOffset, needleLength float32 - needleRotConst float64 // = common.Pi15/(Max-Min) - lineRotConst float64 // = common.Pi15/steps + diameter float32 + radius float32 + middle fyne.Position + needleRotConst float64 // = common.Pi15/(Max-Min) + lineRotConst float64 // = common.Pi15/steps - // Precomputed trig for pips: angle_i = lineRotConst*float64(i) - common.Pi43 + // Precomputed trig for pip labels: angle_i = lineRotConst*float64(i) - common.Pi43 pipSin []float32 pipCos []float32 @@ -95,10 +92,21 @@ func New(cfg *widgets.GaugeConfig) *Dial { c.factor = c.cfg.Max / steps - c.face = canvas.NewArc(-135.73, 135.8, 0.985, color.RGBA{0x80, 0x80, 0x80, 0xFF}) - c.center = &canvas.Circle{FillColor: color.RGBA{R: 0x01, G: 0x0B, B: 0x13, A: 0xFF}} - c.needle = &canvas.Line{StrokeColor: color.RGBA{R: 0xFF, G: 0x67, B: 0, A: 0xFF}, StrokeWidth: 3} - c.highestObservedMarker = &canvas.Line{StrokeColor: color.RGBA{R: 216, G: 250, B: 8, A: 0xFF}, StrokeWidth: 6} + // Constants + c.needleRotConst = common.Pi15 / totalRange + c.lineRotConst = common.Pi15 / steps + + c.shader = canvas.NewShader( + "txlogger-dial", + []byte(dialShaderPreludeGL+dialShaderBody), + []byte(dialShaderPreludeES+dialShaderBody), + ) + c.shader.Uniforms = map[string]float32{ + "size_d": 100, + "steps": float32(cfg.Steps), + "needle_a": c.needleAngle(0), + "marker_a": c.needleAngle(0), + } c.titleText = &canvas.Text{Text: c.cfg.Title, Color: color.RGBA{R: 0xF0, G: 0xF0, B: 0xF0, A: 0xFF}, TextSize: 25} c.titleText.TextStyle.Monospace = true @@ -107,27 +115,8 @@ func New(cfg *widgets.GaugeConfig) *Dial { c.displayText = &canvas.Text{Text: "0", Color: color.RGBA{R: 0x2c, G: 0xfc, B: 0x03, A: 0xFF}, TextSize: 52} c.displayText.Alignment = fyne.TextAlignCenter - // Pip color gradient - // Green to Yellow to Red gradient - // 0% - 50% Green to Yellow - // 50% - 100% Yellow to Red - halfSteps := float64(c.cfg.Steps) / 2.0 - - // Build pips + labels once; also track the longest label length - var col color.RGBA + // Labels at every other pip; also track the longest label length for i := 0; i < c.cfg.Steps+1; i++ { - if float64(i) <= halfSteps { - // Green to Yellow - ratio := float64(i) / halfSteps - col = color.RGBA{R: byte(255 * ratio), G: 255, B: 0, A: 255} - } else { - // Yellow to Red - ratio := (float64(i) - halfSteps) / halfSteps - col = color.RGBA{R: 255, G: byte(255 * (1 - ratio)), B: 0, A: 255} - } - pip := &canvas.Line{StrokeColor: col, StrokeWidth: 2} - c.pips = append(c.pips, pip) - if i%2 == 0 { val := c.cfg.Min + (float64(i)/float64(c.cfg.Steps))*(c.cfg.Max-c.cfg.Min)*c.cfg.GaugeFactor txt := strconv.FormatFloat(val, 'f', c.gaugePrec, 64) @@ -137,7 +126,6 @@ func New(cfg *widgets.GaugeConfig) *Dial { Color: color.RGBA{0xE0, 0xE0, 0xE0, 0xFF}, Alignment: fyne.TextAlignCenter, } - // lbl.TextStyle.Monospace = true if n := len(txt); n > c.maxLabelChars { c.maxLabelChars = n } @@ -147,11 +135,7 @@ func New(cfg *widgets.GaugeConfig) *Dial { } } - // Constants - c.needleRotConst = common.Pi15 / totalRange - c.lineRotConst = common.Pi15 / steps - - // Precompute pip sin/cos (size-independent) + // Precompute pip sin/cos for label placement (size-independent) c.pipSin = make([]float32, c.cfg.Steps+1) c.pipCos = make([]float32, c.cfg.Steps+1) for i := 0; i <= c.cfg.Steps; i++ { @@ -166,29 +150,14 @@ func New(cfg *widgets.GaugeConfig) *Dial { func (c *Dial) GetConfig() *widgets.GaugeConfig { return c.cfg } -// rotate angle (in radians) without refreshing per-object -func (c *Dial) rotateNoRefresh(hand *canvas.Line, rotation float64, offset, length float32) { - s, co := math.Sincos(rotation) - c.applySinCos(hand, float32(s), float32(co), offset, length) -} - -func (c *Dial) applySinCos(hand *canvas.Line, sinRot, cosRot float32, offset, length float32) { - x2 := length * sinRot - y2 := -length * cosRot - offX := offset * sinRot - offY := -offset * cosRot - midxOffX := c.middle.X + offX - midY := c.middle.Y + offY - hand.Position1 = fyne.Position{X: midxOffX, Y: midY} - hand.Position2 = fyne.Position{X: midxOffX + x2, Y: midY + y2} -} - -func (c *Dial) rotateNeedleNoRefresh(hand *canvas.Line, facePosition float64, offset, length float32) { - normalized := facePosition - c.cfg.Min +// needle angle for a face value; clamped below Min like the CPU renderer, +// free to overshoot above Max +func (c *Dial) needleAngle(value float64) float32 { + normalized := value - c.cfg.Min if normalized < 0 { normalized = 0 } - c.rotateNoRefresh(hand, c.needleRotConst*normalized-common.Pi43, offset, length) + return float32(c.needleRotConst*normalized - common.Pi43) } func (c *Dial) SetValue(value float64) { @@ -197,21 +166,13 @@ func (c *Dial) SetValue(value float64) { } c.value = value - // Update needle position (no immediate refresh) - c.rotateNeedleNoRefresh(c.needle, value, c.needleOffset, c.needleLength) + c.shader.Uniforms["needle_a"] = c.needleAngle(value) - // Highest observed marker with lazy reset; only refresh when it actually moves - markerMoved := false - if value > c.highestObserved { - c.highestObserved = value - c.lastHighestObserved = time.Now() - c.rotateNeedleNoRefresh(c.highestObservedMarker, value, c.radius-2, 6) - markerMoved = true - } else if time.Since(c.lastHighestObserved) > 10*time.Second { + // Highest observed marker with lazy reset + if value > c.highestObserved || time.Since(c.lastHighestObserved) > 10*time.Second { c.highestObserved = value c.lastHighestObserved = time.Now() - c.rotateNeedleNoRefresh(c.highestObservedMarker, value, c.radius-2, 6) - markerMoved = true + c.shader.Uniforms["marker_a"] = c.needleAngle(value) } // Update text with minimal allocs; skip refresh if formatted output is unchanged @@ -221,18 +182,12 @@ func (c *Dial) SetValue(value float64) { } else { c.buf = common.AppendFormatFloat(c.buf, c.displayString, value) } - textChanged := !common.SameTextBytes(c.displayText.Text, c.buf) - if textChanged { + if !common.SameTextBytes(c.displayText.Text, c.buf) { c.displayText.Text = string(c.buf) - } - - canvas.Refresh(c.needle) - if markerMoved { - canvas.Refresh(c.highestObservedMarker) - } - if textChanged { canvas.Refresh(c.displayText) } + + canvas.Refresh(c.shader) } func (c *Dial) SetValue2(value float64) { c.SetValue(value) } @@ -253,45 +208,26 @@ func (c *DialRenderer) Layout(space fyne.Size) { c.diameter = fyne.Min(space.Width, space.Height) c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) - c.needleOffset = -c.radius * .15 - c.needleLength = c.radius * 1.14 - - // Stroke sizes - stroke := c.diameter * common.OneSixthieth - midStroke := c.diameter * common.OneEighthieth - smallStroke := c.diameter * common.OneTwohundredth size := fyne.Size{Width: c.diameter, Height: c.diameter} topleft := fyne.NewPos(c.middle.X-c.radius, c.middle.Y-c.radius) + // Shader quad: the dial square padded so the marker overhang isn't clipped + c.shader.Move(topleft.SubtractXY(dialPad, dialPad)) + c.shader.Resize(fyne.Size{Width: c.diameter + 2*dialPad, Height: c.diameter + 2*dialPad}) + c.shader.Uniforms["size_d"] = c.diameter + // Title (no rounding needed) c.titleText.TextSize = float32(int(c.radius * common.OneFourth)) c.titleText.Move(c.middle.Add(fyne.NewPos(0, c.diameter*common.OneFourth))) - // Center element - center := c.radius * common.OneFourth - c.center.Move(c.middle.SubtractXY(center*common.OneHalf, center*common.OneHalf)) - c.center.Resize(fyne.Size{Width: center, Height: center}) - // Display text c.displayText.TextSize = float32(int(c.radius * common.OneThird)) c.displayText.Move(topleft.AddXY(0, c.diameter*common.OneFifth)) c.displayText.Resize(size) - // Face + needle - c.needle.StrokeWidth = stroke - c.rotateNeedleNoRefresh(c.needle, c.value, c.needleOffset, c.needleLength) - - c.face.Move(c.middle.SubtractXY(c.radius, c.radius)) - c.face.Resize(fyne.Size{Width: c.diameter, Height: c.diameter}) - - // Pips: reuse precomputed sin/cos, scale with current radii - fourthRadius := c.radius * common.OneFourth - eightRadius := c.radius * common.OneEight + // Labels: reuse precomputed sin/cos, scale with current radius radius43 := c.radius * common.OneFourth * 3 - radius87 := c.radius * common.OneEight * 7 - - // Label padding and cached box dims (avoid lbl.MinSize per label) labelPad := max(float32(6.0), c.radius*0.14) // Assume monospace, digits only: width ≈ chars * 0.62 * TextSize; height ≈ 1.15 * TextSize @@ -303,35 +239,20 @@ func (c *DialRenderer) Layout(space fyne.Size) { c.labelBoxW = float32(c.maxLabelChars) * float32(charWidthFactor) * labelTextSize c.labelBoxH = float32(heightFactor) * labelTextSize - for i, p := range c.pips { - if i%2 == 0 { - p.StrokeWidth = max(2.0, midStroke) - c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius43, fourthRadius-1) - - // Label for long pip - lbl := c.pipLabels[i] - if lbl != nil { - lbl.TextSize = labelTextSize - - // Place label on the INSIDE of the gauge - labelRadius := radius43 - labelPad - cx := c.middle.X + c.pipSin[i]*labelRadius - cy := c.middle.Y - c.pipCos[i]*labelRadius - - boxW := c.labelBoxW - boxH := c.labelBoxH - lbl.Resize(fyne.NewSize(boxW, boxH)) - // lbl.Move(fyne.NewPos(cx-boxW/2, cy-boxH/2)) - lbl.Move(fyne.Position{X: cx - boxW/2, Y: cy - boxH/2}) - } - } else { - p.StrokeWidth = max(2.0, smallStroke) - c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius87, eightRadius-1) + for i, lbl := range c.pipLabels { + if lbl == nil { + continue } - } + lbl.TextSize = labelTextSize - c.highestObservedMarker.StrokeWidth = max(2.0, midStroke) - c.rotateNeedleNoRefresh(c.highestObservedMarker, c.highestObserved, c.radius-2, 6) + // Place label on the INSIDE of the gauge + labelRadius := radius43 - labelPad + cx := c.middle.X + c.pipSin[i]*labelRadius + cy := c.middle.Y - c.pipCos[i]*labelRadius + + lbl.Resize(fyne.NewSize(c.labelBoxW, c.labelBoxH)) + lbl.Move(fyne.Position{X: cx - c.labelBoxW/2, Y: cy - c.labelBoxH/2}) + } } func (c *DialRenderer) MinSize() fyne.Size { return c.minsize } @@ -340,17 +261,14 @@ func (c *DialRenderer) Destroy() {} func (c *DialRenderer) Objects() []fyne.CanvasObject { if c.objects == nil { - objs := make([]fyne.CanvasObject, 0, len(c.pips)+len(c.pipLabels)+7) - for _, v := range c.pips { - objs = append(objs, v) - } + objs := make([]fyne.CanvasObject, 0, len(c.pipLabels)+3) + objs = append(objs, c.shader) for _, t := range c.pipLabels { if t != nil { objs = append(objs, t) } } - objs = append(objs, c.face, c.titleText, c.center, - c.highestObservedMarker, c.needle, c.displayText) + objs = append(objs, c.titleText, c.displayText) c.objects = objs } return c.objects @@ -365,4 +283,3 @@ func max(a, b float32) float32 { } return b } - diff --git a/pkg/widgets/dial/dial_shader.go b/pkg/widgets/dial/dial_shader.go new file mode 100644 index 00000000..604b6c65 --- /dev/null +++ b/pkg/widgets/dial/dial_shader.go @@ -0,0 +1,126 @@ +package dial + +// GPU renderer: the dial geometry (face rim, pips, needle, highest-observed +// marker and center cap) is drawn by a single canvas.Shader, collapsing ~30 +// canvas objects per dial into one draw call; SetValue only writes a float +// uniform. Text (title, value, pip labels) stays as canvas.Text layered on +// top - rasterizing glyphs in a fragment shader buys nothing. +// +// All dials share one Shader.Name: the painter caches the compiled program +// per name and the dial needs no textures, so every instance reuses the same +// program with its own uniforms. +// +// Conventions shared between the Go side and the GLSL below: +// - angles are radians, 0 pointing up, clockwise positive, matching the +// needle direction (sin a, -cos a) of the CPU renderer +// - the needle sweeps Pi15 (270 degrees) from -3/4 pi at Min to +3/4 pi +// at Max; pip i sits at i*Pi15/steps - 3/4 pi +// - the shader quad is the dial square padded by dialPad logical px per +// side, because the marker pokes 4 px past the rim like the CPU line did +const dialPad = 4 + +const dialShaderPreludeGL = "#version 110\n" + +const dialShaderPreludeES = `#version 100 +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +` + +const dialShaderBody = ` +uniform vec2 frame_size; +uniform vec4 rect_coords; + +uniform float size_d; // dial diameter, logical px +uniform float steps; // pip intervals; steps+1 pips are drawn +uniform float needle_a; // needle angle, radians +uniform float marker_a; // highest-observed marker angle, radians + +const float PIP_ANG = 2.35619449; // 3/4 pi, the first/last pip angle +const float FACE_ANG = 2.3693; // rim ends just past the end pips, like the canvas.Arc face +const float PAD = 4.0; // logical px, keep in sync with dialPad + +const vec3 FACE_COL = vec3(0.502, 0.502, 0.502); // 0x808080 +const vec3 CENTER_COL = vec3(0.0039, 0.0431, 0.0745); // 0x010B13 +const vec3 NEEDLE_COL = vec3(1.0, 0.4039, 0.0); // 0xFF6700 +const vec3 MARKER_COL = vec3(0.8471, 0.9804, 0.0314); // 0xD8FA08 + +// distance to the radial bar at angle a covering radius [r0, r1], half width hw +float radial_d(vec2 p, float a, float r0, float r1, float hw) { + vec2 dir = vec2(sin(a), -cos(a)); + float u = dot(p, dir); + float v = dot(p, vec2(-dir.y, dir.x)); + return length(vec2(u - clamp(u, r0, r1), v)) - hw; +} + +// 1 px anti-aliased coverage of signed distance d (device px) +float aa(float d) { + return clamp(0.5 - d, 0.0, 1.0); +} + +// src-over: lay coverage a of colour c on top; col stays premultiplied +void over(inout vec3 col, inout float alpha, vec3 c, float a) { + col = col * (1.0 - a) + c * a; + alpha = alpha * (1.0 - a) + a; +} + +void main() { + vec2 ext = vec2(rect_coords.y - rect_coords.x, rect_coords.w - rect_coords.z); + vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; + + // the painter expands the quad slightly for edge softness; stay inside + if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > ext.x || p_dev.y > ext.y) { + discard; + } + + float px = ext.x / (size_d + 2.0 * PAD); // device px per logical px + float r = 0.5 * size_d * px; // dial radius, device px + vec2 p = p_dev - 0.5 * ext; + + float len = length(p); + float theta = atan(p.x, -p.y); // 0 up, clockwise positive + + vec3 col = vec3(0.0); + float alpha = 0.0; + + // pips: strokes are far thinner than the pip spacing, so only the + // nearest pip can cover this pixel - no loop needed + float n = max(steps, 1.0); + float step_a = 4.71238898 / n; // Pi15 between first and last pip + float i = clamp(floor((theta + PIP_ANG) / step_a + 0.5), 0.0, n); + float odd = mod(i, 2.0); + float hw = 0.5 * px * mix(max(2.0, size_d / 80.0), max(2.0, size_d / 200.0), odd); + float rin = mix(0.75, 0.875, odd) * r; + // intersect with a disc one logical px inside the rim edge: the round + // end cap must not poke past the rim, and the AA fringe of the cut has + // to stay under the opaque part of the rim + float d = max(radial_d(p, i * step_a - PIP_ANG, rin, r - px, hw), len - (r - px)); + // green -> yellow -> red, like the CPU pip gradient + float t = i / n; + vec3 pip_col = vec3(clamp(2.0 * t, 0.0, 1.0), clamp(2.0 - 2.0 * t, 0.0, 1.0), 0.0); + over(col, alpha, pip_col, aa(d)); + + // face rim: the ring [0.985r, r] over the pip arc; the angular term is + // the arc length past the rim ends + d = max(abs(len - 0.9925 * r) - 0.0075 * r, (abs(theta) - FACE_ANG) * len); + over(col, alpha, FACE_COL, aa(d)); + + // center cap, diameter r/4 + over(col, alpha, CENTER_COL, aa(len - 0.125 * r)); + + // highest-observed marker: radius-2 .. radius+4 like the CPU line + float mhw = 0.5 * px * max(2.0, size_d / 80.0); + over(col, alpha, MARKER_COL, aa(radial_d(p, marker_a, r - 2.0 * px, r + 4.0 * px, mhw))); + + // needle: offset -0.15r, length 1.14r, tip pulled in 2 logical px + float nhw = 0.5 * px * (size_d / 60.0); + over(col, alpha, NEEDLE_COL, aa(radial_d(p, needle_a, -0.15 * r, 0.99 * r - 2.0 * px, nhw))); + + if (alpha < 0.004) { + discard; + } + gl_FragColor = vec4(col / alpha, alpha); +} +` diff --git a/pkg/widgets/dial/dial_shader_test.go b/pkg/widgets/dial/dial_shader_test.go new file mode 100644 index 00000000..31e6e0e3 --- /dev/null +++ b/pkg/widgets/dial/dial_shader_test.go @@ -0,0 +1,29 @@ +package dial + +import ( + "os" + "os/exec" + "path/filepath" + "testing" +) + +// Both shader variants must at least compile; glslangValidator checks them +// against the GLSL 1.10 (desktop) and GLSL ES 1.00 specs. +func TestDialShaderSourcesCompile(t *testing.T) { + validator, err := exec.LookPath("glslangValidator") + if err != nil { + t.Skip("glslangValidator not installed") + } + for name, src := range map[string]string{ + "desktop.frag": dialShaderPreludeGL + dialShaderBody, + "es.frag": dialShaderPreludeES + dialShaderBody, + } { + p := filepath.Join(t.TempDir(), name) + if err := os.WriteFile(p, []byte(src), 0o644); err != nil { + t.Fatal(err) + } + if out, err := exec.Command(validator, p).CombinedOutput(); err != nil { + t.Fatalf("%s: %v\n%s", name, err, out) + } + } +} diff --git a/pkg/widgets/dualdial/dual_dial.go b/pkg/widgets/dualdial/dual_dial.go index 8dc44673..85f9920d 100644 --- a/pkg/widgets/dualdial/dual_dial.go +++ b/pkg/widgets/dualdial/dual_dial.go @@ -23,15 +23,11 @@ type DualDial struct { value float64 value2 float64 - needle *canvas.Line - needle2 *canvas.Line + // All gauge geometry (face, pips, both needles, center) in one object + shader *canvas.Shader - pips []*canvas.Line pipLabels []*canvas.Text - face *canvas.Arc - center *canvas.Circle - displayText *canvas.Text displayText2 *canvas.Text @@ -41,14 +37,13 @@ type DualDial struct { size fyne.Size minsize fyne.Size - diameter float32 - radius float32 - middle fyne.Position - needleOffset, needleLength float32 - needleRotConst float64 - lineRotConst float64 + diameter float32 + radius float32 + middle fyne.Position + needleRotConst float64 + lineRotConst float64 - // cached sin/cos for pips (angle_i = lineRotConst*i - common.Pi43) + // cached sin/cos for pip labels (angle_i = lineRotConst*i - common.Pi43) pipSin []float32 pipCos []float32 @@ -94,10 +89,24 @@ func New(cfg *widgets.GaugeConfig) *DualDial { s.factor = s.cfg.Max / s.steps - s.face = canvas.NewArc(-135.73, 135.8, 0.985, color.RGBA{0x80, 0x80, 0x80, 0xFF}) - s.center = &canvas.Circle{FillColor: color.RGBA{R: 0x01, G: 0x0B, B: 0x13, A: 0xFF}} - s.needle = &canvas.Line{StrokeColor: color.RGBA{R: 0xFF, G: 0x67, B: 0, A: 0xFF}, StrokeWidth: 2} - s.needle2 = &canvas.Line{StrokeColor: color.RGBA{R: 249, G: 27, B: 2, A: 255}, StrokeWidth: 2} + totalRange := s.cfg.Max - s.cfg.Min + if totalRange <= 0 { + totalRange = 1 + } + s.needleRotConst = common.Pi15 / totalRange + s.lineRotConst = common.Pi15 / s.steps + + s.shader = canvas.NewShader( + "txlogger-dualdial", + []byte(dualDialShaderPreludeGL+dualDialShaderBody), + []byte(dualDialShaderPreludeES+dualDialShaderBody), + ) + s.shader.Uniforms = map[string]float32{ + "size_d": 100, + "steps": float32(s.steps), + "needle_a": s.needleAngle(0), + "needle2_a": s.needleAngle(0), + } s.titleText = &canvas.Text{Text: s.cfg.Title, Color: color.RGBA{R: 0xF0, G: 0xF0, B: 0xF0, A: 0xFF}, TextSize: 25} s.titleText.TextStyle.Monospace = true @@ -109,25 +118,8 @@ func New(cfg *widgets.GaugeConfig) *DualDial { s.displayText2 = &canvas.Text{Text: "0", Color: color.RGBA{R: 0xff, G: 0x0, B: 0, A: 0xFF}, TextSize: 35} s.displayText2.Alignment = fyne.TextAlignCenter - // Pip color gradient - // Green to Yellow to Red gradient - // 0% - 50% Green to Yellow - // 50% - 100% Yellow to Red - halfSteps := float64(s.cfg.Steps) / 2.0 - var col color.RGBA + // Labels at every other pip; also track the longest label length for i := 0; i <= int(s.steps); i++ { - if float64(i) <= halfSteps { - // Green to Yellow - ratio := float64(i) / halfSteps - col = color.RGBA{R: byte(255 * ratio), G: 255, B: 0, A: 255} - } else { - // Yellow to Red - ratio := (float64(i) - halfSteps) / halfSteps - col = color.RGBA{R: 255, G: byte(255 * (1 - ratio)), B: 0, A: 255} - } - pip := &canvas.Line{StrokeColor: col, StrokeWidth: 2} - s.pips = append(s.pips, pip) - if i%2 == 0 { val := s.cfg.Min + (float64(i)/float64(s.cfg.Steps))*(s.cfg.Max-s.cfg.Min) txt := strconv.FormatFloat(val, 'f', s.gaugePrec, 64) @@ -136,7 +128,6 @@ func New(cfg *widgets.GaugeConfig) *DualDial { Color: color.RGBA{0xE0, 0xE0, 0xE0, 0xFF}, Alignment: fyne.TextAlignCenter, } - // lbl.TextStyle.Monospace = true if n := len(txt); n > s.maxLabelChars { s.maxLabelChars = n } @@ -146,14 +137,7 @@ func New(cfg *widgets.GaugeConfig) *DualDial { } } - totalRange := s.cfg.Max - s.cfg.Min - if totalRange <= 0 { - totalRange = 1 - } - s.needleRotConst = common.Pi15 / totalRange - s.lineRotConst = common.Pi15 / s.steps - - // precompute pip trig (size independent) + // precompute pip trig for label placement (size independent) s.pipSin = make([]float32, int(s.steps)+1) s.pipCos = make([]float32, int(s.steps)+1) for i := 0; i <= int(s.steps); i++ { @@ -168,24 +152,14 @@ func New(cfg *widgets.GaugeConfig) *DualDial { func (c *DualDial) GetConfig() *widgets.GaugeConfig { return c.cfg } -func (c *DualDial) rotateNeedleNoRefresh(hand *canvas.Line, facePosition float64, offset, length float32) { - normalized := facePosition - c.cfg.Min +// needle angle for a face value; clamped below Min like the CPU renderer, +// free to overshoot above Max +func (c *DualDial) needleAngle(value float64) float32 { + normalized := value - c.cfg.Min if normalized < 0 { normalized = 0 } - s, co := math.Sincos(c.needleRotConst*normalized - common.Pi43) - c.applySinCos(hand, float32(s), float32(co), offset, length) -} - -func (c *DualDial) applySinCos(hand *canvas.Line, sinRot, cosRot float32, offset, length float32) { - x2 := length * sinRot - y2 := -length * cosRot - offX := offset * sinRot - offY := -offset * cosRot - midxOffX := c.middle.X + offX - midY := c.middle.Y + offY - hand.Position1 = fyne.Position{X: midxOffX, Y: midY} - hand.Position2 = fyne.Position{X: midxOffX + x2, Y: midY + y2} + return float32(c.needleRotConst*normalized - common.Pi43) } func (c *DualDial) SetValue(value float64) { @@ -194,7 +168,7 @@ func (c *DualDial) SetValue(value float64) { } c.value = value - c.rotateNeedleNoRefresh(c.needle, value, c.needleOffset, c.needleLength) + c.shader.Uniforms["needle_a"] = c.needleAngle(value) c.buf1 = c.buf1[:0] if c.fmtPrec >= 0 { @@ -202,15 +176,12 @@ func (c *DualDial) SetValue(value float64) { } else { c.buf1 = common.AppendFormatFloat(c.buf1, c.displayString, value) } - textChanged := !common.SameTextBytes(c.displayText.Text, c.buf1) - if textChanged { + if !common.SameTextBytes(c.displayText.Text, c.buf1) { c.displayText.Text = string(c.buf1) - } - - canvas.Refresh(c.needle) - if textChanged { canvas.Refresh(c.displayText) } + + canvas.Refresh(c.shader) } func (c *DualDial) SetValue2(value float64) { @@ -219,7 +190,7 @@ func (c *DualDial) SetValue2(value float64) { } c.value2 = value - c.rotateNeedleNoRefresh(c.needle2, value, c.needleOffset, c.needleLength) + c.shader.Uniforms["needle2_a"] = c.needleAngle(value) c.buf2 = c.buf2[:0] if c.fmtPrec >= 0 { @@ -227,15 +198,12 @@ func (c *DualDial) SetValue2(value float64) { } else { c.buf2 = common.AppendFormatFloat(c.buf2, c.displayString, value) } - textChanged := !common.SameTextBytes(c.displayText2.Text, c.buf2) - if textChanged { + if !common.SameTextBytes(c.displayText2.Text, c.buf2) { c.displayText2.Text = string(c.buf2) - } - - canvas.Refresh(c.needle2) - if textChanged { canvas.Refresh(c.displayText2) } + + canvas.Refresh(c.shader) } func (c *DualDial) CreateRenderer() fyne.WidgetRenderer { return &DualDialRenderer{DualDial: c} } @@ -255,24 +223,17 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) - c.needleOffset = -c.radius * .15 - c.needleLength = c.radius * 1.14 - - stroke := c.diameter * common.OneSixthieth - midStroke := c.diameter * common.OneEighthieth - smallStroke := c.diameter * common.OneTwohundredth - size := fyne.Size{Width: c.diameter, Height: c.diameter} topleft := fyne.NewPos(c.middle.X-c.radius, c.middle.Y-c.radius) + c.shader.Move(topleft) + c.shader.Resize(size) + c.shader.Uniforms["size_d"] = c.diameter + // Title & display text sizes (no math.Round needed) c.titleText.TextSize = c.radius * common.OneFourth c.titleText.Move(c.middle.Add(fyne.NewPos(0, c.diameter*common.OneFourth))) - center := c.radius * common.OneFourth - c.center.Move(c.middle.SubtractXY(center*common.OneHalf, center*common.OneHalf)) - c.center.Resize(fyne.Size{Width: center, Height: center}) - sixthDiameter := c.diameter * common.OneSixth c.displayText.TextSize = c.radius * common.OneThird @@ -283,20 +244,8 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { c.displayText2.Move(topleft.AddXY(0, -sixthDiameter)) c.displayText2.Resize(size) - // Needles & face - c.needle.StrokeWidth = stroke - c.needle2.StrokeWidth = stroke - c.rotateNeedleNoRefresh(c.needle, c.value, c.needleOffset, c.needleLength) - c.rotateNeedleNoRefresh(c.needle2, c.value2, c.needleOffset, c.needleLength) - - c.face.Move(c.middle.SubtractXY(c.radius, c.radius)) - c.face.Resize(fyne.Size{Width: c.diameter, Height: c.diameter}) - - // Pips using precomputed trig scaled by current radius - fourthRadius := c.radius * common.OneFourth - eightRadius := c.radius * common.OneEight + // Labels: reuse precomputed trig scaled by current radius radius43 := c.radius * common.OneFourth * 3 - radius87 := c.radius * common.OneEight * 7 // Label padding and cached box dims (avoid lbl.MinSize per label) labelPad := max(float32(6.0), c.radius*0.14) @@ -306,27 +255,19 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { c.labelBoxW = float32(c.maxLabelChars) * float32(charWidthFactor) * labelTextSize c.labelBoxH = float32(heightFactor) * labelTextSize - for i, p := range c.pips { - if i%2 == 0 { - p.StrokeWidth = max(2.0, midStroke) - c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius43, fourthRadius-1) - // Label for long pip (uniform box; no MinSize) - lbl := c.pipLabels[i] - if lbl != nil { - lbl.TextSize = labelTextSize - // place inside the gauge slightly inward from long pip inner end - labelRadius := radius43 - labelPad - cx := c.middle.X + c.pipSin[i]*labelRadius - cy := c.middle.Y - c.pipCos[i]*labelRadius - boxW := c.labelBoxW - boxH := c.labelBoxH - lbl.Resize(fyne.NewSize(boxW, boxH)) - lbl.Move(fyne.NewPos(cx-boxW/2, cy-boxH/2)) - } - } else { - p.StrokeWidth = max(2.0, smallStroke) - c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius87, eightRadius-1) + for i, lbl := range c.pipLabels { + if lbl == nil { + continue } + lbl.TextSize = labelTextSize + + // place inside the gauge slightly inward from long pip inner end + labelRadius := radius43 - labelPad + cx := c.middle.X + c.pipSin[i]*labelRadius + cy := c.middle.Y - c.pipCos[i]*labelRadius + + lbl.Resize(fyne.NewSize(c.labelBoxW, c.labelBoxH)) + lbl.Move(fyne.NewPos(cx-c.labelBoxW/2, cy-c.labelBoxH/2)) } } @@ -336,16 +277,14 @@ func (c *DualDialRenderer) Destroy() {} func (c *DualDialRenderer) Objects() []fyne.CanvasObject { if c.objects == nil { - objs := make([]fyne.CanvasObject, 0, len(c.pips)+len(c.pipLabels)+7) - for _, v := range c.pips { - objs = append(objs, v) - } + objs := make([]fyne.CanvasObject, 0, len(c.pipLabels)+4) + objs = append(objs, c.shader) for _, v := range c.pipLabels { if v != nil { objs = append(objs, v) } } - objs = append(objs, c.face, c.titleText, c.center, c.needle2, c.needle, c.displayText, c.displayText2) + objs = append(objs, c.titleText, c.displayText, c.displayText2) c.objects = objs } return c.objects diff --git a/pkg/widgets/dualdial/dual_dial_shader.go b/pkg/widgets/dualdial/dual_dial_shader.go new file mode 100644 index 00000000..54b784b7 --- /dev/null +++ b/pkg/widgets/dualdial/dual_dial_shader.go @@ -0,0 +1,119 @@ +package dualdial + +// GPU renderer: like the dial widget, the whole gauge geometry (face rim, +// pips, both needles and center cap) is drawn by a single canvas.Shader; +// a SetValue/SetValue2 only writes that needle's angle uniform. Text +// (title, both values, pip labels) stays as canvas.Text layered on top. +// +// All dual dials share one Shader.Name: the painter caches the compiled +// program per name and the widget needs no textures, so every instance +// reuses the same program with its own uniforms. +// +// Conventions match the dial shader: angles are radians, 0 pointing up, +// clockwise positive; the needles sweep Pi15 (270 degrees) from -3/4 pi at +// Min to +3/4 pi at Max. There is no marker overhanging the rim, so the +// shader quad is exactly the dial square. + +const dualDialShaderPreludeGL = "#version 110\n" + +const dualDialShaderPreludeES = `#version 100 +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +` + +const dualDialShaderBody = ` +uniform vec2 frame_size; +uniform vec4 rect_coords; + +uniform float size_d; // dial diameter, logical px +uniform float steps; // pip intervals; steps+1 pips are drawn +uniform float needle_a; // primary needle angle, radians +uniform float needle2_a; // secondary needle angle, radians + +const float PIP_ANG = 2.35619449; // 3/4 pi, the first/last pip angle +const float FACE_ANG = 2.3693; // rim ends just past the end pips, like the canvas.Arc face + +const vec3 FACE_COL = vec3(0.502, 0.502, 0.502); // 0x808080 +const vec3 CENTER_COL = vec3(0.0039, 0.0431, 0.0745); // 0x010B13 +const vec3 NEEDLE_COL = vec3(1.0, 0.4039, 0.0); // 0xFF6700 +const vec3 NEEDLE2_COL = vec3(0.9765, 0.1059, 0.0078); // 0xF91B02 + +// distance to the radial bar at angle a covering radius [r0, r1], half width hw +float radial_d(vec2 p, float a, float r0, float r1, float hw) { + vec2 dir = vec2(sin(a), -cos(a)); + float u = dot(p, dir); + float v = dot(p, vec2(-dir.y, dir.x)); + return length(vec2(u - clamp(u, r0, r1), v)) - hw; +} + +// 1 px anti-aliased coverage of signed distance d (device px) +float aa(float d) { + return clamp(0.5 - d, 0.0, 1.0); +} + +// src-over: lay coverage a of colour c on top; col stays premultiplied +void over(inout vec3 col, inout float alpha, vec3 c, float a) { + col = col * (1.0 - a) + c * a; + alpha = alpha * (1.0 - a) + a; +} + +void main() { + vec2 ext = vec2(rect_coords.y - rect_coords.x, rect_coords.w - rect_coords.z); + vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; + + // the painter expands the quad slightly for edge softness; stay inside + if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > ext.x || p_dev.y > ext.y) { + discard; + } + + float px = ext.x / max(size_d, 1.0); // device px per logical px + float r = 0.5 * ext.x; // dial radius, device px + vec2 p = p_dev - 0.5 * ext; + + float len = length(p); + float theta = atan(p.x, -p.y); // 0 up, clockwise positive + + vec3 col = vec3(0.0); + float alpha = 0.0; + + // pips: strokes are far thinner than the pip spacing, so only the + // nearest pip can cover this pixel - no loop needed + float n = max(steps, 1.0); + float step_a = 4.71238898 / n; // Pi15 between first and last pip + float i = clamp(floor((theta + PIP_ANG) / step_a + 0.5), 0.0, n); + float odd = mod(i, 2.0); + float hw = 0.5 * px * mix(max(2.0, size_d / 80.0), max(2.0, size_d / 200.0), odd); + float rin = mix(0.75, 0.875, odd) * r; + // intersect with a disc one logical px inside the rim edge: the round + // end cap must not poke past the rim, and the AA fringe of the cut has + // to stay under the opaque part of the rim + float d = max(radial_d(p, i * step_a - PIP_ANG, rin, r - px, hw), len - (r - px)); + // green -> yellow -> red, like the CPU pip gradient + float t = i / n; + vec3 pip_col = vec3(clamp(2.0 * t, 0.0, 1.0), clamp(2.0 - 2.0 * t, 0.0, 1.0), 0.0); + over(col, alpha, pip_col, aa(d)); + + // face rim: the ring [0.985r, r] over the pip arc; the angular term is + // the arc length past the rim ends + d = max(abs(len - 0.9925 * r) - 0.0075 * r, (abs(theta) - FACE_ANG) * len); + over(col, alpha, FACE_COL, aa(d)); + + // center cap, diameter r/4 + over(col, alpha, CENTER_COL, aa(len - 0.125 * r)); + + // needles: offset -0.15r, length 1.14r, tips pulled in 2 logical px; + // the primary draws on top + float nhw = 0.5 * px * (size_d / 60.0); + float ntip = 0.99 * r - 2.0 * px; + over(col, alpha, NEEDLE2_COL, aa(radial_d(p, needle2_a, -0.15 * r, ntip, nhw))); + over(col, alpha, NEEDLE_COL, aa(radial_d(p, needle_a, -0.15 * r, ntip, nhw))); + + if (alpha < 0.004) { + discard; + } + gl_FragColor = vec4(col / alpha, alpha); +} +` diff --git a/pkg/widgets/dualdial/dual_dial_shader_test.go b/pkg/widgets/dualdial/dual_dial_shader_test.go new file mode 100644 index 00000000..eb6d6f63 --- /dev/null +++ b/pkg/widgets/dualdial/dual_dial_shader_test.go @@ -0,0 +1,29 @@ +package dualdial + +import ( + "os" + "os/exec" + "path/filepath" + "testing" +) + +// Both shader variants must at least compile; glslangValidator checks them +// against the GLSL 1.10 (desktop) and GLSL ES 1.00 specs. +func TestDualDialShaderSourcesCompile(t *testing.T) { + validator, err := exec.LookPath("glslangValidator") + if err != nil { + t.Skip("glslangValidator not installed") + } + for name, src := range map[string]string{ + "desktop.frag": dualDialShaderPreludeGL + dualDialShaderBody, + "es.frag": dualDialShaderPreludeES + dualDialShaderBody, + } { + p := filepath.Join(t.TempDir(), name) + if err := os.WriteFile(p, []byte(src), 0o644); err != nil { + t.Fatal(err) + } + if out, err := exec.Command(validator, p).CombinedOutput(); err != nil { + t.Fatalf("%s: %v\n%s", name, err, out) + } + } +} From 988e9ac3192b5c1efec2b17f3047cddc99f6ae3e Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 01:02:31 +0200 Subject: [PATCH 033/102] update whatsnew --- pkg/assets/WHATSNEW.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index d8f250a1..259030a9 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -17,6 +17,7 @@ - Added 2d graph for viewing flat maps - Rewrote logplayer plotter to use about 50% less CPU on zoomed out views - Big refactor of the log writing logic to be simpler to maintain and be more performant +- Dial and Dual Dial now uses shaders to draw the faces, dials and pips # 2.1.9 - Updated default T7 preset to include MAF.m_AirFromp_AirInlet From 905822c854a2ad8897d68b0ff64b3f2be1d29ffa Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 11:42:09 +0200 Subject: [PATCH 034/102] revert dial shaders --- pkg/widgets/dial/dial.go | 202 ++++++++---- pkg/widgets/dial/shader/dial.go | 285 +++++++++++++++++ pkg/widgets/dial/{ => shader}/dial_shader.go | 0 .../dial/{ => shader}/dial_shader_test.go | 0 pkg/widgets/dualdial/dual_dial.go | 183 +++++++---- pkg/widgets/dualdial/shader/dual_dial.go | 300 ++++++++++++++++++ .../dualdial/{ => shader}/dual_dial_shader.go | 0 .../{ => shader}/dual_dial_shader_test.go | 0 8 files changed, 849 insertions(+), 121 deletions(-) create mode 100644 pkg/widgets/dial/shader/dial.go rename pkg/widgets/dial/{ => shader}/dial_shader.go (100%) rename pkg/widgets/dial/{ => shader}/dial_shader_test.go (100%) create mode 100644 pkg/widgets/dualdial/shader/dual_dial.go rename pkg/widgets/dualdial/{ => shader}/dual_dial_shader.go (100%) rename pkg/widgets/dualdial/{ => shader}/dual_dial_shader_test.go (100%) diff --git a/pkg/widgets/dial/dial.go b/pkg/widgets/dial/dial.go index acf10f0a..28057828 100644 --- a/pkg/widgets/dial/dial.go +++ b/pkg/widgets/dial/dial.go @@ -23,26 +23,29 @@ type Dial struct { value float64 highestObserved float64 - lastHighestObserved time.Time - - // All dial geometry (face, pips, needle, marker, center) in one object - shader *canvas.Shader + needle *canvas.Line + highestObservedMarker *canvas.Line + lastHighestObserved time.Time + pips []*canvas.Line pipLabels []*canvas.Text + face *canvas.Arc + center *canvas.Circle displayText *canvas.Text titleText *canvas.Text size fyne.Size minsize fyne.Size - diameter float32 - radius float32 - middle fyne.Position - needleRotConst float64 // = common.Pi15/(Max-Min) - lineRotConst float64 // = common.Pi15/steps + diameter float32 + radius float32 + middle fyne.Position + needleOffset, needleLength float32 + needleRotConst float64 // = common.Pi15/(Max-Min) + lineRotConst float64 // = common.Pi15/steps - // Precomputed trig for pip labels: angle_i = lineRotConst*float64(i) - common.Pi43 + // Precomputed trig for pips: angle_i = lineRotConst*float64(i) - common.Pi43 pipSin []float32 pipCos []float32 @@ -92,21 +95,10 @@ func New(cfg *widgets.GaugeConfig) *Dial { c.factor = c.cfg.Max / steps - // Constants - c.needleRotConst = common.Pi15 / totalRange - c.lineRotConst = common.Pi15 / steps - - c.shader = canvas.NewShader( - "txlogger-dial", - []byte(dialShaderPreludeGL+dialShaderBody), - []byte(dialShaderPreludeES+dialShaderBody), - ) - c.shader.Uniforms = map[string]float32{ - "size_d": 100, - "steps": float32(cfg.Steps), - "needle_a": c.needleAngle(0), - "marker_a": c.needleAngle(0), - } + c.face = canvas.NewArc(-135.73, 135.8, 0.985, color.RGBA{0x80, 0x80, 0x80, 0xFF}) + c.center = &canvas.Circle{FillColor: color.RGBA{R: 0x01, G: 0x0B, B: 0x13, A: 0xFF}} + c.needle = &canvas.Line{StrokeColor: color.RGBA{R: 0xFF, G: 0x67, B: 0, A: 0xFF}, StrokeWidth: 3} + c.highestObservedMarker = &canvas.Line{StrokeColor: color.RGBA{R: 216, G: 250, B: 8, A: 0xFF}, StrokeWidth: 6} c.titleText = &canvas.Text{Text: c.cfg.Title, Color: color.RGBA{R: 0xF0, G: 0xF0, B: 0xF0, A: 0xFF}, TextSize: 25} c.titleText.TextStyle.Monospace = true @@ -115,8 +107,27 @@ func New(cfg *widgets.GaugeConfig) *Dial { c.displayText = &canvas.Text{Text: "0", Color: color.RGBA{R: 0x2c, G: 0xfc, B: 0x03, A: 0xFF}, TextSize: 52} c.displayText.Alignment = fyne.TextAlignCenter - // Labels at every other pip; also track the longest label length + // Pip color gradient + // Green to Yellow to Red gradient + // 0% - 50% Green to Yellow + // 50% - 100% Yellow to Red + halfSteps := float64(c.cfg.Steps) / 2.0 + + // Build pips + labels once; also track the longest label length + var col color.RGBA for i := 0; i < c.cfg.Steps+1; i++ { + if float64(i) <= halfSteps { + // Green to Yellow + ratio := float64(i) / halfSteps + col = color.RGBA{R: byte(255 * ratio), G: 255, B: 0, A: 255} + } else { + // Yellow to Red + ratio := (float64(i) - halfSteps) / halfSteps + col = color.RGBA{R: 255, G: byte(255 * (1 - ratio)), B: 0, A: 255} + } + pip := &canvas.Line{StrokeColor: col, StrokeWidth: 2} + c.pips = append(c.pips, pip) + if i%2 == 0 { val := c.cfg.Min + (float64(i)/float64(c.cfg.Steps))*(c.cfg.Max-c.cfg.Min)*c.cfg.GaugeFactor txt := strconv.FormatFloat(val, 'f', c.gaugePrec, 64) @@ -126,6 +137,7 @@ func New(cfg *widgets.GaugeConfig) *Dial { Color: color.RGBA{0xE0, 0xE0, 0xE0, 0xFF}, Alignment: fyne.TextAlignCenter, } + // lbl.TextStyle.Monospace = true if n := len(txt); n > c.maxLabelChars { c.maxLabelChars = n } @@ -135,7 +147,11 @@ func New(cfg *widgets.GaugeConfig) *Dial { } } - // Precompute pip sin/cos for label placement (size-independent) + // Constants + c.needleRotConst = common.Pi15 / totalRange + c.lineRotConst = common.Pi15 / steps + + // Precompute pip sin/cos (size-independent) c.pipSin = make([]float32, c.cfg.Steps+1) c.pipCos = make([]float32, c.cfg.Steps+1) for i := 0; i <= c.cfg.Steps; i++ { @@ -150,14 +166,29 @@ func New(cfg *widgets.GaugeConfig) *Dial { func (c *Dial) GetConfig() *widgets.GaugeConfig { return c.cfg } -// needle angle for a face value; clamped below Min like the CPU renderer, -// free to overshoot above Max -func (c *Dial) needleAngle(value float64) float32 { - normalized := value - c.cfg.Min +// rotate angle (in radians) without refreshing per-object +func (c *Dial) rotateNoRefresh(hand *canvas.Line, rotation float64, offset, length float32) { + s, co := math.Sincos(rotation) + c.applySinCos(hand, float32(s), float32(co), offset, length) +} + +func (c *Dial) applySinCos(hand *canvas.Line, sinRot, cosRot float32, offset, length float32) { + x2 := length * sinRot + y2 := -length * cosRot + offX := offset * sinRot + offY := -offset * cosRot + midxOffX := c.middle.X + offX + midY := c.middle.Y + offY + hand.Position1 = fyne.Position{X: midxOffX, Y: midY} + hand.Position2 = fyne.Position{X: midxOffX + x2, Y: midY + y2} +} + +func (c *Dial) rotateNeedleNoRefresh(hand *canvas.Line, facePosition float64, offset, length float32) { + normalized := facePosition - c.cfg.Min if normalized < 0 { normalized = 0 } - return float32(c.needleRotConst*normalized - common.Pi43) + c.rotateNoRefresh(hand, c.needleRotConst*normalized-common.Pi43, offset, length) } func (c *Dial) SetValue(value float64) { @@ -166,13 +197,21 @@ func (c *Dial) SetValue(value float64) { } c.value = value - c.shader.Uniforms["needle_a"] = c.needleAngle(value) + // Update needle position (no immediate refresh) + c.rotateNeedleNoRefresh(c.needle, value, c.needleOffset, c.needleLength) - // Highest observed marker with lazy reset - if value > c.highestObserved || time.Since(c.lastHighestObserved) > 10*time.Second { + // Highest observed marker with lazy reset; only refresh when it actually moves + markerMoved := false + if value > c.highestObserved { c.highestObserved = value c.lastHighestObserved = time.Now() - c.shader.Uniforms["marker_a"] = c.needleAngle(value) + c.rotateNeedleNoRefresh(c.highestObservedMarker, value, c.radius-2, 6) + markerMoved = true + } else if time.Since(c.lastHighestObserved) > 10*time.Second { + c.highestObserved = value + c.lastHighestObserved = time.Now() + c.rotateNeedleNoRefresh(c.highestObservedMarker, value, c.radius-2, 6) + markerMoved = true } // Update text with minimal allocs; skip refresh if formatted output is unchanged @@ -182,12 +221,18 @@ func (c *Dial) SetValue(value float64) { } else { c.buf = common.AppendFormatFloat(c.buf, c.displayString, value) } - if !common.SameTextBytes(c.displayText.Text, c.buf) { + textChanged := !common.SameTextBytes(c.displayText.Text, c.buf) + if textChanged { c.displayText.Text = string(c.buf) - canvas.Refresh(c.displayText) } - canvas.Refresh(c.shader) + canvas.Refresh(c.needle) + if markerMoved { + canvas.Refresh(c.highestObservedMarker) + } + if textChanged { + canvas.Refresh(c.displayText) + } } func (c *Dial) SetValue2(value float64) { c.SetValue(value) } @@ -208,26 +253,45 @@ func (c *DialRenderer) Layout(space fyne.Size) { c.diameter = fyne.Min(space.Width, space.Height) c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) + c.needleOffset = -c.radius * .15 + c.needleLength = c.radius * 1.14 + + // Stroke sizes + stroke := c.diameter * common.OneSixthieth + midStroke := c.diameter * common.OneEighthieth + smallStroke := c.diameter * common.OneTwohundredth size := fyne.Size{Width: c.diameter, Height: c.diameter} topleft := fyne.NewPos(c.middle.X-c.radius, c.middle.Y-c.radius) - // Shader quad: the dial square padded so the marker overhang isn't clipped - c.shader.Move(topleft.SubtractXY(dialPad, dialPad)) - c.shader.Resize(fyne.Size{Width: c.diameter + 2*dialPad, Height: c.diameter + 2*dialPad}) - c.shader.Uniforms["size_d"] = c.diameter - // Title (no rounding needed) c.titleText.TextSize = float32(int(c.radius * common.OneFourth)) c.titleText.Move(c.middle.Add(fyne.NewPos(0, c.diameter*common.OneFourth))) + // Center element + center := c.radius * common.OneFourth + c.center.Move(c.middle.SubtractXY(center*common.OneHalf, center*common.OneHalf)) + c.center.Resize(fyne.Size{Width: center, Height: center}) + // Display text c.displayText.TextSize = float32(int(c.radius * common.OneThird)) c.displayText.Move(topleft.AddXY(0, c.diameter*common.OneFifth)) c.displayText.Resize(size) - // Labels: reuse precomputed sin/cos, scale with current radius + // Face + needle + c.needle.StrokeWidth = stroke + c.rotateNeedleNoRefresh(c.needle, c.value, c.needleOffset, c.needleLength) + + c.face.Move(c.middle.SubtractXY(c.radius, c.radius)) + c.face.Resize(fyne.Size{Width: c.diameter, Height: c.diameter}) + + // Pips: reuse precomputed sin/cos, scale with current radii + fourthRadius := c.radius * common.OneFourth + eightRadius := c.radius * common.OneEight radius43 := c.radius * common.OneFourth * 3 + radius87 := c.radius * common.OneEight * 7 + + // Label padding and cached box dims (avoid lbl.MinSize per label) labelPad := max(float32(6.0), c.radius*0.14) // Assume monospace, digits only: width ≈ chars * 0.62 * TextSize; height ≈ 1.15 * TextSize @@ -239,20 +303,35 @@ func (c *DialRenderer) Layout(space fyne.Size) { c.labelBoxW = float32(c.maxLabelChars) * float32(charWidthFactor) * labelTextSize c.labelBoxH = float32(heightFactor) * labelTextSize - for i, lbl := range c.pipLabels { - if lbl == nil { - continue + for i, p := range c.pips { + if i%2 == 0 { + p.StrokeWidth = max(2.0, midStroke) + c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius43, fourthRadius-1) + + // Label for long pip + lbl := c.pipLabels[i] + if lbl != nil { + lbl.TextSize = labelTextSize + + // Place label on the INSIDE of the gauge + labelRadius := radius43 - labelPad + cx := c.middle.X + c.pipSin[i]*labelRadius + cy := c.middle.Y - c.pipCos[i]*labelRadius + + boxW := c.labelBoxW + boxH := c.labelBoxH + lbl.Resize(fyne.NewSize(boxW, boxH)) + // lbl.Move(fyne.NewPos(cx-boxW/2, cy-boxH/2)) + lbl.Move(fyne.Position{X: cx - boxW/2, Y: cy - boxH/2}) + } + } else { + p.StrokeWidth = max(2.0, smallStroke) + c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius87, eightRadius-1) } - lbl.TextSize = labelTextSize - - // Place label on the INSIDE of the gauge - labelRadius := radius43 - labelPad - cx := c.middle.X + c.pipSin[i]*labelRadius - cy := c.middle.Y - c.pipCos[i]*labelRadius - - lbl.Resize(fyne.NewSize(c.labelBoxW, c.labelBoxH)) - lbl.Move(fyne.Position{X: cx - c.labelBoxW/2, Y: cy - c.labelBoxH/2}) } + + c.highestObservedMarker.StrokeWidth = max(2.0, midStroke) + c.rotateNeedleNoRefresh(c.highestObservedMarker, c.highestObserved, c.radius-2, 6) } func (c *DialRenderer) MinSize() fyne.Size { return c.minsize } @@ -261,14 +340,17 @@ func (c *DialRenderer) Destroy() {} func (c *DialRenderer) Objects() []fyne.CanvasObject { if c.objects == nil { - objs := make([]fyne.CanvasObject, 0, len(c.pipLabels)+3) - objs = append(objs, c.shader) + objs := make([]fyne.CanvasObject, 0, len(c.pips)+len(c.pipLabels)+7) + for _, v := range c.pips { + objs = append(objs, v) + } for _, t := range c.pipLabels { if t != nil { objs = append(objs, t) } } - objs = append(objs, c.titleText, c.displayText) + objs = append(objs, c.face, c.titleText, c.center, + c.highestObservedMarker, c.needle, c.displayText) c.objects = objs } return c.objects diff --git a/pkg/widgets/dial/shader/dial.go b/pkg/widgets/dial/shader/dial.go new file mode 100644 index 00000000..acf10f0a --- /dev/null +++ b/pkg/widgets/dial/shader/dial.go @@ -0,0 +1,285 @@ +package dial + +import ( + "image/color" + "math" + "strconv" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/common" + "github.com/roffe/txlogger/pkg/widgets" +) + +type Dial struct { + widget.BaseWidget + displayString string + + cfg *widgets.GaugeConfig + + factor float64 + value float64 + highestObserved float64 + + lastHighestObserved time.Time + + // All dial geometry (face, pips, needle, marker, center) in one object + shader *canvas.Shader + + pipLabels []*canvas.Text + + displayText *canvas.Text + titleText *canvas.Text + + size fyne.Size + minsize fyne.Size + + diameter float32 + radius float32 + middle fyne.Position + needleRotConst float64 // = common.Pi15/(Max-Min) + lineRotConst float64 // = common.Pi15/steps + + // Precomputed trig for pip labels: angle_i = lineRotConst*float64(i) - common.Pi43 + pipSin []float32 + pipCos []float32 + + // Fast float formatting + fmtPrec int // precision extracted from displayString like "%.0f", "%.1f", defaults to -1 + gaugePrec int // precision extracted from GaugeTextString like "%.0f", "%.1f", defaults to -1 + buf []byte + + // Label sizing cache (avoids MinSize calls every layout) + maxLabelChars int // longest label length at construction + labelBoxW float32 // computed each layout from TextSize and maxLabelChars + labelBoxH float32 // computed each layout from TextSize +} + +func New(cfg *widgets.GaugeConfig) *Dial { + c := &Dial{ + cfg: cfg, + displayString: "%.0f", + minsize: fyne.NewSize(100, 100), + fmtPrec: -1, + } + c.ExtendBaseWidget(c) + + if cfg.DisplayString != "" { + c.displayString = cfg.DisplayString + if n := common.ParseFixedPrec(c.displayString); n >= 0 { + c.fmtPrec = n + } + } + if cfg.GaugeTextString != "" { + if n := common.ParseFixedPrec(cfg.GaugeTextString); n >= 0 { + c.gaugePrec = n + } + } + if cfg.GaugeFactor == 0 { + cfg.GaugeFactor = 1.0 + } + if cfg.MinSize.Width > 0 && cfg.MinSize.Height > 0 { + c.minsize = cfg.MinSize + } + + steps := float64(cfg.Steps) + totalRange := c.cfg.Max - c.cfg.Min + if totalRange <= 0 { + totalRange = 1 + } + + c.factor = c.cfg.Max / steps + + // Constants + c.needleRotConst = common.Pi15 / totalRange + c.lineRotConst = common.Pi15 / steps + + c.shader = canvas.NewShader( + "txlogger-dial", + []byte(dialShaderPreludeGL+dialShaderBody), + []byte(dialShaderPreludeES+dialShaderBody), + ) + c.shader.Uniforms = map[string]float32{ + "size_d": 100, + "steps": float32(cfg.Steps), + "needle_a": c.needleAngle(0), + "marker_a": c.needleAngle(0), + } + + c.titleText = &canvas.Text{Text: c.cfg.Title, Color: color.RGBA{R: 0xF0, G: 0xF0, B: 0xF0, A: 0xFF}, TextSize: 25} + c.titleText.TextStyle.Monospace = true + c.titleText.Alignment = fyne.TextAlignCenter + + c.displayText = &canvas.Text{Text: "0", Color: color.RGBA{R: 0x2c, G: 0xfc, B: 0x03, A: 0xFF}, TextSize: 52} + c.displayText.Alignment = fyne.TextAlignCenter + + // Labels at every other pip; also track the longest label length + for i := 0; i < c.cfg.Steps+1; i++ { + if i%2 == 0 { + val := c.cfg.Min + (float64(i)/float64(c.cfg.Steps))*(c.cfg.Max-c.cfg.Min)*c.cfg.GaugeFactor + txt := strconv.FormatFloat(val, 'f', c.gaugePrec, 64) + + lbl := &canvas.Text{ + Text: txt, + Color: color.RGBA{0xE0, 0xE0, 0xE0, 0xFF}, + Alignment: fyne.TextAlignCenter, + } + if n := len(txt); n > c.maxLabelChars { + c.maxLabelChars = n + } + c.pipLabels = append(c.pipLabels, lbl) + } else { + c.pipLabels = append(c.pipLabels, nil) + } + } + + // Precompute pip sin/cos for label placement (size-independent) + c.pipSin = make([]float32, c.cfg.Steps+1) + c.pipCos = make([]float32, c.cfg.Steps+1) + for i := 0; i <= c.cfg.Steps; i++ { + ang := c.lineRotConst*float64(i) - common.Pi43 + s, co := math.Sincos(ang) + c.pipSin[i] = float32(s) + c.pipCos[i] = float32(co) + } + + return c +} + +func (c *Dial) GetConfig() *widgets.GaugeConfig { return c.cfg } + +// needle angle for a face value; clamped below Min like the CPU renderer, +// free to overshoot above Max +func (c *Dial) needleAngle(value float64) float32 { + normalized := value - c.cfg.Min + if normalized < 0 { + normalized = 0 + } + return float32(c.needleRotConst*normalized - common.Pi43) +} + +func (c *Dial) SetValue(value float64) { + if value == c.value { + return + } + c.value = value + + c.shader.Uniforms["needle_a"] = c.needleAngle(value) + + // Highest observed marker with lazy reset + if value > c.highestObserved || time.Since(c.lastHighestObserved) > 10*time.Second { + c.highestObserved = value + c.lastHighestObserved = time.Now() + c.shader.Uniforms["marker_a"] = c.needleAngle(value) + } + + // Update text with minimal allocs; skip refresh if formatted output is unchanged + c.buf = c.buf[:0] + if c.fmtPrec >= 0 { + c.buf = strconv.AppendFloat(c.buf, value, 'f', c.fmtPrec, 64) + } else { + c.buf = common.AppendFormatFloat(c.buf, c.displayString, value) + } + if !common.SameTextBytes(c.displayText.Text, c.buf) { + c.displayText.Text = string(c.buf) + canvas.Refresh(c.displayText) + } + + canvas.Refresh(c.shader) +} + +func (c *Dial) SetValue2(value float64) { c.SetValue(value) } + +func (c *Dial) CreateRenderer() fyne.WidgetRenderer { return &DialRenderer{Dial: c} } + +type DialRenderer struct { + *Dial + objects []fyne.CanvasObject +} + +func (c *DialRenderer) Layout(space fyne.Size) { + if c.size == space { + return + } + c.size = space + + c.diameter = fyne.Min(space.Width, space.Height) + c.radius = c.diameter * common.OneHalf + c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) + + size := fyne.Size{Width: c.diameter, Height: c.diameter} + topleft := fyne.NewPos(c.middle.X-c.radius, c.middle.Y-c.radius) + + // Shader quad: the dial square padded so the marker overhang isn't clipped + c.shader.Move(topleft.SubtractXY(dialPad, dialPad)) + c.shader.Resize(fyne.Size{Width: c.diameter + 2*dialPad, Height: c.diameter + 2*dialPad}) + c.shader.Uniforms["size_d"] = c.diameter + + // Title (no rounding needed) + c.titleText.TextSize = float32(int(c.radius * common.OneFourth)) + c.titleText.Move(c.middle.Add(fyne.NewPos(0, c.diameter*common.OneFourth))) + + // Display text + c.displayText.TextSize = float32(int(c.radius * common.OneThird)) + c.displayText.Move(topleft.AddXY(0, c.diameter*common.OneFifth)) + c.displayText.Resize(size) + + // Labels: reuse precomputed sin/cos, scale with current radius + radius43 := c.radius * common.OneFourth * 3 + labelPad := max(float32(6.0), c.radius*0.14) + + // Assume monospace, digits only: width ≈ chars * 0.62 * TextSize; height ≈ 1.15 * TextSize + // This keeps alignment stable and removes per-label measuring. + const charWidthFactor = 0.62 + const heightFactor = 1.15 + + labelTextSize := c.radius * 0.10 + c.labelBoxW = float32(c.maxLabelChars) * float32(charWidthFactor) * labelTextSize + c.labelBoxH = float32(heightFactor) * labelTextSize + + for i, lbl := range c.pipLabels { + if lbl == nil { + continue + } + lbl.TextSize = labelTextSize + + // Place label on the INSIDE of the gauge + labelRadius := radius43 - labelPad + cx := c.middle.X + c.pipSin[i]*labelRadius + cy := c.middle.Y - c.pipCos[i]*labelRadius + + lbl.Resize(fyne.NewSize(c.labelBoxW, c.labelBoxH)) + lbl.Move(fyne.Position{X: cx - c.labelBoxW/2, Y: cy - c.labelBoxH/2}) + } +} + +func (c *DialRenderer) MinSize() fyne.Size { return c.minsize } +func (c *DialRenderer) Refresh() {} +func (c *DialRenderer) Destroy() {} + +func (c *DialRenderer) Objects() []fyne.CanvasObject { + if c.objects == nil { + objs := make([]fyne.CanvasObject, 0, len(c.pipLabels)+3) + objs = append(objs, c.shader) + for _, t := range c.pipLabels { + if t != nil { + objs = append(objs, t) + } + } + objs = append(objs, c.titleText, c.displayText) + c.objects = objs + } + return c.objects +} + +// --- helpers --- + +// max helper that matches your float32 usage +func max(a, b float32) float32 { + if a > b { + return a + } + return b +} diff --git a/pkg/widgets/dial/dial_shader.go b/pkg/widgets/dial/shader/dial_shader.go similarity index 100% rename from pkg/widgets/dial/dial_shader.go rename to pkg/widgets/dial/shader/dial_shader.go diff --git a/pkg/widgets/dial/dial_shader_test.go b/pkg/widgets/dial/shader/dial_shader_test.go similarity index 100% rename from pkg/widgets/dial/dial_shader_test.go rename to pkg/widgets/dial/shader/dial_shader_test.go diff --git a/pkg/widgets/dualdial/dual_dial.go b/pkg/widgets/dualdial/dual_dial.go index 85f9920d..8dc44673 100644 --- a/pkg/widgets/dualdial/dual_dial.go +++ b/pkg/widgets/dualdial/dual_dial.go @@ -23,11 +23,15 @@ type DualDial struct { value float64 value2 float64 - // All gauge geometry (face, pips, both needles, center) in one object - shader *canvas.Shader + needle *canvas.Line + needle2 *canvas.Line + pips []*canvas.Line pipLabels []*canvas.Text + face *canvas.Arc + center *canvas.Circle + displayText *canvas.Text displayText2 *canvas.Text @@ -37,13 +41,14 @@ type DualDial struct { size fyne.Size minsize fyne.Size - diameter float32 - radius float32 - middle fyne.Position - needleRotConst float64 - lineRotConst float64 + diameter float32 + radius float32 + middle fyne.Position + needleOffset, needleLength float32 + needleRotConst float64 + lineRotConst float64 - // cached sin/cos for pip labels (angle_i = lineRotConst*i - common.Pi43) + // cached sin/cos for pips (angle_i = lineRotConst*i - common.Pi43) pipSin []float32 pipCos []float32 @@ -89,24 +94,10 @@ func New(cfg *widgets.GaugeConfig) *DualDial { s.factor = s.cfg.Max / s.steps - totalRange := s.cfg.Max - s.cfg.Min - if totalRange <= 0 { - totalRange = 1 - } - s.needleRotConst = common.Pi15 / totalRange - s.lineRotConst = common.Pi15 / s.steps - - s.shader = canvas.NewShader( - "txlogger-dualdial", - []byte(dualDialShaderPreludeGL+dualDialShaderBody), - []byte(dualDialShaderPreludeES+dualDialShaderBody), - ) - s.shader.Uniforms = map[string]float32{ - "size_d": 100, - "steps": float32(s.steps), - "needle_a": s.needleAngle(0), - "needle2_a": s.needleAngle(0), - } + s.face = canvas.NewArc(-135.73, 135.8, 0.985, color.RGBA{0x80, 0x80, 0x80, 0xFF}) + s.center = &canvas.Circle{FillColor: color.RGBA{R: 0x01, G: 0x0B, B: 0x13, A: 0xFF}} + s.needle = &canvas.Line{StrokeColor: color.RGBA{R: 0xFF, G: 0x67, B: 0, A: 0xFF}, StrokeWidth: 2} + s.needle2 = &canvas.Line{StrokeColor: color.RGBA{R: 249, G: 27, B: 2, A: 255}, StrokeWidth: 2} s.titleText = &canvas.Text{Text: s.cfg.Title, Color: color.RGBA{R: 0xF0, G: 0xF0, B: 0xF0, A: 0xFF}, TextSize: 25} s.titleText.TextStyle.Monospace = true @@ -118,8 +109,25 @@ func New(cfg *widgets.GaugeConfig) *DualDial { s.displayText2 = &canvas.Text{Text: "0", Color: color.RGBA{R: 0xff, G: 0x0, B: 0, A: 0xFF}, TextSize: 35} s.displayText2.Alignment = fyne.TextAlignCenter - // Labels at every other pip; also track the longest label length + // Pip color gradient + // Green to Yellow to Red gradient + // 0% - 50% Green to Yellow + // 50% - 100% Yellow to Red + halfSteps := float64(s.cfg.Steps) / 2.0 + var col color.RGBA for i := 0; i <= int(s.steps); i++ { + if float64(i) <= halfSteps { + // Green to Yellow + ratio := float64(i) / halfSteps + col = color.RGBA{R: byte(255 * ratio), G: 255, B: 0, A: 255} + } else { + // Yellow to Red + ratio := (float64(i) - halfSteps) / halfSteps + col = color.RGBA{R: 255, G: byte(255 * (1 - ratio)), B: 0, A: 255} + } + pip := &canvas.Line{StrokeColor: col, StrokeWidth: 2} + s.pips = append(s.pips, pip) + if i%2 == 0 { val := s.cfg.Min + (float64(i)/float64(s.cfg.Steps))*(s.cfg.Max-s.cfg.Min) txt := strconv.FormatFloat(val, 'f', s.gaugePrec, 64) @@ -128,6 +136,7 @@ func New(cfg *widgets.GaugeConfig) *DualDial { Color: color.RGBA{0xE0, 0xE0, 0xE0, 0xFF}, Alignment: fyne.TextAlignCenter, } + // lbl.TextStyle.Monospace = true if n := len(txt); n > s.maxLabelChars { s.maxLabelChars = n } @@ -137,7 +146,14 @@ func New(cfg *widgets.GaugeConfig) *DualDial { } } - // precompute pip trig for label placement (size independent) + totalRange := s.cfg.Max - s.cfg.Min + if totalRange <= 0 { + totalRange = 1 + } + s.needleRotConst = common.Pi15 / totalRange + s.lineRotConst = common.Pi15 / s.steps + + // precompute pip trig (size independent) s.pipSin = make([]float32, int(s.steps)+1) s.pipCos = make([]float32, int(s.steps)+1) for i := 0; i <= int(s.steps); i++ { @@ -152,14 +168,24 @@ func New(cfg *widgets.GaugeConfig) *DualDial { func (c *DualDial) GetConfig() *widgets.GaugeConfig { return c.cfg } -// needle angle for a face value; clamped below Min like the CPU renderer, -// free to overshoot above Max -func (c *DualDial) needleAngle(value float64) float32 { - normalized := value - c.cfg.Min +func (c *DualDial) rotateNeedleNoRefresh(hand *canvas.Line, facePosition float64, offset, length float32) { + normalized := facePosition - c.cfg.Min if normalized < 0 { normalized = 0 } - return float32(c.needleRotConst*normalized - common.Pi43) + s, co := math.Sincos(c.needleRotConst*normalized - common.Pi43) + c.applySinCos(hand, float32(s), float32(co), offset, length) +} + +func (c *DualDial) applySinCos(hand *canvas.Line, sinRot, cosRot float32, offset, length float32) { + x2 := length * sinRot + y2 := -length * cosRot + offX := offset * sinRot + offY := -offset * cosRot + midxOffX := c.middle.X + offX + midY := c.middle.Y + offY + hand.Position1 = fyne.Position{X: midxOffX, Y: midY} + hand.Position2 = fyne.Position{X: midxOffX + x2, Y: midY + y2} } func (c *DualDial) SetValue(value float64) { @@ -168,7 +194,7 @@ func (c *DualDial) SetValue(value float64) { } c.value = value - c.shader.Uniforms["needle_a"] = c.needleAngle(value) + c.rotateNeedleNoRefresh(c.needle, value, c.needleOffset, c.needleLength) c.buf1 = c.buf1[:0] if c.fmtPrec >= 0 { @@ -176,12 +202,15 @@ func (c *DualDial) SetValue(value float64) { } else { c.buf1 = common.AppendFormatFloat(c.buf1, c.displayString, value) } - if !common.SameTextBytes(c.displayText.Text, c.buf1) { + textChanged := !common.SameTextBytes(c.displayText.Text, c.buf1) + if textChanged { c.displayText.Text = string(c.buf1) - canvas.Refresh(c.displayText) } - canvas.Refresh(c.shader) + canvas.Refresh(c.needle) + if textChanged { + canvas.Refresh(c.displayText) + } } func (c *DualDial) SetValue2(value float64) { @@ -190,7 +219,7 @@ func (c *DualDial) SetValue2(value float64) { } c.value2 = value - c.shader.Uniforms["needle2_a"] = c.needleAngle(value) + c.rotateNeedleNoRefresh(c.needle2, value, c.needleOffset, c.needleLength) c.buf2 = c.buf2[:0] if c.fmtPrec >= 0 { @@ -198,12 +227,15 @@ func (c *DualDial) SetValue2(value float64) { } else { c.buf2 = common.AppendFormatFloat(c.buf2, c.displayString, value) } - if !common.SameTextBytes(c.displayText2.Text, c.buf2) { + textChanged := !common.SameTextBytes(c.displayText2.Text, c.buf2) + if textChanged { c.displayText2.Text = string(c.buf2) - canvas.Refresh(c.displayText2) } - canvas.Refresh(c.shader) + canvas.Refresh(c.needle2) + if textChanged { + canvas.Refresh(c.displayText2) + } } func (c *DualDial) CreateRenderer() fyne.WidgetRenderer { return &DualDialRenderer{DualDial: c} } @@ -223,17 +255,24 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) + c.needleOffset = -c.radius * .15 + c.needleLength = c.radius * 1.14 + + stroke := c.diameter * common.OneSixthieth + midStroke := c.diameter * common.OneEighthieth + smallStroke := c.diameter * common.OneTwohundredth + size := fyne.Size{Width: c.diameter, Height: c.diameter} topleft := fyne.NewPos(c.middle.X-c.radius, c.middle.Y-c.radius) - c.shader.Move(topleft) - c.shader.Resize(size) - c.shader.Uniforms["size_d"] = c.diameter - // Title & display text sizes (no math.Round needed) c.titleText.TextSize = c.radius * common.OneFourth c.titleText.Move(c.middle.Add(fyne.NewPos(0, c.diameter*common.OneFourth))) + center := c.radius * common.OneFourth + c.center.Move(c.middle.SubtractXY(center*common.OneHalf, center*common.OneHalf)) + c.center.Resize(fyne.Size{Width: center, Height: center}) + sixthDiameter := c.diameter * common.OneSixth c.displayText.TextSize = c.radius * common.OneThird @@ -244,8 +283,20 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { c.displayText2.Move(topleft.AddXY(0, -sixthDiameter)) c.displayText2.Resize(size) - // Labels: reuse precomputed trig scaled by current radius + // Needles & face + c.needle.StrokeWidth = stroke + c.needle2.StrokeWidth = stroke + c.rotateNeedleNoRefresh(c.needle, c.value, c.needleOffset, c.needleLength) + c.rotateNeedleNoRefresh(c.needle2, c.value2, c.needleOffset, c.needleLength) + + c.face.Move(c.middle.SubtractXY(c.radius, c.radius)) + c.face.Resize(fyne.Size{Width: c.diameter, Height: c.diameter}) + + // Pips using precomputed trig scaled by current radius + fourthRadius := c.radius * common.OneFourth + eightRadius := c.radius * common.OneEight radius43 := c.radius * common.OneFourth * 3 + radius87 := c.radius * common.OneEight * 7 // Label padding and cached box dims (avoid lbl.MinSize per label) labelPad := max(float32(6.0), c.radius*0.14) @@ -255,19 +306,27 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { c.labelBoxW = float32(c.maxLabelChars) * float32(charWidthFactor) * labelTextSize c.labelBoxH = float32(heightFactor) * labelTextSize - for i, lbl := range c.pipLabels { - if lbl == nil { - continue + for i, p := range c.pips { + if i%2 == 0 { + p.StrokeWidth = max(2.0, midStroke) + c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius43, fourthRadius-1) + // Label for long pip (uniform box; no MinSize) + lbl := c.pipLabels[i] + if lbl != nil { + lbl.TextSize = labelTextSize + // place inside the gauge slightly inward from long pip inner end + labelRadius := radius43 - labelPad + cx := c.middle.X + c.pipSin[i]*labelRadius + cy := c.middle.Y - c.pipCos[i]*labelRadius + boxW := c.labelBoxW + boxH := c.labelBoxH + lbl.Resize(fyne.NewSize(boxW, boxH)) + lbl.Move(fyne.NewPos(cx-boxW/2, cy-boxH/2)) + } + } else { + p.StrokeWidth = max(2.0, smallStroke) + c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius87, eightRadius-1) } - lbl.TextSize = labelTextSize - - // place inside the gauge slightly inward from long pip inner end - labelRadius := radius43 - labelPad - cx := c.middle.X + c.pipSin[i]*labelRadius - cy := c.middle.Y - c.pipCos[i]*labelRadius - - lbl.Resize(fyne.NewSize(c.labelBoxW, c.labelBoxH)) - lbl.Move(fyne.NewPos(cx-c.labelBoxW/2, cy-c.labelBoxH/2)) } } @@ -277,14 +336,16 @@ func (c *DualDialRenderer) Destroy() {} func (c *DualDialRenderer) Objects() []fyne.CanvasObject { if c.objects == nil { - objs := make([]fyne.CanvasObject, 0, len(c.pipLabels)+4) - objs = append(objs, c.shader) + objs := make([]fyne.CanvasObject, 0, len(c.pips)+len(c.pipLabels)+7) + for _, v := range c.pips { + objs = append(objs, v) + } for _, v := range c.pipLabels { if v != nil { objs = append(objs, v) } } - objs = append(objs, c.titleText, c.displayText, c.displayText2) + objs = append(objs, c.face, c.titleText, c.center, c.needle2, c.needle, c.displayText, c.displayText2) c.objects = objs } return c.objects diff --git a/pkg/widgets/dualdial/shader/dual_dial.go b/pkg/widgets/dualdial/shader/dual_dial.go new file mode 100644 index 00000000..85f9920d --- /dev/null +++ b/pkg/widgets/dualdial/shader/dual_dial.go @@ -0,0 +1,300 @@ +package dualdial + +import ( + "image/color" + "math" + "strconv" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/common" + "github.com/roffe/txlogger/pkg/widgets" +) + +type DualDial struct { + widget.BaseWidget + + cfg *widgets.GaugeConfig + + titleText *canvas.Text + displayString string + + value float64 + value2 float64 + + // All gauge geometry (face, pips, both needles, center) in one object + shader *canvas.Shader + + pipLabels []*canvas.Text + + displayText *canvas.Text + displayText2 *canvas.Text + + steps float64 + factor float64 + + size fyne.Size + minsize fyne.Size + + diameter float32 + radius float32 + middle fyne.Position + needleRotConst float64 + lineRotConst float64 + + // cached sin/cos for pip labels (angle_i = lineRotConst*i - common.Pi43) + pipSin []float32 + pipCos []float32 + + // fast float formatting buffers + fmtPrec int + gaugePrec int + buf1 []byte + buf2 []byte + + // Label sizing cache (avoid per-label MinSize on each layout) + maxLabelChars int + labelBoxW float32 + labelBoxH float32 +} + +func New(cfg *widgets.GaugeConfig) *DualDial { + s := &DualDial{ + cfg: cfg, + steps: 10, + displayString: "%.0f", + minsize: fyne.NewSize(100, 100), + fmtPrec: -1, + } + s.ExtendBaseWidget(s) + + if cfg.Steps > 0 { + s.steps = float64(cfg.Steps) + } + if cfg.DisplayString != "" { + s.displayString = cfg.DisplayString + if n := common.ParseFixedPrec(s.displayString); n >= 0 { + s.fmtPrec = n + } + } + if cfg.GaugeTextString != "" { + if n := common.ParseFixedPrec(cfg.GaugeTextString); n >= 0 { + s.gaugePrec = n + } + } + if cfg.MinSize.Width > 0 && cfg.MinSize.Height > 0 { + s.minsize = cfg.MinSize + } + + s.factor = s.cfg.Max / s.steps + + totalRange := s.cfg.Max - s.cfg.Min + if totalRange <= 0 { + totalRange = 1 + } + s.needleRotConst = common.Pi15 / totalRange + s.lineRotConst = common.Pi15 / s.steps + + s.shader = canvas.NewShader( + "txlogger-dualdial", + []byte(dualDialShaderPreludeGL+dualDialShaderBody), + []byte(dualDialShaderPreludeES+dualDialShaderBody), + ) + s.shader.Uniforms = map[string]float32{ + "size_d": 100, + "steps": float32(s.steps), + "needle_a": s.needleAngle(0), + "needle2_a": s.needleAngle(0), + } + + s.titleText = &canvas.Text{Text: s.cfg.Title, Color: color.RGBA{R: 0xF0, G: 0xF0, B: 0xF0, A: 0xFF}, TextSize: 25} + s.titleText.TextStyle.Monospace = true + s.titleText.Alignment = fyne.TextAlignCenter + + s.displayText = &canvas.Text{Text: "0", Color: color.RGBA{R: 0x2c, G: 0xfc, B: 0x03, A: 0xFF}, TextSize: 52} + s.displayText.Alignment = fyne.TextAlignCenter + + s.displayText2 = &canvas.Text{Text: "0", Color: color.RGBA{R: 0xff, G: 0x0, B: 0, A: 0xFF}, TextSize: 35} + s.displayText2.Alignment = fyne.TextAlignCenter + + // Labels at every other pip; also track the longest label length + for i := 0; i <= int(s.steps); i++ { + if i%2 == 0 { + val := s.cfg.Min + (float64(i)/float64(s.cfg.Steps))*(s.cfg.Max-s.cfg.Min) + txt := strconv.FormatFloat(val, 'f', s.gaugePrec, 64) + lbl := &canvas.Text{ + Text: txt, + Color: color.RGBA{0xE0, 0xE0, 0xE0, 0xFF}, + Alignment: fyne.TextAlignCenter, + } + if n := len(txt); n > s.maxLabelChars { + s.maxLabelChars = n + } + s.pipLabels = append(s.pipLabels, lbl) + } else { + s.pipLabels = append(s.pipLabels, nil) + } + } + + // precompute pip trig for label placement (size independent) + s.pipSin = make([]float32, int(s.steps)+1) + s.pipCos = make([]float32, int(s.steps)+1) + for i := 0; i <= int(s.steps); i++ { + ang := s.lineRotConst*float64(i) - common.Pi43 + sinA, cosA := math.Sincos(ang) + s.pipSin[i] = float32(sinA) + s.pipCos[i] = float32(cosA) + } + + return s +} + +func (c *DualDial) GetConfig() *widgets.GaugeConfig { return c.cfg } + +// needle angle for a face value; clamped below Min like the CPU renderer, +// free to overshoot above Max +func (c *DualDial) needleAngle(value float64) float32 { + normalized := value - c.cfg.Min + if normalized < 0 { + normalized = 0 + } + return float32(c.needleRotConst*normalized - common.Pi43) +} + +func (c *DualDial) SetValue(value float64) { + if value == c.value { + return + } + c.value = value + + c.shader.Uniforms["needle_a"] = c.needleAngle(value) + + c.buf1 = c.buf1[:0] + if c.fmtPrec >= 0 { + c.buf1 = strconv.AppendFloat(c.buf1, value, 'f', c.fmtPrec, 64) + } else { + c.buf1 = common.AppendFormatFloat(c.buf1, c.displayString, value) + } + if !common.SameTextBytes(c.displayText.Text, c.buf1) { + c.displayText.Text = string(c.buf1) + canvas.Refresh(c.displayText) + } + + canvas.Refresh(c.shader) +} + +func (c *DualDial) SetValue2(value float64) { + if value == c.value2 { + return + } + c.value2 = value + + c.shader.Uniforms["needle2_a"] = c.needleAngle(value) + + c.buf2 = c.buf2[:0] + if c.fmtPrec >= 0 { + c.buf2 = strconv.AppendFloat(c.buf2, value, 'f', c.fmtPrec, 64) + } else { + c.buf2 = common.AppendFormatFloat(c.buf2, c.displayString, value) + } + if !common.SameTextBytes(c.displayText2.Text, c.buf2) { + c.displayText2.Text = string(c.buf2) + canvas.Refresh(c.displayText2) + } + + canvas.Refresh(c.shader) +} + +func (c *DualDial) CreateRenderer() fyne.WidgetRenderer { return &DualDialRenderer{DualDial: c} } + +type DualDialRenderer struct { + *DualDial + objects []fyne.CanvasObject +} + +func (c *DualDialRenderer) Layout(space fyne.Size) { + if c.size == space { + return + } + c.size = space + + c.diameter = fyne.Min(space.Width, space.Height) + c.radius = c.diameter * common.OneHalf + c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) + + size := fyne.Size{Width: c.diameter, Height: c.diameter} + topleft := fyne.NewPos(c.middle.X-c.radius, c.middle.Y-c.radius) + + c.shader.Move(topleft) + c.shader.Resize(size) + c.shader.Uniforms["size_d"] = c.diameter + + // Title & display text sizes (no math.Round needed) + c.titleText.TextSize = c.radius * common.OneFourth + c.titleText.Move(c.middle.Add(fyne.NewPos(0, c.diameter*common.OneFourth))) + + sixthDiameter := c.diameter * common.OneSixth + + c.displayText.TextSize = c.radius * common.OneThird + c.displayText.Move(topleft.AddXY(0, c.diameter*common.OneFifth)) + c.displayText.Resize(size) + + c.displayText2.TextSize = c.radius * common.OneThird + c.displayText2.Move(topleft.AddXY(0, -sixthDiameter)) + c.displayText2.Resize(size) + + // Labels: reuse precomputed trig scaled by current radius + radius43 := c.radius * common.OneFourth * 3 + + // Label padding and cached box dims (avoid lbl.MinSize per label) + labelPad := max(float32(6.0), c.radius*0.14) + const charWidthFactor = 0.62 + const heightFactor = 1.15 + labelTextSize := c.radius * 0.10 + c.labelBoxW = float32(c.maxLabelChars) * float32(charWidthFactor) * labelTextSize + c.labelBoxH = float32(heightFactor) * labelTextSize + + for i, lbl := range c.pipLabels { + if lbl == nil { + continue + } + lbl.TextSize = labelTextSize + + // place inside the gauge slightly inward from long pip inner end + labelRadius := radius43 - labelPad + cx := c.middle.X + c.pipSin[i]*labelRadius + cy := c.middle.Y - c.pipCos[i]*labelRadius + + lbl.Resize(fyne.NewSize(c.labelBoxW, c.labelBoxH)) + lbl.Move(fyne.NewPos(cx-c.labelBoxW/2, cy-c.labelBoxH/2)) + } +} + +func (c *DualDialRenderer) MinSize() fyne.Size { return c.minsize } +func (c *DualDialRenderer) Refresh() {} +func (c *DualDialRenderer) Destroy() {} + +func (c *DualDialRenderer) Objects() []fyne.CanvasObject { + if c.objects == nil { + objs := make([]fyne.CanvasObject, 0, len(c.pipLabels)+4) + objs = append(objs, c.shader) + for _, v := range c.pipLabels { + if v != nil { + objs = append(objs, v) + } + } + objs = append(objs, c.titleText, c.displayText, c.displayText2) + c.objects = objs + } + return c.objects +} + +// --- helpers --- + +func max(a, b float32) float32 { + if a > b { + return a + } + return b +} diff --git a/pkg/widgets/dualdial/dual_dial_shader.go b/pkg/widgets/dualdial/shader/dual_dial_shader.go similarity index 100% rename from pkg/widgets/dualdial/dual_dial_shader.go rename to pkg/widgets/dualdial/shader/dual_dial_shader.go diff --git a/pkg/widgets/dualdial/dual_dial_shader_test.go b/pkg/widgets/dualdial/shader/dual_dial_shader_test.go similarity index 100% rename from pkg/widgets/dualdial/dual_dial_shader_test.go rename to pkg/widgets/dualdial/shader/dual_dial_shader_test.go From e2f8a857303c46cb3bb7a097f83f1c9c90d59957 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 11:42:39 +0200 Subject: [PATCH 035/102] make render mode configurable --- pkg/widgets/logplayer/logplayer.go | 8 +- pkg/widgets/mapviewer/mapviewer.go | 1 + pkg/widgets/mapviewer/mapviewer_opts.go | 5 +- pkg/widgets/meshgrid/meshgrid_render_test.go | 8 +- pkg/widgets/meshgrid/meshgrid_widget.go | 40 +-- pkg/widgets/plotter/plotter.go | 22 +- pkg/widgets/plotter/plotter_shader.go | 3 + pkg/widgets/plotter/plotter_shader_test.go | 4 +- pkg/widgets/settings/settings.go | 244 +----------------- pkg/widgets/settings/settings_getters.go | 251 +++++++++++++++++++ pkg/widgets/settings/settings_internal.go | 33 +++ pkg/widgets/settings/settings_tabs.go | 85 ++++--- pkg/windows/mainWindow.go | 5 +- pkg/windows/mainWindow_menu.go | 1 + 14 files changed, 404 insertions(+), 306 deletions(-) create mode 100644 pkg/widgets/settings/settings_getters.go diff --git a/pkg/widgets/logplayer/logplayer.go b/pkg/widgets/logplayer/logplayer.go index 9dfc717e..b2be8c96 100644 --- a/pkg/widgets/logplayer/logplayer.go +++ b/pkg/widgets/logplayer/logplayer.go @@ -73,9 +73,10 @@ type logplayerObjects struct { } type Config struct { - EBus *bus.Controller[string, float64] - Logfile logfile.Logfile - TimeSetter func(time.Time) + EBus *bus.Controller[string, float64] + Logfile logfile.Logfile + TimeSetter func(time.Time) + PlotterRenderer plotter.PlotBackend } func New(cfg *Config) *Logplayer { @@ -258,6 +259,7 @@ func (l *Logplayer) render() { l.objs.positionSlider.Refresh() l.control(&controlMsg{Op: OpSeek, Pos: int(pos)}) }), + plotter.WithRenderer(l.cfg.PlotterRenderer), ) } diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index 97cd5c56..b92b9494 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -258,6 +258,7 @@ func (mv *MapViewer) render() fyne.CanvasObject { mv.numColumns, mv.numRows, mv.colorMode, + mv.cfg.MeshRenderer, ) if mv.cfg.OnMouseDown != nil { diff --git a/pkg/widgets/mapviewer/mapviewer_opts.go b/pkg/widgets/mapviewer/mapviewer_opts.go index f7065139..d1ea6361 100644 --- a/pkg/widgets/mapviewer/mapviewer_opts.go +++ b/pkg/widgets/mapviewer/mapviewer_opts.go @@ -3,6 +3,7 @@ package mapviewer import ( "fyne.io/fyne/v2" "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/widgets/meshgrid" ) type Config struct { @@ -27,7 +28,9 @@ type Config struct { OnUpdateCell func(idx int, value []float64) OnMouseDown func() - MeshView bool + MeshView bool + MeshRenderer meshgrid.RenderBackend + Editable bool CursorFollowCrosshair bool diff --git a/pkg/widgets/meshgrid/meshgrid_render_test.go b/pkg/widgets/meshgrid/meshgrid_render_test.go index 56524fb6..3b04bf0e 100644 --- a/pkg/widgets/meshgrid/meshgrid_render_test.go +++ b/pkg/widgets/meshgrid/meshgrid_render_test.go @@ -21,7 +21,7 @@ func testGrid(t testing.TB) *Meshgrid { values[i*cols+j] = 100 / (1 + x*x + y*y) // central hump } } - m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal) + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal, backendFromEnv()) if err != nil { t.Fatal(err) } @@ -46,7 +46,7 @@ func TestRenderRotated(t *testing.T) { values[i*cols+j] = 10 + 100/(1+x*x+y*y) // spike near one corner } } - m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal) + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal, backendFromEnv()) if err != nil { t.Fatal(err) } @@ -95,7 +95,7 @@ func BenchmarkDrawSurface(b *testing.B) { // usePolyBackend switches a test grid to the polygon renderer (the default // is the shader backend, whose per-frame work happens on the GPU). func usePolyBackend(m *Meshgrid) { - m.backend = backendPolygons + m.backend = BackendPolygons m.initPolygons() } @@ -129,7 +129,7 @@ func TestPolygonDegenerateQuads(t *testing.T) { values[i*cols+j] = float64((i / 4) * 100) } } - m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal) + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal, backendFromEnv()) if err != nil { t.Fatal(err) } diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index 2fc4a904..a7382700 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -28,28 +28,28 @@ var _ fyne.Widget = (*Meshgrid)(nil) // renderBackend selects how the mesh is drawn. The GPU shader is the // default; TXLOGGER_MESH_RENDERER=poly|image selects the older paths. -type renderBackend int +type RenderBackend int const ( // backendShader ray-casts the whole mesh in one fragment shader // (meshgrid_shader.go); per-frame CPU cost is a uniform update. - backendShader renderBackend = iota + BackendShader RenderBackend = iota // backendPolygons draws one canvas.ArbitraryPolygon per cell // (meshgrid_poly.go). - backendPolygons + BackendPolygons // backendImage rasterizes on the CPU into a canvas.Image // (meshgrid_draw.go). - backendImage + BackendImage ) -func backendFromEnv() renderBackend { +func backendFromEnv() RenderBackend { switch os.Getenv("TXLOGGER_MESH_RENDERER") { case "poly": - return backendPolygons + return BackendPolygons case "image": - return backendImage + return BackendImage default: - return backendShader + return BackendShader } } @@ -75,7 +75,7 @@ type Meshgrid struct { scratchLines []lineSegment scratchQuads []quadRef - backend renderBackend + backend RenderBackend // Shader backend (meshgrid_shader.go): the whole mesh in one object. shader *canvas.Shader @@ -138,7 +138,7 @@ var ( const cursorRadius = 6 // NewMeshgrid creates a new Meshgrid given width, height, depth and spacing. -func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int, colorBlindMode colors.ColorBlindMode) (*Meshgrid, error) { +func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int, colorBlindMode colors.ColorBlindMode, backend RenderBackend) (*Meshgrid, error) { cols = max(1, cols) rows = max(1, rows) // Check if the provided values slice has the correct number of elements @@ -172,7 +172,7 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int colorMode: colorBlindMode, - backend: backendFromEnv(), + backend: backend, } m.createVertices() @@ -209,9 +209,9 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int m.initAxisObjects() switch m.backend { - case backendShader: + case BackendShader: m.initShader() - case backendPolygons: + case BackendPolygons: m.initPolygons() } @@ -463,14 +463,14 @@ func (m *Meshgrid) Refresh() { func (m *Meshgrid) refresh() { switch m.backend { - case backendShader: + case BackendShader: // All per-frame state lives in shader uniforms; the GPU re-renders // from them on the next paint. Only the overlays move on the CPU. m.updateShaderUniforms() m.updateAxisObjects() m.moveCursor() canvas.Refresh(m) - case backendPolygons: + case BackendPolygons: // Geometry/color updates on the reusable polygons; the canvas.Refresh // marks the scene dirty so color-only changes repaint too. m.updatePolygons() @@ -502,7 +502,7 @@ func (m *Meshgrid) throttledRefresh() { } func (m *Meshgrid) CreateRenderer() fyne.WidgetRenderer { - if m.backend != backendImage { + if m.backend != BackendImage { // Text measuring needs a driver, so the labels can't be sized in the // constructor (tests build widgets without an app). for _, t := range m.axisLabels { @@ -523,9 +523,9 @@ func (m *meshgridRenderer) Layout(size fyne.Size) { } m.MG.size = size switch m.MG.backend { - case backendShader: + case BackendShader: m.MG.shader.Resize(size) - case backendImage: + case BackendImage: m.MG.image.Resize(size) } m.MG.throttledRefresh() @@ -544,7 +544,7 @@ func (m *meshgridRenderer) Destroy() { func (m *meshgridRenderer) Objects() []fyne.CanvasObject { switch m.MG.backend { - case backendShader: + case BackendShader: if m.objects == nil { m.MG.updateAxisObjects() objs := []fyne.CanvasObject{m.MG.shader} @@ -554,7 +554,7 @@ func (m *meshgridRenderer) Objects() []fyne.CanvasObject { m.objects = append(objs, m.MG.cursor) } return m.objects - case backendPolygons: + case BackendPolygons: // updatePolygons rebuilds the list in painter's order every frame; // populate it here for the first paint. if len(m.MG.polyObjects) == 0 { diff --git a/pkg/widgets/plotter/plotter.go b/pkg/widgets/plotter/plotter.go index baed1c01..4439f4bb 100644 --- a/pkg/widgets/plotter/plotter.go +++ b/pkg/widgets/plotter/plotter.go @@ -5,7 +5,6 @@ import ( "image" "image/color" "log" - "os" "sort" "sync" "sync/atomic" @@ -33,17 +32,17 @@ type PlotterControl interface { // plotBackend selects how the plot is drawn. The GPU shader is the default; // TXLOGGER_PLOT_RENDERER=image selects the CPU rasterizer, which is also the // automatic fallback when the log does not fit the shader's texture layout. -type plotBackend int +type PlotBackend int const ( - plotBackendImage plotBackend = iota - plotBackendShader + PlotBackendImage PlotBackend = iota + PlotBackendShader ) type Plotter struct { widget.BaseWidget - backend plotBackend + backend PlotBackend shader *canvas.Shader // plotObj is the canvas object showing the plot: shader on the GPU // backend, canvasImage on the image backend. @@ -109,6 +108,12 @@ func WithOrder(order []string) PlotterOpt { } } +func WithRenderer(renderer PlotBackend) PlotterOpt { + return func(p *Plotter) { + p.backend = renderer + } +} + func NewPlotter(values map[string][]float64, opts ...PlotterOpt) *Plotter { p := &Plotter{ values: values, @@ -200,8 +205,7 @@ func NewPlotter(values map[string][]float64, opts ...PlotterOpt) *Plotter { p.dataPointsToShow = min(p.dataLength, 250.0) p.plotObj = p.canvasImage - if os.Getenv("TXLOGGER_PLOT_RENDERER") != "image" && p.initShader() { - p.backend = plotBackendShader + if p.backend == PlotBackendShader && p.initShader() { p.plotObj = p.shader } @@ -317,7 +321,7 @@ func (p *Plotter) seekTo(pos int) { obj.Refresh() } - if p.backend == plotBackendShader { + if p.backend == PlotBackendShader { // The view window moved; the GPU re-renders from two uniforms. p.updateShaderView() p.layoutCursor() @@ -368,7 +372,7 @@ func (p *Plotter) drawImage() { } func (p *Plotter) refreshImage(goroutine bool) { - if p.backend == plotBackendShader { + if p.backend == PlotBackendShader { // Legend toggles/recolors, hover and zoom all funnel through here; // the metadata texture is 4xN so rebuilding it unconditionally is // cheap, and the painter uploads it only because it is a new image. diff --git a/pkg/widgets/plotter/plotter_shader.go b/pkg/widgets/plotter/plotter_shader.go index de4ef813..5b828140 100644 --- a/pkg/widgets/plotter/plotter_shader.go +++ b/pkg/widgets/plotter/plotter_shader.go @@ -4,6 +4,7 @@ import ( "fmt" "image" "image/color" + "log" "sync/atomic" "fyne.io/fyne/v2/canvas" @@ -230,6 +231,8 @@ void main() { // many series, or a log too long for the texture budget); the caller then // stays on the image backend. func (p *Plotter) initShader() bool { + log.Println("Init plotter shaders") + if len(p.ts) == 0 || len(p.ts) > plotMaxSeries { return false } diff --git a/pkg/widgets/plotter/plotter_shader_test.go b/pkg/widgets/plotter/plotter_shader_test.go index 9d30e17b..649ec92a 100644 --- a/pkg/widgets/plotter/plotter_shader_test.go +++ b/pkg/widgets/plotter/plotter_shader_test.go @@ -12,7 +12,7 @@ import ( func shaderPlotter(t testing.TB, numSeries, numPoints int) *Plotter { t.Helper() p := NewPlotter(benchValues(numSeries, numPoints)) - if p.backend != plotBackendShader { + if p.backend != PlotBackendShader { t.Fatal("shader backend not selected") } return p @@ -132,7 +132,7 @@ func TestPlotShaderMeta(t *testing.T) { // Logs that exceed the texture budget must fall back to the image backend. func TestPlotShaderFallback(t *testing.T) { p := NewPlotter(benchValues(2, plotTexW*plotTexMaxH/2+1)) - if p.backend != plotBackendImage { + if p.backend != PlotBackendImage { t.Fatal("oversized log did not fall back to the image backend") } if p.plotObj != p.canvasImage { diff --git a/pkg/widgets/settings/settings.go b/pkg/widgets/settings/settings.go index 81417649..eb93c302 100644 --- a/pkg/widgets/settings/settings.go +++ b/pkg/widgets/settings/settings.go @@ -1,13 +1,10 @@ package settings import ( - "errors" "fmt" - "log" "os" "slices" "sort" - "strconv" "strings" "sync" @@ -17,16 +14,6 @@ import ( "fyne.io/fyne/v2/widget" "github.com/roffe/gocan" "github.com/roffe/gocan/proto" - "github.com/roffe/txlogger/pkg/colors" - "github.com/roffe/txlogger/pkg/common" - "github.com/roffe/txlogger/pkg/datalogger" - "github.com/roffe/txlogger/pkg/ota" - "github.com/roffe/txlogger/pkg/wbl/aem" - "github.com/roffe/txlogger/pkg/wbl/ecumaster" - "github.com/roffe/txlogger/pkg/wbl/innovate" - "github.com/roffe/txlogger/pkg/wbl/plx" - "github.com/roffe/txlogger/pkg/wbl/stag" - "github.com/roffe/txlogger/pkg/wbl/zeitronix" "github.com/roffe/txlogger/pkg/widgets/txconfigurator" "go.bug.st/serial/enumerator" ) @@ -56,6 +43,9 @@ const ( prefsUseADScanner = "useADScanner" prefsColorBlindMode = "colorBlindMode" + prefsPlotterRenderer = "plotterRenderer" + prefsMeshRenderer = "meshRenderer" + // CAN prefsAdapter = "adapter" prefsPort = "port" @@ -104,6 +94,10 @@ type Widget struct { useMPH *widget.Check swapRPMandSpeed *widget.Check colorBlindMode *widget.Select + // Graphics + plotRendererSelect *widget.Select + meshRendererSelect *widget.Select + // can settings debugCheckbox *widget.Check adapterSelector *widget.Select @@ -187,6 +181,10 @@ func (sw *Widget) CreateRenderer() fyne.WidgetRenderer { sw.colorBlindMode = sw.newColorBlindMode() sw.wblSelectContainer = sw.newWBLSelector() + // Graphics + sw.plotRendererSelect = sw.newPlotRendererSelect() + sw.meshRendererSelect = sw.newMeshRendererSelect() + // CAN sw.adapterSelector = sw.newAdapterSelector() sw.portSelector = sw.newPortSelector() @@ -210,6 +208,7 @@ func (sw *Widget) CreateRenderer() fyne.WidgetRenderer { tabs := container.NewAppTabs() tabs.Append(sw.generalTab()) + tabs.Append(sw.graphicsTab()) tabs.Append(sw.canTab()) tabs.Append(sw.loggingTab()) tabs.Append(sw.dashboardTab()) @@ -302,222 +301,3 @@ func (c *Widget) Enable() { } } } - -func (cs *Widget) GetAdapter(ecuType string) (gocan.Adapter, error) { - return cs.GetAdapterWithExtraFilters(ecuType, []uint32{}) -} - -func (cs *Widget) GetAdapterWithExtraFilters(ecuType string, filters []uint32) (gocan.Adapter, error) { - debug := fyne.CurrentApp().Preferences().Bool(prefsDebug) - port := fyne.CurrentApp().Preferences().String(prefsPort) - - baudstring := fyne.CurrentApp().Preferences().String(prefsSpeed) - switch baudstring { - case "1mbit": - baudstring = "1000000" - case "2mbit": - baudstring = "2000000" - case "3mbit": - baudstring = "3000000" - } - - if baudstring == "" { - baudstring = "1000000" - } - - baudrate, err := strconv.Atoi(baudstring) - if err != nil { - return nil, err - } - adapterName := fyne.CurrentApp().Preferences().String(prefsAdapter) - - if adapterName == "" { - return nil, errors.New("Select CANbus adapter in settings") //lint:ignore ST1005 This is ok - } - - if ad, found := cs.adapters[adapterName]; found { - if ad.RequiresSerialPort { - if port == "" { - return nil, errors.New("Select port in setings") //lint:ignore ST1005 This is ok - } - if baudstring == "" { - return nil, errors.New("Select port speed in settings") //lint:ignore ST1005 This is ok - } - } - } - - var canFilter []uint32 - var canRate float64 - - switch ecuType { - case "T5", "Trionic 5": - canFilter = []uint32{0xC} - canRate = 615.384 - case "T7", "Trionic 7": - if strings.Contains(adapterName, "ELM327") || strings.Contains(adapterName, "STN") || strings.Contains(adapterName, "OBDLink") || strings.HasSuffix(adapterName, "Wifi") { - canFilter = []uint32{0x238, 0x258, 0x270} - } else { - canFilter = []uint32{0x1A0, 0x238, 0x258, 0x270, 0x280, 0x3A0, 0x664, 0x665} - } - if fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") == "CAN" { - canFilter = append(canFilter, 0x180) - } - canRate = 500 - case "T8", "Trionic 8", "Trionic 8 MCP", "Z22SE", "Z22SE MCP": - if strings.Contains(adapterName, "ELM327") || strings.Contains(adapterName, "STN") || strings.Contains(adapterName, "OBDLink") { - canFilter = []uint32{0x5E8, 0x7E8} - } else { - canFilter = []uint32{0x5E8, 0x7E8, 0x664, 0x665} - } - if fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") == "CAN" { - canFilter = append(canFilter, 0x180) - } - canFilter = append(canFilter, filters...) - - canRate = 500 - } - - cfg := &gocan.AdapterConfig{ - Port: port, - PortBaudrate: baudrate, - CANRate: canRate, - CANFilter: canFilter, - Debug: debug, - PrintVersion: true, - } - - if strings.HasPrefix(adapterName, "J2534") { // || strings.HasPrefix(adapterName, "CANlib") { - return gocan.NewGWClient(adapterName, cfg) - } - - if adapterName == "txbridge wifi" { - //ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - //defer cancel() - //addr, err := mdns.Query(ctx, "txbridge.local") - //if err != nil { - // cs.cfg.Logger(fmt.Sprintf("Failed to resolve txbridge address via mDNS: %v", err)) - //} else { - cfg.AdditionalConfig = map[string]string{ - "address": fmt.Sprintf("%s:%d", "192.168.4.1", 1337), - "minversion": ota.MinimumtxbridgeVersion, - } - //} - } - return gocan.NewAdapter(adapterName, cfg) -} - -func (sw *Widget) GetADScannerSymbolName() string { - return fyne.CurrentApp().Preferences().String(prefsWBLADScannerSymbol) -} - -func (sw *Widget) GetWidebandName() string { - return fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") -} - -func (sw *Widget) GetWidebandSymbolName() string { - useADScanner := fyne.CurrentApp().Preferences().Bool(prefsUseADScanner) - switch sw.GetWidebandName() { - case "ECU": - switch sw.cfg.SelectedEcuFunc() { - case "T5": - return datalogger.LAMBDAADSCANNER // Lambda.ADScanner - case "T7": - if useADScanner { - return datalogger.LAMBDAADSCANNER // Lambda.ADScanner - } - return "DisplProt.LambdaScanner" - case "T8": - if useADScanner { - return datalogger.LAMBDAADSCANNER // Lambda.ADScanner - } - return "LambdaScan.LambdaScanner" - default: - return "None" - } - case aem.ProductString, - "CombiAdapter", - ecumaster.ProductString, - innovate.ProductString, - plx.ProductString, - stag.ProductString, - zeitronix.ProductString: - return datalogger.EXTERNALWBLSYM // Lambda.External - default: - return "None" - } -} - -func (sw *Widget) GetColorBlindMode() colors.ColorBlindMode { - return colors.StringToColorBlindMode(fyne.CurrentApp().Preferences().StringWithFallback(prefsColorBlindMode, "Normal")) -} - -func (sw *Widget) GetWidebandPort() string { - return fyne.CurrentApp().Preferences().String(prefsWBLPort) -} - -func (sw *Widget) GetWBLSupportPoints() []int { - return fyne.CurrentApp().Preferences().IntListWithFallback(prefsWBLSupportPoints, []int{0, 1024}) -} - -func (sw *Widget) GetWBLLambdaValues() []float64 { - return fyne.CurrentApp().Preferences().FloatListWithFallback(prefsWBLLambdaValues, []float64{0.5, 1.5}) -} - -func (sw *Widget) GetUseADScanner() bool { - if fyne.CurrentApp().Preferences().String(prefsWblSource) != "ECU" { - return false - } - return fyne.CurrentApp().Preferences().Bool(prefsUseADScanner) -} - -func (sw *Widget) GetFreq() int { - return int(fyne.CurrentApp().Preferences().IntWithFallback(prefsFreq, 25)) -} - -func (sw *Widget) GetAutoSave() bool { - return fyne.CurrentApp().Preferences().Bool(prefsAutoUpdateSaveEcu) -} - -func (sw *Widget) GetAutoLoad() bool { - return fyne.CurrentApp().Preferences().Bool(prefsAutoUpdateLoadEcu) -} - -func (sw *Widget) GetLivePreview() bool { - return fyne.CurrentApp().Preferences().Bool(prefsLivePreview) -} - -func (sw *Widget) GetRealtimeBars() bool { - return fyne.CurrentApp().Preferences().Bool(prefsRealtimeBars) -} - -func (sw *Widget) GetMeshView() bool { - return fyne.CurrentApp().Preferences().Bool(prefsMeshView) -} - -func (sw *Widget) GetLogFormat() string { - return fyne.CurrentApp().Preferences().StringWithFallback(prefsLogFormat, "CSV") -} - -func (sw *Widget) GetLogPath() string { - p := fyne.CurrentApp().Preferences().String(prefsLogPath) - if p == "" { - var err error - p, err = common.GetLogPath() - if err != nil { - log.Println("GetLogPath: ", err) - } - } - return p -} - -func (sw *Widget) GetUseMPH() bool { - return fyne.CurrentApp().Preferences().Bool(prefsUseMPH) -} - -func (sw *Widget) GetSwapRPMandSpeed() bool { - return fyne.CurrentApp().Preferences().Bool(prefsSwapRPMandSpeed) -} - -func (sw *Widget) GetCursorFollowCrosshair() bool { - return fyne.CurrentApp().Preferences().Bool(prefsCursorFollowCrosshair) -} diff --git a/pkg/widgets/settings/settings_getters.go b/pkg/widgets/settings/settings_getters.go new file mode 100644 index 00000000..d7154a9e --- /dev/null +++ b/pkg/widgets/settings/settings_getters.go @@ -0,0 +1,251 @@ +package settings + +import ( + "errors" + "fmt" + "log" + "strconv" + "strings" + + "fyne.io/fyne/v2" + "github.com/roffe/gocan" + "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/common" + "github.com/roffe/txlogger/pkg/datalogger" + "github.com/roffe/txlogger/pkg/ota" + "github.com/roffe/txlogger/pkg/wbl/aem" + "github.com/roffe/txlogger/pkg/wbl/ecumaster" + "github.com/roffe/txlogger/pkg/wbl/innovate" + "github.com/roffe/txlogger/pkg/wbl/plx" + "github.com/roffe/txlogger/pkg/wbl/stag" + "github.com/roffe/txlogger/pkg/wbl/zeitronix" + "github.com/roffe/txlogger/pkg/widgets/meshgrid" + "github.com/roffe/txlogger/pkg/widgets/plotter" +) + +func (cs *Widget) GetAdapter(ecuType string) (gocan.Adapter, error) { + return cs.GetAdapterWithExtraFilters(ecuType, []uint32{}) +} + +func (cs *Widget) GetAdapterWithExtraFilters(ecuType string, filters []uint32) (gocan.Adapter, error) { + debug := fyne.CurrentApp().Preferences().Bool(prefsDebug) + port := fyne.CurrentApp().Preferences().String(prefsPort) + + baudstring := fyne.CurrentApp().Preferences().String(prefsSpeed) + switch baudstring { + case "1mbit": + baudstring = "1000000" + case "2mbit": + baudstring = "2000000" + case "3mbit": + baudstring = "3000000" + } + + if baudstring == "" { + baudstring = "1000000" + } + + baudrate, err := strconv.Atoi(baudstring) + if err != nil { + return nil, err + } + adapterName := fyne.CurrentApp().Preferences().String(prefsAdapter) + + if adapterName == "" { + return nil, errors.New("Select CANbus adapter in settings") //lint:ignore ST1005 This is ok + } + + if ad, found := cs.adapters[adapterName]; found { + if ad.RequiresSerialPort { + if port == "" { + return nil, errors.New("Select port in setings") //lint:ignore ST1005 This is ok + } + if baudstring == "" { + return nil, errors.New("Select port speed in settings") //lint:ignore ST1005 This is ok + } + } + } + + var canFilter []uint32 + var canRate float64 + + switch ecuType { + case "T5", "Trionic 5": + canFilter = []uint32{0xC} + canRate = 615.384 + case "T7", "Trionic 7": + if strings.Contains(adapterName, "ELM327") || strings.Contains(adapterName, "STN") || strings.Contains(adapterName, "OBDLink") || strings.HasSuffix(adapterName, "Wifi") { + canFilter = []uint32{0x238, 0x258, 0x270} + } else { + canFilter = []uint32{0x1A0, 0x238, 0x258, 0x270, 0x280, 0x3A0, 0x664, 0x665} + } + if fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") == "CAN" { + canFilter = append(canFilter, 0x180) + } + canRate = 500 + case "T8", "Trionic 8", "Trionic 8 MCP", "Z22SE", "Z22SE MCP": + if strings.Contains(adapterName, "ELM327") || strings.Contains(adapterName, "STN") || strings.Contains(adapterName, "OBDLink") { + canFilter = []uint32{0x5E8, 0x7E8} + } else { + canFilter = []uint32{0x5E8, 0x7E8, 0x664, 0x665} + } + if fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") == "CAN" { + canFilter = append(canFilter, 0x180) + } + canFilter = append(canFilter, filters...) + + canRate = 500 + } + + cfg := &gocan.AdapterConfig{ + Port: port, + PortBaudrate: baudrate, + CANRate: canRate, + CANFilter: canFilter, + Debug: debug, + PrintVersion: true, + } + + if strings.HasPrefix(adapterName, "J2534") { // || strings.HasPrefix(adapterName, "CANlib") { + return gocan.NewGWClient(adapterName, cfg) + } + + if adapterName == "txbridge wifi" { + //ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + //defer cancel() + //addr, err := mdns.Query(ctx, "txbridge.local") + //if err != nil { + // cs.cfg.Logger(fmt.Sprintf("Failed to resolve txbridge address via mDNS: %v", err)) + //} else { + cfg.AdditionalConfig = map[string]string{ + "address": fmt.Sprintf("%s:%d", "192.168.4.1", 1337), + "minversion": ota.MinimumtxbridgeVersion, + } + //} + } + return gocan.NewAdapter(adapterName, cfg) +} + +func (sw *Widget) GetADScannerSymbolName() string { + return fyne.CurrentApp().Preferences().String(prefsWBLADScannerSymbol) +} + +func (sw *Widget) GetWidebandName() string { + return fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") +} + +func (sw *Widget) GetWidebandSymbolName() string { + useADScanner := fyne.CurrentApp().Preferences().Bool(prefsUseADScanner) + switch sw.GetWidebandName() { + case "ECU": + switch sw.cfg.SelectedEcuFunc() { + case "T5": + return datalogger.LAMBDAADSCANNER // Lambda.ADScanner + case "T7": + if useADScanner { + return datalogger.LAMBDAADSCANNER // Lambda.ADScanner + } + return "DisplProt.LambdaScanner" + case "T8": + if useADScanner { + return datalogger.LAMBDAADSCANNER // Lambda.ADScanner + } + return "LambdaScan.LambdaScanner" + default: + return "None" + } + case aem.ProductString, + "CombiAdapter", + ecumaster.ProductString, + innovate.ProductString, + plx.ProductString, + stag.ProductString, + zeitronix.ProductString: + return datalogger.EXTERNALWBLSYM // Lambda.External + default: + return "None" + } +} + +func (sw *Widget) GetColorBlindMode() colors.ColorBlindMode { + return colors.StringToColorBlindMode(fyne.CurrentApp().Preferences().StringWithFallback(prefsColorBlindMode, "Normal")) +} + +func (sw *Widget) GetWidebandPort() string { + return fyne.CurrentApp().Preferences().String(prefsWBLPort) +} + +func (sw *Widget) GetWBLSupportPoints() []int { + return fyne.CurrentApp().Preferences().IntListWithFallback(prefsWBLSupportPoints, []int{0, 1024}) +} + +func (sw *Widget) GetWBLLambdaValues() []float64 { + return fyne.CurrentApp().Preferences().FloatListWithFallback(prefsWBLLambdaValues, []float64{0.5, 1.5}) +} + +func (sw *Widget) GetUseADScanner() bool { + if fyne.CurrentApp().Preferences().String(prefsWblSource) != "ECU" { + return false + } + return fyne.CurrentApp().Preferences().Bool(prefsUseADScanner) +} + +func (sw *Widget) GetFreq() int { + return int(fyne.CurrentApp().Preferences().IntWithFallback(prefsFreq, 25)) +} + +func (sw *Widget) GetAutoSave() bool { + return fyne.CurrentApp().Preferences().Bool(prefsAutoUpdateSaveEcu) +} + +func (sw *Widget) GetAutoLoad() bool { + return fyne.CurrentApp().Preferences().Bool(prefsAutoUpdateLoadEcu) +} + +func (sw *Widget) GetLivePreview() bool { + return fyne.CurrentApp().Preferences().Bool(prefsLivePreview) +} + +func (sw *Widget) GetRealtimeBars() bool { + return fyne.CurrentApp().Preferences().Bool(prefsRealtimeBars) +} + +func (sw *Widget) GetMeshView() bool { + return fyne.CurrentApp().Preferences().Bool(prefsMeshView) +} + +func (sw *Widget) GetLogFormat() string { + return fyne.CurrentApp().Preferences().StringWithFallback(prefsLogFormat, "CSV") +} + +func (sw *Widget) GetLogPath() string { + p := fyne.CurrentApp().Preferences().String(prefsLogPath) + if p == "" { + var err error + p, err = common.GetLogPath() + if err != nil { + log.Println("GetLogPath: ", err) + } + } + return p +} + +func (sw *Widget) GetUseMPH() bool { + return fyne.CurrentApp().Preferences().Bool(prefsUseMPH) +} + +func (sw *Widget) GetSwapRPMandSpeed() bool { + return fyne.CurrentApp().Preferences().Bool(prefsSwapRPMandSpeed) +} + +func (sw *Widget) GetCursorFollowCrosshair() bool { + return fyne.CurrentApp().Preferences().Bool(prefsCursorFollowCrosshair) +} + +func (sw *Widget) GetPlotterRenderer() plotter.PlotBackend { + return plotter.PlotBackend(fyne.CurrentApp().Preferences().IntWithFallback(prefsPlotterRenderer, 0)) +} + +func (sw *Widget) GetMeshRenderer() meshgrid.RenderBackend { + return meshgrid.RenderBackend(fyne.CurrentApp().Preferences().IntWithFallback(prefsMeshRenderer, 0)) +} diff --git a/pkg/widgets/settings/settings_internal.go b/pkg/widgets/settings/settings_internal.go index 09c2fd2a..9704ff30 100644 --- a/pkg/widgets/settings/settings_internal.go +++ b/pkg/widgets/settings/settings_internal.go @@ -220,6 +220,34 @@ func (sw *Widget) newColorBlindMode() *widget.Select { }) } +func (sw *Widget) newPlotRendererSelect() *widget.Select { + return widget.NewSelect([]string{"Software", "Shader"}, func(s string) { + var mode int + switch s { + case "Software": + mode = 0 + case "Shader": + mode = 1 + } + fyne.CurrentApp().Preferences().SetInt(prefsPlotterRenderer, mode) + }) +} + +func (sw *Widget) newMeshRendererSelect() *widget.Select { + return widget.NewSelect([]string{"Software", "Polygons", "Shader"}, func(s string) { + var mode int + switch s { + case "Shader": + mode = 0 + case "Polygons": + mode = 1 + case "Software": + mode = 2 + } + fyne.CurrentApp().Preferences().SetInt(prefsMeshRenderer, mode) + }) +} + func (sw *Widget) newAdapterSelector() *widget.Select { return widget.NewSelect(gocan.ListAdapterNames(), func(s string) { if info, found := sw.adapters[s]; found { @@ -319,6 +347,11 @@ func (sw *Widget) loadPreferences() { loadPrefsSelect(sw.portSelector, prefsPort, "") loadPrefsSelect(sw.speedSelector, prefsSpeed, "115200") loadPrefsCheck(sw.debugCheckbox, prefsDebug, false) + + // graphics settings + + sw.plotRendererSelect.SetSelectedIndex(fyne.CurrentApp().Preferences().IntWithFallback(prefsPlotterRenderer, 0)) + sw.meshRendererSelect.SetSelectedIndex(fyne.CurrentApp().Preferences().IntWithFallback(prefsMeshRenderer, 2)) } func loadPrefsSelect(s *widget.Select, prefKey string, fallback string) { diff --git a/pkg/widgets/settings/settings_tabs.go b/pkg/widgets/settings/settings_tabs.go index bf4ab6ee..eccfa5da 100644 --- a/pkg/widgets/settings/settings_tabs.go +++ b/pkg/widgets/settings/settings_tabs.go @@ -68,6 +68,58 @@ func (sw *Widget) generalTab() *container.TabItem { )) } +func (sw *Widget) graphicsTab() *container.TabItem { + return container.NewTabItem("Graphics", container.NewVBox( + container.NewBorder( + nil, + nil, + widget.NewLabel("Plot renderer"), + nil, + sw.plotRendererSelect, + ), + container.NewBorder( + nil, + nil, + widget.NewLabel("Mesh renderer"), + nil, + sw.meshRendererSelect, + ), + )) +} + +func (sw *Widget) canTab() *container.TabItem { + return container.NewTabItem("CAN", container.NewVBox( + container.NewBorder( + nil, + nil, + xlayout.NewFixedWidth(70, widget.NewLabel("Adapter")), + sw.debugCheckbox, + sw.adapterSelector, + ), + container.NewBorder( + nil, + nil, + xlayout.NewFixedWidth(70, widget.NewLabel("Port")), + sw.refreshBtn, + sw.portSelector, + ), + container.NewBorder( + nil, + nil, + xlayout.NewFixedWidth(70, widget.NewLabel("Info")), + nil, + sw.portDescription, + ), + container.NewBorder( + nil, + nil, + xlayout.NewFixedWidth(70, widget.NewLabel("Speed")), + nil, + sw.speedSelector, + ), + )) +} + func (sw *Widget) loggingTab() *container.TabItem { return container.NewTabItem("Logging", container.NewVBox( container.NewBorder( @@ -188,36 +240,3 @@ func (sw *Widget) adScannerTab() *container.TabItem { sw.wbleditor.Hide() return container.NewTabItem("AD Scanner", sw.wbleditor) } - -func (sw *Widget) canTab() *container.TabItem { - return container.NewTabItem("CAN", container.NewVBox( - container.NewBorder( - nil, - nil, - xlayout.NewFixedWidth(70, widget.NewLabel("Adapter")), - sw.debugCheckbox, - sw.adapterSelector, - ), - container.NewBorder( - nil, - nil, - xlayout.NewFixedWidth(70, widget.NewLabel("Port")), - sw.refreshBtn, - sw.portSelector, - ), - container.NewBorder( - nil, - nil, - xlayout.NewFixedWidth(70, widget.NewLabel("Info")), - nil, - sw.portDescription, - ), - container.NewBorder( - nil, - nil, - xlayout.NewFixedWidth(70, widget.NewLabel("Speed")), - nil, - sw.speedSelector, - ), - )) -} diff --git a/pkg/windows/mainWindow.go b/pkg/windows/mainWindow.go index f4d44b50..90e0cb32 100644 --- a/pkg/windows/mainWindow.go +++ b/pkg/windows/mainWindow.go @@ -416,8 +416,9 @@ func (mw *MainWindow) LoadLogfile(filename string, r io.Reader, pos fyne.Positio mw.Log("loaded log file " + filename) lp := logplayer.New(&logplayer.Config{ - EBus: ebus.CONTROLLER, - Logfile: logz, + EBus: ebus.CONTROLLER, + Logfile: logz, + PlotterRenderer: mw.settings.GetPlotterRenderer(), }) /* content := container.NewBorder( diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index 7e9083f5..483a4861 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -500,6 +500,7 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) OnUpdateCell: updateFunc, MeshView: mw.settings.GetMeshView(), + MeshRenderer: mw.settings.GetMeshRenderer(), Editable: true, CursorFollowCrosshair: mw.settings.GetCursorFollowCrosshair(), ColorblindMode: mw.settings.GetColorBlindMode(), From ef9c1a648ec9bedae6ace56fbc9bdea6bb4710ca Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 12:06:46 +0200 Subject: [PATCH 036/102] fix poly meshgrid --- pkg/widgets/meshgrid/meshgrid_poly.go | 16 +++++++++++----- pkg/widgets/meshgrid/meshgrid_shader.go | 13 +++++++++---- pkg/widgets/settings/settings_internal.go | 2 +- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/pkg/widgets/meshgrid/meshgrid_poly.go b/pkg/widgets/meshgrid/meshgrid_poly.go index 709d8d95..fd78cb3c 100644 --- a/pkg/widgets/meshgrid/meshgrid_poly.go +++ b/pkg/widgets/meshgrid/meshgrid_poly.go @@ -68,9 +68,15 @@ func (m *Meshgrid) updatePolygons() { zRange = 1 } - // Per-vertex screen projection and color. Float projection keeps subpixel - // precision, which the GPU edges make visible (the image path rounds to - // whole pixels). + // Per-vertex screen projection and color. Each vertex is snapped to a whole + // pixel: the GL painter renders a cell's corner at + // round(origin*scale) + round((corner-origin)*scale), and since every cell + // uses its own bounding-box origin, an un-snapped (fractional) corner rounds + // to a different device pixel for each of the two cells that share it - the + // gaps/overlaps that made cell spacing look uneven. Snapping the projection + // to integer pixels (as the image path also does) cancels the per-cell + // origin out at integer pixel-scales, so a shared corner lands identically + // for both cells and their edges line up. n := vRows * vCols if cap(m.scratchFX) < n { m.scratchFX = make([]float32, n) @@ -91,8 +97,8 @@ func (m *Meshgrid) updatePolygons() { for j := 0; j < vCols; j++ { v := row[j] idx := base + j - fxs[idx] = float32(cx + v.X) - fys[idx] = float32(cy + v.Y) + fxs[idx] = float32(math.Round(cx + v.X)) + fys[idx] = float32(math.Round(cy + v.Y)) depth := (v.Z - minZ) / zRange vertCol[idx] = m.getColorWithDepth(v.V, depth) } diff --git a/pkg/widgets/meshgrid/meshgrid_shader.go b/pkg/widgets/meshgrid/meshgrid_shader.go index 79f0e7b9..b6f81140 100644 --- a/pkg/widgets/meshgrid/meshgrid_shader.go +++ b/pkg/widgets/meshgrid/meshgrid_shader.go @@ -151,10 +151,15 @@ vec3 height_color(float h, float view_z) { float df = clamp((view_z - view_zmin) / view_zrange, 0.0, 1.0); vec3 rgb = base.rgb * (0.6 + 0.4 * df); rgb.b = min(1.0, rgb.b + (1.0 - df) * 0.05882353); - if (base.r > 0.784 && base.g > 0.784 && base.b < 0.196) { - rgb.r = min(1.0, rgb.r * 1.1); - rgb.g = min(1.0, rgb.g * 1.1); - } + // Yellow emphasis from getColorWithDepth, but ramped smoothly: the CPU + // path applies the 10% boost per vertex and lets Gouraud blur its edge, + // while this shader runs per pixel, so a hard threshold would draw a + // visible band where the boost switches on. smoothstep fades it in around + // pure yellow so the mid-range stays a continuous gradient. + float yellow = smoothstep(0.6, 0.95, base.r) * smoothstep(0.6, 0.95, base.g) * (1.0 - smoothstep(0.2, 0.4, base.b)); + float boost = 1.0 + 0.1 * yellow; + rgb.r = min(1.0, rgb.r * boost); + rgb.g = min(1.0, rgb.g * boost); return rgb; } diff --git a/pkg/widgets/settings/settings_internal.go b/pkg/widgets/settings/settings_internal.go index 9704ff30..10fc216f 100644 --- a/pkg/widgets/settings/settings_internal.go +++ b/pkg/widgets/settings/settings_internal.go @@ -234,7 +234,7 @@ func (sw *Widget) newPlotRendererSelect() *widget.Select { } func (sw *Widget) newMeshRendererSelect() *widget.Select { - return widget.NewSelect([]string{"Software", "Polygons", "Shader"}, func(s string) { + return widget.NewSelect([]string{"Shader", "Polygons", "Software"}, func(s string) { var mode int switch s { case "Shader": From 24af0ea58d46d30f7778d0dc807425786a87de05 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 18:56:58 +0200 Subject: [PATCH 037/102] meshgrid improvements --- pkg/widgets/meshgrid/meshgrid_shader.go | 45 ++++++++- pkg/widgets/meshgrid/meshgrid_widget.go | 122 ++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 4 deletions(-) diff --git a/pkg/widgets/meshgrid/meshgrid_shader.go b/pkg/widgets/meshgrid/meshgrid_shader.go index b6f81140..23db6bd9 100644 --- a/pkg/widgets/meshgrid/meshgrid_shader.go +++ b/pkg/widgets/meshgrid/meshgrid_shader.go @@ -204,6 +204,41 @@ void edge_check(vec2 p_dev, vec3 pa, vec3 pb, float ha, float hb, inout float be } } +// fake ambient occlusion: darken concave cells (valleys, creases) using the +// height-field Laplacian sampled at the cell centre and its four neighbours. +// A positive Laplacian means the centre sits below its surroundings, so it +// would be shadowed by them; convex ridges (negative) are left untouched. +float cell_ao(float cx, float cy) { + float cxm = max(cx - 0.5, 0.0); + float cxp = min(cx + 1.5, grid_cols); + float cym = max(cy - 0.5, 0.0); + float cyp = min(cy + 1.5, grid_rows); + float hc = corner_height(cx + 0.5, cy + 0.5); + float lap = corner_height(cxp, cy + 0.5) + corner_height(cxm, cy + 0.5) + + corner_height(cx + 0.5, cyp) + corner_height(cx + 0.5, cym) - 4.0 * hc; + float c = clamp(lap / max(height_units, 0.0001), 0.0, 1.0); + return 1.0 - 0.4 * c; +} + +// Blinn-Phong shading with an ambient floor and fake AO. n is the raw cell +// normal from cross(C-A, D-B), which points along -Z for a flat cell, so it is +// flipped to face up. light and view_dir are unit vectors in grid space; the +// specular term is gated to the lit side and the ambient term keeps shadowed +// faces readable instead of black. +vec3 shade_surface(vec3 base, vec3 n, vec3 light, vec3 view_dir, float ao) { + float nl = length(n); + if (nl <= 0.0) { + return base * ao; + } + vec3 N = -n / nl; + float diff = max(dot(N, light), 0.0); + vec3 H = normalize(light + view_dir); + float spec = (diff > 0.0) ? pow(max(dot(N, H), 0.0), 32.0) : 0.0; + vec3 col = base * ((0.32 + 0.68 * diff) * ao); + col += vec3(0.25 * spec); + return col; +} + void main() { mat3 rot = mat3(r0, r3, r6, r1, r4, r7, r2, r5, r8); @@ -256,6 +291,10 @@ void main() { int mode = int(render_mode + 0.5); float half_w = 0.5 * pix_scale; vec3 light = vec3(light_x, light_y, light_z); + // grid-space direction from the surface toward the camera: the view ray + // marches from near to far along rd = -(r6,r7,r8), so the viewer lies along + // +(r6,r7,r8), already unit length since the rotation is orthonormal + vec3 view_dir = vec3(r6, r7, r8); vec3 acc = vec3(0.0); float acc_a = 0.0; @@ -304,10 +343,8 @@ void main() { vec3 rgb = height_color(hit_h, view_z); vec3 n = cross(C - A, D - B); - float nl = length(n); - if (nl > 0.0) { - rgb *= 0.6 + 0.4 * abs(dot(n / nl, light)); - } + float ao = cell_ao(cx, cy); + rgb = shade_surface(rgb, n, light, view_dir, ao); if (mode == 0) { vec3 pa = project_grid(rot, A, pix_scale); diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index a7382700..a637a94f 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -5,6 +5,7 @@ import ( "image" "image/color" "log" + "math" "os" "time" @@ -105,6 +106,11 @@ type Meshgrid struct { rotationMatrix Matrix3x3 scale float64 + // fitted is set once the widget has received its first real size and the + // mesh has been auto-scaled to fit. Subsequent resizes scale relative to + // the size change so the user's manual zoom is preserved. + fitted bool + cameraRotation Matrix3x3 // Camera's rotation matrix cameraPosition [3]float64 // Camera's position in world space mousePosition image.Point @@ -320,6 +326,118 @@ func (m *Meshgrid) scaleMeshgrid(factor float64) { m.updateVertexPositions() } +// fitMargin leaves a border around the mesh so it isn't drawn flush to the +// widget edges (and so the axis indicator, which extends past the mesh, has +// some room). +const fitMargin = 0.85 + +// projectedBounds returns the min/max of the mesh's current projected +// (orthographic) screen positions. Both reflect the current scale, rotation +// and pan. +func (m *Meshgrid) projectedBounds() (minX, maxX, minY, maxY float64) { + minX, maxX = math.Inf(1), math.Inf(-1) + minY, maxY = math.Inf(1), math.Inf(-1) + for i := range m.vertices { + row := m.vertices[i] + for j := range row { + v := &row[j] + if v.X < minX { + minX = v.X + } + if v.X > maxX { + maxX = v.X + } + if v.Y < minY { + minY = v.Y + } + if v.Y > maxY { + maxY = v.Y + } + } + } + return +} + +// projectedExtent returns the width and height, in logical pixels, of the +// mesh's current projected bounding box. It reflects the current scale and +// rotation; panning shifts every vertex equally so the extent is +// pan-invariant. +func (m *Meshgrid) projectedExtent() (w, h float64) { + minX, maxX, minY, maxY := m.projectedBounds() + if math.IsInf(minX, 0) || math.IsInf(minY, 0) { + return 0, 0 + } + return maxX - minX, maxY - minY +} + +// centerInView pans the camera so the mesh's projected bounding box is +// centered in the widget. Used on the initial fit only; the camera offset it +// produces scales with the mesh on later resizes, so the centering (and any +// user pan applied on top) is preserved. +func (m *Meshgrid) centerInView() { + minX, maxX, minY, maxY := m.projectedBounds() + if math.IsInf(minX, 0) || math.IsInf(minY, 0) { + return + } + // v.{X,Y} = base - cam, so adding the current box center to the camera + // moves the box center to the view origin (which maps to screen center). + m.cameraPosition[0] += (minX + maxX) / 2 + m.cameraPosition[1] += (minY + maxY) / 2 + m.updateVertexPositions() +} + +// fitScaleForSize returns the m.scale value that makes the mesh's projected +// bounding box fill the given widget size (minus fitMargin) at the current +// rotation. The bounding box is linear in m.scale, so it is normalized to a +// unit scale first. +func (m *Meshgrid) fitScaleForSize(size fyne.Size) float64 { + w, h := m.projectedExtent() + if w <= 0 || h <= 0 || m.scale == 0 { + return m.scale + } + wPer := w / m.scale + hPer := h / m.scale + sx := float64(size.Width) * fitMargin / wPer + sy := float64(size.Height) * fitMargin / hPer + return math.Min(sx, sy) +} + +// adaptZoom keeps the mesh sized to the widget across layout changes. The +// first real layout fits the mesh to the view and centers it; later resizes +// scale the mesh (and the camera pan) by the ratio of the new fit-scale to +// the old. This grows and shrinks the mesh with the window while preserving +// whatever zoom and pan the user has applied — it adjusts the existing scale +// and pan multiplicatively rather than resetting them. +func (m *Meshgrid) adaptZoom(oldSize, newSize fyne.Size) { + if newSize.Width <= 0 || newSize.Height <= 0 { + return + } + if !m.fitted { + m.scale = m.fitScaleForSize(newSize) + m.fitted = true + m.updateVertexPositions() + m.centerInView() + return + } + if oldSize.Width <= 0 || oldSize.Height <= 0 { + return + } + oldFit := m.fitScaleForSize(oldSize) + if oldFit == 0 { + return + } + ratio := m.fitScaleForSize(newSize) / oldFit + if ratio <= 0 || math.IsInf(ratio, 0) || math.IsNaN(ratio) { + return + } + m.scale *= ratio + // Pan lives in the same scaled view space, so scale it alongside the mesh + // to keep the centering and any user pan in the same relative spot. + m.cameraPosition[0] *= ratio + m.cameraPosition[1] *= ratio + m.updateVertexPositions() +} + // orbit performs a Fusion 360-style "turntable" orbit. Spin is applied around // the mesh's own vertical axis — data Z, the height axis (right-multiplied so // it rotates the model before the camera) — pitch around the camera-local X @@ -521,7 +639,11 @@ func (m *meshgridRenderer) Layout(size fyne.Size) { if size == m.MG.size { return } + oldSize := m.MG.size m.MG.size = size + // Auto-fit on the first real size and scale with the window thereafter, + // preserving any zoom the user has applied. + m.MG.adaptZoom(oldSize, size) switch m.MG.backend { case BackendShader: m.MG.shader.Resize(size) From 8031da553e7fcb4a3db2eb2abdc41b77e29ba8e2 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 21:01:02 +0200 Subject: [PATCH 038/102] set default mode for mesh --- pkg/widgets/settings/settings_getters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/widgets/settings/settings_getters.go b/pkg/widgets/settings/settings_getters.go index d7154a9e..f12404e4 100644 --- a/pkg/widgets/settings/settings_getters.go +++ b/pkg/widgets/settings/settings_getters.go @@ -247,5 +247,5 @@ func (sw *Widget) GetPlotterRenderer() plotter.PlotBackend { } func (sw *Widget) GetMeshRenderer() meshgrid.RenderBackend { - return meshgrid.RenderBackend(fyne.CurrentApp().Preferences().IntWithFallback(prefsMeshRenderer, 0)) + return meshgrid.RenderBackend(fyne.CurrentApp().Preferences().IntWithFallback(prefsMeshRenderer, 2)) } From 59bfd11f3b504a83acb71a6027c7a8fbc1705f33 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 22:52:12 +0200 Subject: [PATCH 039/102] save settings --- pkg/widgets/settings/settings_internal.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/widgets/settings/settings_internal.go b/pkg/widgets/settings/settings_internal.go index 10fc216f..53c36798 100644 --- a/pkg/widgets/settings/settings_internal.go +++ b/pkg/widgets/settings/settings_internal.go @@ -221,9 +221,9 @@ func (sw *Widget) newColorBlindMode() *widget.Select { } func (sw *Widget) newPlotRendererSelect() *widget.Select { - return widget.NewSelect([]string{"Software", "Shader"}, func(s string) { + return widget.NewSelect([]string{"Software", "Shader"}, func(selection string) { var mode int - switch s { + switch selection { case "Software": mode = 0 case "Shader": @@ -234,9 +234,9 @@ func (sw *Widget) newPlotRendererSelect() *widget.Select { } func (sw *Widget) newMeshRendererSelect() *widget.Select { - return widget.NewSelect([]string{"Shader", "Polygons", "Software"}, func(s string) { + return widget.NewSelect([]string{"Shader", "Polygons", "Software"}, func(selection string) { var mode int - switch s { + switch selection { case "Shader": mode = 0 case "Polygons": From 3c66f3f5aa43e7d10e623560b12d3362c648c5d6 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 13 Jun 2026 23:59:28 +0200 Subject: [PATCH 040/102] refactor settings --- .../loggingsettings/loggingsettings.go | 0 pkg/widgets/settings/adapter.go | 115 +++++ pkg/widgets/settings/controls.go | 212 ++++++++ pkg/widgets/settings/getters.go | 106 ++++ pkg/widgets/settings/images.go | 39 ++ pkg/widgets/settings/prefs.go | 110 +++++ pkg/widgets/settings/settings.go | 196 +++----- pkg/widgets/settings/settings_getters.go | 251 ---------- pkg/widgets/settings/settings_internal.go | 383 --------------- pkg/widgets/settings/settings_tabs.go | 242 ---------- pkg/widgets/settings/tabs.go | 128 +++++ pkg/widgets/settings/wbleditor.go | 457 +++--------------- pkg/widgets/settings/wblgraph.go | 325 +++++++++++++ 13 files changed, 1151 insertions(+), 1413 deletions(-) rename {pkg/widgets/settings => experiments}/loggingsettings/loggingsettings.go (100%) create mode 100644 pkg/widgets/settings/adapter.go create mode 100644 pkg/widgets/settings/controls.go create mode 100644 pkg/widgets/settings/getters.go create mode 100644 pkg/widgets/settings/images.go create mode 100644 pkg/widgets/settings/prefs.go delete mode 100644 pkg/widgets/settings/settings_getters.go delete mode 100644 pkg/widgets/settings/settings_internal.go delete mode 100644 pkg/widgets/settings/settings_tabs.go create mode 100644 pkg/widgets/settings/tabs.go create mode 100644 pkg/widgets/settings/wblgraph.go diff --git a/pkg/widgets/settings/loggingsettings/loggingsettings.go b/experiments/loggingsettings/loggingsettings.go similarity index 100% rename from pkg/widgets/settings/loggingsettings/loggingsettings.go rename to experiments/loggingsettings/loggingsettings.go diff --git a/pkg/widgets/settings/adapter.go b/pkg/widgets/settings/adapter.go new file mode 100644 index 00000000..dbf4545f --- /dev/null +++ b/pkg/widgets/settings/adapter.go @@ -0,0 +1,115 @@ +package settings + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/roffe/gocan" + "github.com/roffe/txlogger/pkg/ota" +) + +func (sw *Widget) GetAdapter(ecuType string) (gocan.Adapter, error) { + return sw.GetAdapterWithExtraFilters(ecuType, nil) +} + +func (sw *Widget) GetAdapterWithExtraFilters(ecuType string, filters []uint32) (gocan.Adapter, error) { + baudrate, err := parseBaudrate(prefSpeed.getOr("")) + if err != nil { + return nil, err + } + + adapterName := prefAdapter.get() + if adapterName == "" { + return nil, errors.New("Select CANbus adapter in settings") //lint:ignore ST1005 This is ok + } + + port := prefPort.get() + if ad, found := sw.adapters[adapterName]; found && ad.RequiresSerialPort && port == "" { + return nil, errors.New("Select port in setings") //lint:ignore ST1005 This is ok + } + + canFilter, canRate := canFilterAndRate(ecuType, adapterName, filters) + + cfg := &gocan.AdapterConfig{ + Port: port, + PortBaudrate: baudrate, + CANRate: canRate, + CANFilter: canFilter, + Debug: prefDebug.get(), + PrintVersion: true, + } + + if strings.HasPrefix(adapterName, "J2534") { + return gocan.NewGWClient(adapterName, cfg) + } + + if adapterName == "txbridge wifi" { + cfg.AdditionalConfig = map[string]string{ + "address": fmt.Sprintf("%s:%d", "192.168.4.1", 1337), + "minversion": ota.MinimumtxbridgeVersion, + } + } + + return gocan.NewAdapter(adapterName, cfg) +} + +// parseBaudrate converts a stored port speed (which may use the "Nmbit" +// shorthand or be empty) into a numeric baudrate. +func parseBaudrate(speed string) (int, error) { + switch speed { + case "1mbit": + speed = "1000000" + case "2mbit": + speed = "2000000" + case "3mbit": + speed = "3000000" + case "": + speed = "1000000" + } + return strconv.Atoi(speed) +} + +// canFilterAndRate returns the CAN acceptance filter and bus rate for the given +// ECU type and adapter. extraFilters are appended for the Trionic 8 family. +func canFilterAndRate(ecuType, adapterName string, extraFilters []uint32) ([]uint32, float64) { + wblOnCAN := prefWblSource.get() == "CAN" + + switch ecuType { + case "T5", "Trionic 5": + return []uint32{0xC}, 615.384 + + case "T7", "Trionic 7": + var filter []uint32 + if isOBDAdapter(adapterName) || strings.HasSuffix(adapterName, "Wifi") { + filter = []uint32{0x238, 0x258, 0x270} + } else { + filter = []uint32{0x1A0, 0x238, 0x258, 0x270, 0x280, 0x3A0, 0x664, 0x665} + } + if wblOnCAN { + filter = append(filter, 0x180) + } + return filter, 500 + + case "T8", "Trionic 8", "Trionic 8 MCP", "Z22SE", "Z22SE MCP": + var filter []uint32 + if isOBDAdapter(adapterName) { + filter = []uint32{0x5E8, 0x7E8} + } else { + filter = []uint32{0x5E8, 0x7E8, 0x664, 0x665} + } + if wblOnCAN { + filter = append(filter, 0x180) + } + return append(filter, extraFilters...), 500 + } + + return nil, 0 +} + +func isOBDAdapter(name string) bool { + return strings.Contains(name, "ELM327") || + strings.Contains(name, "STN") || + strings.Contains(name, "OBDLink") +} diff --git a/pkg/widgets/settings/controls.go b/pkg/widgets/settings/controls.go new file mode 100644 index 00000000..3be2f7a9 --- /dev/null +++ b/pkg/widgets/settings/controls.go @@ -0,0 +1,212 @@ +package settings + +import ( + "strconv" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/common" + "github.com/roffe/txlogger/pkg/ebus" + "github.com/roffe/txlogger/pkg/wbl/aem" + "github.com/roffe/txlogger/pkg/wbl/ecumaster" + "github.com/roffe/txlogger/pkg/wbl/innovate" + "github.com/roffe/txlogger/pkg/wbl/plx" + "github.com/roffe/txlogger/pkg/wbl/stag" + "github.com/roffe/txlogger/pkg/wbl/zeitronix" +) + +var ( + logFormats = []string{"CSV", "BPL" /*"TXL"*/} + portSpeeds = []string{"9600", "19200", "38400", "57600", "115200", "230400", "460800", "500000", "921600", "1mbit", "2mbit", "3mbit"} +) + +// --- generic control helpers ----------------------------------------------- + +// checkBox builds a checkbox bound to a boolean preference. +func checkBox(label string, p boolPref) *widget.Check { + return widget.NewCheck(label, p.set) +} + +// indexSelect builds a select whose chosen option index is stored in an +// integer preference (the option order defines the stored value). +func indexSelect(options []string, p intPref) *widget.Select { + sel := widget.NewSelect(options, nil) + sel.OnChanged = func(string) { + p.set(sel.SelectedIndex()) + } + return sel +} + +// setVisible shows or hides a group of canvas objects. +func setVisible(show bool, objs ...fyne.CanvasObject) { + for _, o := range objs { + if show { + o.Show() + } else { + o.Hide() + } + } +} + +// --- wideband source table ------------------------------------------------- + +// wblSource describes a selectable wideband source and the UI it enables. +type wblSource struct { + name string + image string // image resource key, "" for none + portSelect bool // show the WBL port selector + adScanner bool // show the AD scanner controls +} + +var wblSources = []wblSource{ + {name: "None"}, + {name: "ECU", image: "t7", adScanner: true}, // T7 image used as a placeholder + {name: aem.ProductString, image: "uego", portSelect: true}, + {name: "CombiAdapter", image: "combi", adScanner: true}, + {name: ecumaster.ProductString, image: "lambdatocan"}, + {name: innovate.ProductString, image: "mtx-l", portSelect: true}, + {name: plx.ProductString, image: "plx", portSelect: true}, + {name: stag.ProductString, image: "stagafr", portSelect: true}, + {name: zeitronix.ProductString, image: "zeitronix", portSelect: true}, +} + +func (sw *Widget) newWBLSelector() *fyne.Container { + names := make([]string, len(wblSources)) + byName := make(map[string]wblSource, len(wblSources)) + for i, s := range wblSources { + names[i] = s.name + byName[s.name] = s + } + + sw.wblSource = widget.NewSelect(names, func(s string) { + prefWblSource.set(s) + prefWidebandSymbolName.set(sw.GetWidebandSymbolName()) + + src := byName[s] + if src.image != "" { + if img := newImageFromResource(src.image); img != nil { + sw.wblImage.Resource = img.Resource + sw.wblImage.SetMinSize(img.MinSize()) + sw.wblImage.Refresh() + } + } + setVisible(src.portSelect, sw.wblPortLabel, sw.wblPortSelect, sw.wblPortRefreshButton) + setVisible(src.adScanner, sw.wblADscanner, sw.wblADScannerSymbol) + }) + + return container.NewBorder(nil, nil, widget.NewLabel("Source"), nil, sw.wblSource) +} + +// --- individual controls --------------------------------------------------- + +func (sw *Widget) newFreqSlider() *widget.Slider { + slider := widget.NewSlider(5, 300) + slider.Step = 5 + slider.OnChanged = func(f float64) { + sw.freqValue.SetText(strconv.FormatFloat(f, 'f', 0, 64)) + } + slider.OnChangeEnded = func(f float64) { + prefFreq.set(int(f)) + } + return slider +} + +func (sw *Widget) newLogFormat() *widget.Select { + return widget.NewSelect(logFormats, prefLogFormat.set) +} + +func (sw *Widget) newADscannerCheck() *widget.Check { + return widget.NewCheck("use AD Scanner (don't forget to add symbol)", func(b bool) { + setVisible(b, sw.wblADScannerSymbol) + prefUseADScanner.set(b) + }) +} + +func (sw *Widget) newColorBlindMode() *widget.Select { + return widget.NewSelect(colors.SupportedColorBlindModes[:], func(s string) { + prefColorBlindMode.set(s) + ebus.Publish(ebus.TOPIC_COLORBLINDMODE, float64(sw.colorBlindMode.SelectedIndex())) + }) +} + +func (sw *Widget) newAdapterSelector() *widget.Select { + return widget.NewSelect(nil, func(s string) { + info, found := sw.adapters[s] + if !found { + return + } + prefAdapter.set(s) + if info.RequiresSerialPort { + sw.portSelector.Enable() + sw.speedSelector.Enable() + return + } + sw.portDescription.SetText("") + sw.portSelector.Disable() + sw.speedSelector.Disable() + }) +} + +func (sw *Widget) newPortSelector() *widget.Select { + return widget.NewSelect(sw.ListPorts(), func(s string) { + prefPort.set(s) + if itm, ok := portCache[s]; ok { + sw.portDescription.SetText(itm.SerialNumber) + } else { + sw.portDescription.SetText("") + } + }) +} + +func (sw *Widget) newSpeedSelector() *widget.Select { + return widget.NewSelect(portSpeeds, prefSpeed.set) +} + +func (sw *Widget) newPortRefreshButton() *widget.Button { + return widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { + sw.portSelector.Options = sw.ListPorts() + sw.portSelector.Refresh() + }) +} + +// --- preference hydration -------------------------------------------------- + +// loadPreferences pushes persisted values into the controls once every widget +// has been constructed. +func (sw *Widget) loadPreferences() { + sw.freqSlider.SetValue(float64(prefFreq.get())) + sw.autoLoad.SetChecked(prefAutoLoad.get()) + sw.autoSave.SetChecked(prefAutoSave.get()) + sw.cursorFollowCrosshair.SetChecked(prefCursorFollowCrosshair.get()) + sw.livePreview.SetChecked(prefLivePreview.get()) + sw.meshView.SetChecked(prefMeshView.get()) + sw.realtimeBars.SetChecked(prefRealtimeBars.get()) + sw.logFormat.SetSelected(prefLogFormat.get()) + + logPath, err := common.GetLogPath() + if err != nil { + fyne.LogError("Could not get log path", err) + } + sw.logPath.SetText(prefLogPath.getOr(logPath)) + + sw.wblSource.SetSelected(prefWblSource.get()) + sw.wblADscanner.SetChecked(prefUseADScanner.get()) + setVisible(sw.wblADscanner.Checked && sw.wblSource.Selected == "ECU", sw.wblADScannerSymbol) + + sw.useMPH.SetChecked(prefUseMPH.get()) + sw.swapRPMandSpeed.SetChecked(prefSwapRPMandSpeed.get()) + sw.wblPortSelect.SetSelected(prefWBLPort.get()) + sw.colorBlindMode.SetSelected(prefColorBlindMode.get()) + + sw.adapterSelector.SetSelected(prefAdapter.get()) + sw.portSelector.SetSelected(prefPort.get()) + sw.speedSelector.SetSelected(prefSpeed.get()) + sw.debugCheckbox.SetChecked(prefDebug.get()) + + // Graphics + sw.plotRendererSelect.SetSelectedIndex(prefPlotterRenderer.get()) + sw.meshRendererSelect.SetSelectedIndex(prefMeshRenderer.get()) +} diff --git a/pkg/widgets/settings/getters.go b/pkg/widgets/settings/getters.go new file mode 100644 index 00000000..c73667ad --- /dev/null +++ b/pkg/widgets/settings/getters.go @@ -0,0 +1,106 @@ +package settings + +import ( + "log" + + "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/common" + "github.com/roffe/txlogger/pkg/datalogger" + "github.com/roffe/txlogger/pkg/wbl/aem" + "github.com/roffe/txlogger/pkg/wbl/ecumaster" + "github.com/roffe/txlogger/pkg/wbl/innovate" + "github.com/roffe/txlogger/pkg/wbl/plx" + "github.com/roffe/txlogger/pkg/wbl/stag" + "github.com/roffe/txlogger/pkg/wbl/zeitronix" + "github.com/roffe/txlogger/pkg/widgets/meshgrid" + "github.com/roffe/txlogger/pkg/widgets/plotter" +) + +// General +func (sw *Widget) GetFreq() int { return prefFreq.get() } +func (sw *Widget) GetAutoSave() bool { return prefAutoSave.get() } +func (sw *Widget) GetAutoLoad() bool { return prefAutoLoad.get() } +func (sw *Widget) GetLivePreview() bool { return prefLivePreview.get() } +func (sw *Widget) GetRealtimeBars() bool { return prefRealtimeBars.get() } +func (sw *Widget) GetMeshView() bool { return prefMeshView.get() } +func (sw *Widget) GetCursorFollowCrosshair() bool { return prefCursorFollowCrosshair.get() } + +func (sw *Widget) GetColorBlindMode() colors.ColorBlindMode { + return colors.StringToColorBlindMode(prefColorBlindMode.get()) +} + +// Graphics +func (sw *Widget) GetPlotterRenderer() plotter.PlotBackend { + return plotter.PlotBackend(prefPlotterRenderer.get()) +} + +func (sw *Widget) GetMeshRenderer() meshgrid.RenderBackend { + return meshgrid.RenderBackend(prefMeshRenderer.get()) +} + +// Logging +func (sw *Widget) GetLogFormat() string { return prefLogFormat.get() } + +func (sw *Widget) GetLogPath() string { + if p := prefLogPath.get(); p != "" { + return p + } + p, err := common.GetLogPath() + if err != nil { + log.Println("GetLogPath: ", err) + } + return p +} + +// Dashboard +func (sw *Widget) GetUseMPH() bool { return prefUseMPH.get() } +func (sw *Widget) GetSwapRPMandSpeed() bool { return prefSwapRPMandSpeed.get() } + +// Wideband +func (sw *Widget) GetADScannerSymbolName() string { return prefWBLADScannerSymbol.get() } +func (sw *Widget) GetWidebandName() string { return prefWblSource.get() } +func (sw *Widget) GetWidebandPort() string { return prefWBLPort.get() } +func (sw *Widget) GetWBLSupportPoints() []int { return prefWBLSupportPoints.get() } +func (sw *Widget) GetWBLLambdaValues() []float64 { return prefWBLLambdaValues.get() } + +func (sw *Widget) GetUseADScanner() bool { + if prefWblSource.get() != "ECU" { + return false + } + return prefUseADScanner.get() +} + +// GetWidebandSymbolName resolves the symbol to log for the selected wideband +// source, taking the connected ECU and AD scanner mode into account. +func (sw *Widget) GetWidebandSymbolName() string { + switch sw.GetWidebandName() { + case "ECU": + useADScanner := prefUseADScanner.get() + switch sw.cfg.SelectedEcuFunc() { + case "T5": + return datalogger.LAMBDAADSCANNER + case "T7": + if useADScanner { + return datalogger.LAMBDAADSCANNER + } + return "DisplProt.LambdaScanner" + case "T8": + if useADScanner { + return datalogger.LAMBDAADSCANNER + } + return "LambdaScan.LambdaScanner" + default: + return "None" + } + case aem.ProductString, + "CombiAdapter", + ecumaster.ProductString, + innovate.ProductString, + plx.ProductString, + stag.ProductString, + zeitronix.ProductString: + return datalogger.EXTERNALWBLSYM + default: + return "None" + } +} diff --git a/pkg/widgets/settings/images.go b/pkg/widgets/settings/images.go new file mode 100644 index 00000000..2ed62f2d --- /dev/null +++ b/pkg/widgets/settings/images.go @@ -0,0 +1,39 @@ +package settings + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "github.com/roffe/txlogger/pkg/assets" +) + +// imageSpec describes a bundled wideband product image and its display size. +type imageSpec struct { + content []byte + w, h float32 +} + +var imageSpecs = map[string]imageSpec{ + "mtx-l": {assets.MtxL, 224, 224}, + "lc-2": {assets.Lc2, 400, 224}, + "uego": {assets.Uego, 315, 224}, + "lambdatocan": {assets.LambdaToCan, 481, 224}, + "t7": {assets.T7, 320, 224}, + "plx": {assets.PLX, 470, 224}, + "combi": {assets.CombiV2, 360, 245}, + "zeitronix": {assets.ZeitronixZT2, 252, 252}, + "stagafr": {assets.STAGAfr, 252, 252}, +} + +// newImageFromResource builds a contained, fast-scaling image for the named +// product. Returns nil if the name is unknown. +func newImageFromResource(name string) *canvas.Image { + spec, ok := imageSpecs[name] + if !ok { + return nil + } + img := canvas.NewImageFromResource(fyne.NewStaticResource(name, spec.content)) + img.SetMinSize(fyne.NewSize(spec.w, spec.h)) + img.FillMode = canvas.ImageFillContain + img.ScaleMode = canvas.ImageScaleFastest + return img +} diff --git a/pkg/widgets/settings/prefs.go b/pkg/widgets/settings/prefs.go new file mode 100644 index 00000000..5b996fb9 --- /dev/null +++ b/pkg/widgets/settings/prefs.go @@ -0,0 +1,110 @@ +package settings + +import "fyne.io/fyne/v2" + +// prefs returns the application preference store. Every setting in this package +// is read and written through the typed descriptors below so that storage keys +// and default values live in exactly one place. +func prefs() fyne.Preferences { + return fyne.CurrentApp().Preferences() +} + +// Exported preference keys read directly by other packages (flash settings). +const ( + PrefsNvdm = "nvdm" + PrefsBoot = "boot" +) + +// --- typed preference descriptors ------------------------------------------ +// +// Each descriptor bundles a storage key with its default value and exposes +// get/set helpers, so adding a new setting is a single declaration. + +type boolPref struct { + key string + def bool +} + +func (p boolPref) get() bool { return prefs().BoolWithFallback(p.key, p.def) } +func (p boolPref) set(v bool) { prefs().SetBool(p.key, v) } + +type stringPref struct { + key string + def string +} + +func (p stringPref) get() string { return prefs().StringWithFallback(p.key, p.def) } +func (p stringPref) set(v string) { prefs().SetString(p.key, v) } + +// getOr reads the value but uses the supplied fallback instead of the +// descriptor's default (used when the default must be computed at runtime). +func (p stringPref) getOr(fallback string) string { + return prefs().StringWithFallback(p.key, fallback) +} + +type intPref struct { + key string + def int +} + +func (p intPref) get() int { return prefs().IntWithFallback(p.key, p.def) } +func (p intPref) set(v int) { prefs().SetInt(p.key, v) } + +type intListPref struct { + key string + def []int +} + +func (p intListPref) get() []int { return prefs().IntListWithFallback(p.key, p.def) } +func (p intListPref) set(v []int) { prefs().SetIntList(p.key, v) } + +type floatListPref struct { + key string + def []float64 +} + +func (p floatListPref) get() []float64 { return prefs().FloatListWithFallback(p.key, p.def) } +func (p floatListPref) set(v []float64) { prefs().SetFloatList(p.key, v) } + +// --- preference declarations ----------------------------------------------- + +var ( + // General + prefFreq = intPref{"freq", 25} + prefAutoLoad = boolPref{"autoUpdateLoadEcu", true} + prefAutoSave = boolPref{"autoUpdateSaveEcu", false} + prefCursorFollowCrosshair = boolPref{"cursorFollowCrosshair", false} + prefLivePreview = boolPref{"livePreview", true} + prefMeshView = boolPref{"liveMeshView", true} + prefRealtimeBars = boolPref{"realtimeBars", true} + prefColorBlindMode = stringPref{"colorBlindMode", "Normal"} + + // Graphics + prefPlotterRenderer = intPref{"plotterRenderer", 0} + prefMeshRenderer = intPref{"meshRenderer", 2} + + // Logging + prefLogFormat = stringPref{"logFormat", "CSV"} + prefLogPath = stringPref{"logPath", ""} + + // Dashboard + prefUseMPH = boolPref{"useMPH", false} + prefSwapRPMandSpeed = boolPref{"swapRPMandSpeed", false} + + // Wideband + prefWblSource = stringPref{"wblSource", "None"} + prefWidebandSymbolName = stringPref{"widebandSymbolName", ""} + prefWBLPort = stringPref{"wblPort", ""} + prefUseADScanner = boolPref{"useADScanner", false} + prefWBLADScannerSymbol = stringPref{"wblADScannerSymbol", ""} + prefWBLSupportPoints = intListPref{"wblSupportPoints", []int{0, 1024}} + prefWBLLambdaValues = floatListPref{"wblLambdaValues", []float64{0.5, 1.5}} + prefLastADScannerPreset = stringPref{"lastADScannerPreset", ""} + prefLastADScannerECU = stringPref{"lastADScannerECU", "T7"} + + // CAN + prefAdapter = stringPref{"adapter", ""} + prefPort = stringPref{"port", ""} + prefSpeed = stringPref{"speed", "115200"} + prefDebug = boolPref{"debug", false} +) diff --git a/pkg/widgets/settings/settings.go b/pkg/widgets/settings/settings.go index eb93c302..a312ef6a 100644 --- a/pkg/widgets/settings/settings.go +++ b/pkg/widgets/settings/settings.go @@ -18,61 +18,15 @@ import ( "go.bug.st/serial/enumerator" ) -const ( - prefsFreq = "freq" - prefsAutoUpdateLoadEcu = "autoUpdateLoadEcu" - prefsAutoUpdateSaveEcu = "autoUpdateSaveEcu" - prefsLivePreview = "livePreview" - prefsMeshView = "liveMeshView" - prefsRealtimeBars = "realtimeBars" - prefsLogFormat = "logFormat" - prefsLogPath = "logPath" - prefsWblSource = "wblSource" - prefsWidebandSymbolName = "widebandSymbolName" - prefsUseMPH = "useMPH" - prefsSwapRPMandSpeed = "swapRPMandSpeed" - prefsCursorFollowCrosshair = "cursorFollowCrosshair" - prefsWBLPort = "wblPort" - - prefsLastADScannerPreset = "lastADScannerPreset" - prefsWBLSupportPoints = "wblSupportPoints" - prefsWBLLambdaValues = "wblLambdaValues" - prefsLastADScannerECU = "lastADScannerECU" - prefsWBLADScannerSymbol = "wblADScannerSymbol" - - prefsUseADScanner = "useADScanner" - prefsColorBlindMode = "colorBlindMode" - - prefsPlotterRenderer = "plotterRenderer" - prefsMeshRenderer = "meshRenderer" - - // CAN - prefsAdapter = "adapter" - prefsPort = "port" - prefsSpeed = "speed" - prefsDebug = "debug" - - // Flash - PrefsNvdm = "nvdm" - PrefsBoot = "boot" -) - -var portSpeeds = []string{"9600", "19200", "38400", "57600", "115200", "230400", "460800", "500000", "921600", "1mbit", "2mbit", "3mbit"} - -type SettingsWidgetInterface interface { - Get(key string) (string, error) - Widget() fyne.Widget -} - -type SetText interface { - SetText(string) -} - +// Config carries callbacks the settings widget needs from its host. type Config struct { Logger func(string) SelectedEcuFunc func() string } +// Widget is the settings panel. All persisted state lives in the application +// preferences (see prefs.go); the fields below are just the UI controls bound +// to those preferences. type Widget struct { widget.BaseWidget @@ -80,7 +34,7 @@ type Widget struct { workDir *widget.Label - // CANSettings *cansettings.Widget + // General freqSlider *widget.Slider freqValue *widget.Label autoSave *widget.Check @@ -94,24 +48,22 @@ type Widget struct { useMPH *widget.Check swapRPMandSpeed *widget.Check colorBlindMode *widget.Select + // Graphics plotRendererSelect *widget.Select meshRendererSelect *widget.Select - // can settings + // CAN debugCheckbox *widget.Check adapterSelector *widget.Select refreshBtn *widget.Button portSelector *widget.Select portDescription *widget.Label speedSelector *widget.Select + adapters map[string]*gocan.AdapterInfo - adapters map[string]*gocan.AdapterInfo - - // WBL Specific - - wbleditor *WBLEditor - + // Wideband + wbleditor *WBLEditor wblADscanner *widget.Check wblADScannerSymbol *widget.Select wblSelectContainer *fyne.Container @@ -119,22 +71,7 @@ type Widget struct { wblPortLabel *widget.Label wblPortSelect *widget.Select wblPortRefreshButton *widget.Button - - images struct { - wblImage *canvas.Image - - /* - mtxl *canvas.Image - lc2 *canvas.Image - uego *canvas.Image - lambdatocan *canvas.Image - t7 *canvas.Image - plx *canvas.Image - combi *canvas.Image - zeitronix *canvas.Image - stagafr *canvas.Image - */ - } + wblImage *canvas.Image mu sync.Mutex } @@ -143,14 +80,13 @@ func New(cfg *Config) *Widget { sw := &Widget{ cfg: cfg, adapters: make(map[string]*gocan.AdapterInfo), + wblImage: newImageFromResource("t7"), } for _, adapter := range gocan.ListAdapters() { sw.adapters[adapter.Name] = &adapter } - sw.images.wblImage = newImageFromResource("t7") - sw.ExtendBaseWidget(sw) return sw } @@ -158,32 +94,32 @@ func New(cfg *Config) *Widget { func (sw *Widget) CreateRenderer() fyne.WidgetRenderer { sw.workDir = widget.NewLabel("") sw.workDir.Selectable = true - wd, err := os.Getwd() - if err != nil { + if wd, err := os.Getwd(); err != nil { sw.workDir.SetText(fmt.Sprintf("Error getting working directory: %v", err)) } else { sw.workDir.SetText(wd) } + // General sw.freqSlider = sw.newFreqSlider() sw.freqValue = widget.NewLabel("") - sw.autoLoad = sw.newAutoUpdateLoad() - sw.autoSave = sw.newAutoUpdateSave() - sw.cursorFollowCrosshair = sw.newCursorFollowCrosshair() - sw.livePreview = sw.newLivePreview() - sw.meshView = sw.newMeshView() - sw.realtimeBars = sw.newRealtimeBars() + sw.autoLoad = checkBox("Load maps from ECU when connected", prefAutoLoad) + sw.autoSave = checkBox("Save changes automaticly if connected to ECU (requires open bin)", prefAutoSave) + sw.cursorFollowCrosshair = checkBox("Cursor follows crosshair in MapViewer (one hand mapping)", prefCursorFollowCrosshair) + sw.livePreview = checkBox("Live preview values in symbollist (uncheck if you have a slow pc)", prefLivePreview) + sw.meshView = checkBox("3D Mesh on map viewing", prefMeshView) + sw.realtimeBars = checkBox("Bars on live preview values (uncheck if you have a slow pc)", prefRealtimeBars) sw.logFormat = sw.newLogFormat() sw.logPath = widget.NewLabel("") sw.logPath.Truncation = fyne.TextTruncateEllipsis - sw.useMPH = sw.newUserMPH() - sw.swapRPMandSpeed = sw.newSwapRPMandSpeed() + sw.useMPH = checkBox("Use mph instead of km/h", prefUseMPH) + sw.swapRPMandSpeed = checkBox("Swap RPM and speed gauge position", prefSwapRPMandSpeed) sw.colorBlindMode = sw.newColorBlindMode() sw.wblSelectContainer = sw.newWBLSelector() // Graphics - sw.plotRendererSelect = sw.newPlotRendererSelect() - sw.meshRendererSelect = sw.newMeshRendererSelect() + sw.plotRendererSelect = indexSelect([]string{"Software", "Shader"}, prefPlotterRenderer) + sw.meshRendererSelect = indexSelect([]string{"Shader", "Polygons", "Software"}, prefMeshRenderer) // CAN sw.adapterSelector = sw.newAdapterSelector() @@ -191,18 +127,11 @@ func (sw *Widget) CreateRenderer() fyne.WidgetRenderer { sw.portDescription = widget.NewLabel("") sw.portDescription.Importance = widget.LowImportance sw.speedSelector = sw.newSpeedSelector() - sw.debugCheckbox = sw.newDebugCheckbox() + sw.debugCheckbox = checkBox("Debug", prefDebug) sw.refreshBtn = sw.newPortRefreshButton() - names := make([]string, 0, len(sw.adapters)) - for name := range sw.adapters { - names = append(names, name) - } - slices.SortFunc(names, func(i, j string) int { - return strings.Compare(strings.ToLower(i), strings.ToLower(j)) - }) - sw.adapterSelector.SetOptions(names) - if ad := fyne.CurrentApp().Preferences().String(prefsAdapter); ad != "" { + sw.adapterSelector.SetOptions(sw.sortedAdapterNames()) + if ad := prefAdapter.get(); ad != "" { sw.adapterSelector.SetSelected(ad) } @@ -216,34 +145,32 @@ func (sw *Widget) CreateRenderer() fyne.WidgetRenderer { tabs.Append(sw.adScannerTab()) tabs.Append(container.NewTabItem("txbridge", txconfigurator.NewConfigurator())) - for _, adapter := range gocan.ListAdapters() { - sw.adapters[adapter.Name] = &adapter - } - sw.loadPreferences() return widget.NewSimpleRenderer(tabs) } -// Public API +func (sw *Widget) sortedAdapterNames() []string { + names := make([]string, 0, len(sw.adapters)) + for name := range sw.adapters { + names = append(names, name) + } + slices.SortFunc(names, func(i, j string) int { + return strings.Compare(strings.ToLower(i), strings.ToLower(j)) + }) + return names +} + +// --- public API ------------------------------------------------------------ var portCache = make(map[string]*enumerator.PortDetails) func (sw *Widget) ListPorts() []string { - var portsList []string ports, err := enumerator.GetDetailedPortsList() - if err != nil { - // m.output(err.Error()) - return []string{} - } - if len(ports) == 0 { - // m.output("No serial ports found!") + if err != nil || len(ports) == 0 { return []string{} } + portsList := make([]string, 0, len(ports)) for _, port := range ports { - // m.output(fmt.Sprintf("Found port: %s", port.Name)) - // if port.IsUSB { - // m.output(fmt.Sprintf(" USB ID %s:%s", port.VID, port.PID)) - // m.output(fmt.Sprintf(" USB serial %s", port.SerialNumber)) portsList = append(portsList, port.Name) portCache[port.Name] = port } @@ -258,7 +185,7 @@ func (sw *Widget) AddAdapters(adapters []*proto.AdapterInfo) { sw.mu.Lock() defer sw.mu.Unlock() for _, adapter := range adapters { - adapter := &gocan.AdapterInfo{ + info := &gocan.AdapterInfo{ Name: adapter.GetName(), Description: adapter.GetDescription(), Capabilities: gocan.AdapterCapabilities{ @@ -268,36 +195,35 @@ func (sw *Widget) AddAdapters(adapters []*proto.AdapterInfo) { }, RequiresSerialPort: adapter.GetRequireSerialPort(), } - - if _, found := sw.adapters[adapter.Name]; found { + if _, found := sw.adapters[info.Name]; found { continue } - sw.adapters[adapter.Name] = adapter + sw.adapters[info.Name] = info } } -func (c *Widget) Disable() { - c.adapterSelector.Disable() - c.portSelector.Disable() - c.speedSelector.Disable() - c.debugCheckbox.Disable() - c.refreshBtn.Disable() +func (sw *Widget) Disable() { + sw.adapterSelector.Disable() + sw.portSelector.Disable() + sw.speedSelector.Disable() + sw.debugCheckbox.Disable() + sw.refreshBtn.Disable() } -func (c *Widget) Enable() { - c.adapterSelector.Enable() - c.portSelector.Enable() - c.speedSelector.Enable() - c.debugCheckbox.Enable() - c.refreshBtn.Enable() +func (sw *Widget) Enable() { + sw.adapterSelector.Enable() + sw.portSelector.Enable() + sw.speedSelector.Enable() + sw.debugCheckbox.Enable() + sw.refreshBtn.Enable() - if info, found := c.adapters[c.adapterSelector.Selected]; found { + if info, found := sw.adapters[sw.adapterSelector.Selected]; found { if info.RequiresSerialPort { - c.portSelector.Enable() - c.speedSelector.Enable() + sw.portSelector.Enable() + sw.speedSelector.Enable() } else { - c.portSelector.Disable() - c.speedSelector.Disable() + sw.portSelector.Disable() + sw.speedSelector.Disable() } } } diff --git a/pkg/widgets/settings/settings_getters.go b/pkg/widgets/settings/settings_getters.go deleted file mode 100644 index f12404e4..00000000 --- a/pkg/widgets/settings/settings_getters.go +++ /dev/null @@ -1,251 +0,0 @@ -package settings - -import ( - "errors" - "fmt" - "log" - "strconv" - "strings" - - "fyne.io/fyne/v2" - "github.com/roffe/gocan" - "github.com/roffe/txlogger/pkg/colors" - "github.com/roffe/txlogger/pkg/common" - "github.com/roffe/txlogger/pkg/datalogger" - "github.com/roffe/txlogger/pkg/ota" - "github.com/roffe/txlogger/pkg/wbl/aem" - "github.com/roffe/txlogger/pkg/wbl/ecumaster" - "github.com/roffe/txlogger/pkg/wbl/innovate" - "github.com/roffe/txlogger/pkg/wbl/plx" - "github.com/roffe/txlogger/pkg/wbl/stag" - "github.com/roffe/txlogger/pkg/wbl/zeitronix" - "github.com/roffe/txlogger/pkg/widgets/meshgrid" - "github.com/roffe/txlogger/pkg/widgets/plotter" -) - -func (cs *Widget) GetAdapter(ecuType string) (gocan.Adapter, error) { - return cs.GetAdapterWithExtraFilters(ecuType, []uint32{}) -} - -func (cs *Widget) GetAdapterWithExtraFilters(ecuType string, filters []uint32) (gocan.Adapter, error) { - debug := fyne.CurrentApp().Preferences().Bool(prefsDebug) - port := fyne.CurrentApp().Preferences().String(prefsPort) - - baudstring := fyne.CurrentApp().Preferences().String(prefsSpeed) - switch baudstring { - case "1mbit": - baudstring = "1000000" - case "2mbit": - baudstring = "2000000" - case "3mbit": - baudstring = "3000000" - } - - if baudstring == "" { - baudstring = "1000000" - } - - baudrate, err := strconv.Atoi(baudstring) - if err != nil { - return nil, err - } - adapterName := fyne.CurrentApp().Preferences().String(prefsAdapter) - - if adapterName == "" { - return nil, errors.New("Select CANbus adapter in settings") //lint:ignore ST1005 This is ok - } - - if ad, found := cs.adapters[adapterName]; found { - if ad.RequiresSerialPort { - if port == "" { - return nil, errors.New("Select port in setings") //lint:ignore ST1005 This is ok - } - if baudstring == "" { - return nil, errors.New("Select port speed in settings") //lint:ignore ST1005 This is ok - } - } - } - - var canFilter []uint32 - var canRate float64 - - switch ecuType { - case "T5", "Trionic 5": - canFilter = []uint32{0xC} - canRate = 615.384 - case "T7", "Trionic 7": - if strings.Contains(adapterName, "ELM327") || strings.Contains(adapterName, "STN") || strings.Contains(adapterName, "OBDLink") || strings.HasSuffix(adapterName, "Wifi") { - canFilter = []uint32{0x238, 0x258, 0x270} - } else { - canFilter = []uint32{0x1A0, 0x238, 0x258, 0x270, 0x280, 0x3A0, 0x664, 0x665} - } - if fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") == "CAN" { - canFilter = append(canFilter, 0x180) - } - canRate = 500 - case "T8", "Trionic 8", "Trionic 8 MCP", "Z22SE", "Z22SE MCP": - if strings.Contains(adapterName, "ELM327") || strings.Contains(adapterName, "STN") || strings.Contains(adapterName, "OBDLink") { - canFilter = []uint32{0x5E8, 0x7E8} - } else { - canFilter = []uint32{0x5E8, 0x7E8, 0x664, 0x665} - } - if fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") == "CAN" { - canFilter = append(canFilter, 0x180) - } - canFilter = append(canFilter, filters...) - - canRate = 500 - } - - cfg := &gocan.AdapterConfig{ - Port: port, - PortBaudrate: baudrate, - CANRate: canRate, - CANFilter: canFilter, - Debug: debug, - PrintVersion: true, - } - - if strings.HasPrefix(adapterName, "J2534") { // || strings.HasPrefix(adapterName, "CANlib") { - return gocan.NewGWClient(adapterName, cfg) - } - - if adapterName == "txbridge wifi" { - //ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - //defer cancel() - //addr, err := mdns.Query(ctx, "txbridge.local") - //if err != nil { - // cs.cfg.Logger(fmt.Sprintf("Failed to resolve txbridge address via mDNS: %v", err)) - //} else { - cfg.AdditionalConfig = map[string]string{ - "address": fmt.Sprintf("%s:%d", "192.168.4.1", 1337), - "minversion": ota.MinimumtxbridgeVersion, - } - //} - } - return gocan.NewAdapter(adapterName, cfg) -} - -func (sw *Widget) GetADScannerSymbolName() string { - return fyne.CurrentApp().Preferences().String(prefsWBLADScannerSymbol) -} - -func (sw *Widget) GetWidebandName() string { - return fyne.CurrentApp().Preferences().StringWithFallback(prefsWblSource, "None") -} - -func (sw *Widget) GetWidebandSymbolName() string { - useADScanner := fyne.CurrentApp().Preferences().Bool(prefsUseADScanner) - switch sw.GetWidebandName() { - case "ECU": - switch sw.cfg.SelectedEcuFunc() { - case "T5": - return datalogger.LAMBDAADSCANNER // Lambda.ADScanner - case "T7": - if useADScanner { - return datalogger.LAMBDAADSCANNER // Lambda.ADScanner - } - return "DisplProt.LambdaScanner" - case "T8": - if useADScanner { - return datalogger.LAMBDAADSCANNER // Lambda.ADScanner - } - return "LambdaScan.LambdaScanner" - default: - return "None" - } - case aem.ProductString, - "CombiAdapter", - ecumaster.ProductString, - innovate.ProductString, - plx.ProductString, - stag.ProductString, - zeitronix.ProductString: - return datalogger.EXTERNALWBLSYM // Lambda.External - default: - return "None" - } -} - -func (sw *Widget) GetColorBlindMode() colors.ColorBlindMode { - return colors.StringToColorBlindMode(fyne.CurrentApp().Preferences().StringWithFallback(prefsColorBlindMode, "Normal")) -} - -func (sw *Widget) GetWidebandPort() string { - return fyne.CurrentApp().Preferences().String(prefsWBLPort) -} - -func (sw *Widget) GetWBLSupportPoints() []int { - return fyne.CurrentApp().Preferences().IntListWithFallback(prefsWBLSupportPoints, []int{0, 1024}) -} - -func (sw *Widget) GetWBLLambdaValues() []float64 { - return fyne.CurrentApp().Preferences().FloatListWithFallback(prefsWBLLambdaValues, []float64{0.5, 1.5}) -} - -func (sw *Widget) GetUseADScanner() bool { - if fyne.CurrentApp().Preferences().String(prefsWblSource) != "ECU" { - return false - } - return fyne.CurrentApp().Preferences().Bool(prefsUseADScanner) -} - -func (sw *Widget) GetFreq() int { - return int(fyne.CurrentApp().Preferences().IntWithFallback(prefsFreq, 25)) -} - -func (sw *Widget) GetAutoSave() bool { - return fyne.CurrentApp().Preferences().Bool(prefsAutoUpdateSaveEcu) -} - -func (sw *Widget) GetAutoLoad() bool { - return fyne.CurrentApp().Preferences().Bool(prefsAutoUpdateLoadEcu) -} - -func (sw *Widget) GetLivePreview() bool { - return fyne.CurrentApp().Preferences().Bool(prefsLivePreview) -} - -func (sw *Widget) GetRealtimeBars() bool { - return fyne.CurrentApp().Preferences().Bool(prefsRealtimeBars) -} - -func (sw *Widget) GetMeshView() bool { - return fyne.CurrentApp().Preferences().Bool(prefsMeshView) -} - -func (sw *Widget) GetLogFormat() string { - return fyne.CurrentApp().Preferences().StringWithFallback(prefsLogFormat, "CSV") -} - -func (sw *Widget) GetLogPath() string { - p := fyne.CurrentApp().Preferences().String(prefsLogPath) - if p == "" { - var err error - p, err = common.GetLogPath() - if err != nil { - log.Println("GetLogPath: ", err) - } - } - return p -} - -func (sw *Widget) GetUseMPH() bool { - return fyne.CurrentApp().Preferences().Bool(prefsUseMPH) -} - -func (sw *Widget) GetSwapRPMandSpeed() bool { - return fyne.CurrentApp().Preferences().Bool(prefsSwapRPMandSpeed) -} - -func (sw *Widget) GetCursorFollowCrosshair() bool { - return fyne.CurrentApp().Preferences().Bool(prefsCursorFollowCrosshair) -} - -func (sw *Widget) GetPlotterRenderer() plotter.PlotBackend { - return plotter.PlotBackend(fyne.CurrentApp().Preferences().IntWithFallback(prefsPlotterRenderer, 0)) -} - -func (sw *Widget) GetMeshRenderer() meshgrid.RenderBackend { - return meshgrid.RenderBackend(fyne.CurrentApp().Preferences().IntWithFallback(prefsMeshRenderer, 2)) -} diff --git a/pkg/widgets/settings/settings_internal.go b/pkg/widgets/settings/settings_internal.go deleted file mode 100644 index 53c36798..00000000 --- a/pkg/widgets/settings/settings_internal.go +++ /dev/null @@ -1,383 +0,0 @@ -package settings - -import ( - "strconv" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" - "github.com/roffe/gocan" - "github.com/roffe/txlogger/pkg/assets" - "github.com/roffe/txlogger/pkg/colors" - "github.com/roffe/txlogger/pkg/common" - "github.com/roffe/txlogger/pkg/ebus" - "github.com/roffe/txlogger/pkg/wbl/aem" - "github.com/roffe/txlogger/pkg/wbl/ecumaster" - "github.com/roffe/txlogger/pkg/wbl/innovate" - "github.com/roffe/txlogger/pkg/wbl/plx" - "github.com/roffe/txlogger/pkg/wbl/stag" - "github.com/roffe/txlogger/pkg/wbl/zeitronix" -) - -func newImageFromResource(name string) *canvas.Image { - var img *canvas.Image - switch name { - case "mtx-l": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.MtxL)) - img.SetMinSize(fyne.NewSize(224, 224)) - case "lc-2": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.Lc2)) - img.SetMinSize(fyne.NewSize(400, 224)) - case "uego": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.Uego)) - img.SetMinSize(fyne.NewSize(315, 224)) - case "lambdatocan": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.LambdaToCan)) - img.SetMinSize(fyne.NewSize(481, 224)) - case "t7": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.T7)) - img.SetMinSize(fyne.NewSize(320, 224)) - case "plx": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.PLX)) - img.SetMinSize(fyne.NewSize(470, 224)) - case "combi": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.CombiV2)) - img.SetMinSize(fyne.NewSize(360, 245)) - case "zeitronix": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.ZeitronixZT2)) - img.SetMinSize(fyne.NewSize(252, 252)) - case "stagafr": - img = canvas.NewImageFromResource(fyne.NewStaticResource(name, assets.STAGAfr)) - img.SetMinSize(fyne.NewSize(252, 252)) - } - img.FillMode = canvas.ImageFillContain - img.ScaleMode = canvas.ImageScaleFastest - - return img -} - -var logFormats = []string{"CSV", "BPL" /*"TXL"*/} - -func (sw *Widget) newLogFormat() *widget.Select { - return widget.NewSelect(logFormats, func(s string) { - fyne.CurrentApp().Preferences().SetString(prefsLogFormat, s) - }) -} - -var wblAdapters = []string{ - "None", - "ECU", - aem.ProductString, - "CombiAdapter", - ecumaster.ProductString, - innovate.ProductString, - plx.ProductString, - stag.ProductString, - zeitronix.ProductString, -} - -func (sw *Widget) newWBLSelector() *fyne.Container { - wblImages := map[string]*canvas.Image{ - "ECU": newImageFromResource("t7"), // Using T7 image for ECU as a placeholder - ecumaster.ProductString: newImageFromResource("lambdatocan"), - innovate.ProductString: newImageFromResource("mtx-l"), // Using MTX-L image for Innovate as a placeholder - aem.ProductString: newImageFromResource("uego"), - plx.ProductString: newImageFromResource("plx"), - "CombiAdapter": newImageFromResource("combi"), - zeitronix.ProductString: newImageFromResource("zeitronix"), - stag.ProductString: newImageFromResource("stagafr"), - } - - sw.wblSource = widget.NewSelect(wblAdapters, func(s string) { - fyne.CurrentApp().Preferences().SetString(prefsWblSource, s) - fyne.CurrentApp().Preferences().SetString(prefsWidebandSymbolName, sw.GetWidebandSymbolName()) - - var adScanner, portSelect bool - - img, found := wblImages[s] - if found && img != nil { - sw.images.wblImage.Resource = img.Resource - sw.images.wblImage.SetMinSize(img.MinSize()) - sw.images.wblImage.Refresh() - } - - switch s { - case "ECU", "CombiAdapter": - adScanner = true - portSelect = false - case aem.ProductString, innovate.ProductString, plx.ProductString, stag.ProductString, zeitronix.ProductString: - portSelect = true - case ecumaster.ProductString: - portSelect = false - default: - portSelect = false - } - - if portSelect { - sw.wblPortLabel.Show() - sw.wblPortSelect.Show() - sw.wblPortRefreshButton.Show() - } else { - sw.wblPortLabel.Hide() - sw.wblPortSelect.Hide() - sw.wblPortRefreshButton.Hide() - } - - if adScanner { - sw.wblADscanner.Show() - sw.wblADScannerSymbol.Show() - } else { - sw.wblADscanner.Hide() - sw.wblADScannerSymbol.Hide() - } - }) - return container.NewBorder( - nil, - nil, - widget.NewLabel("Source"), - nil, - sw.wblSource, - ) -} - -func (sw *Widget) newFreqSlider() *widget.Slider { - slider := widget.NewSlider(5, 300) - slider.Step = 5 - slider.OnChanged = func(f float64) { - sw.freqValue.SetText(strconv.FormatFloat(f, 'f', 0, 64)) - } - slider.OnChangeEnded = func(f float64) { - fyne.CurrentApp().Preferences().SetInt(prefsFreq, int(f)) - } - return slider -} - -func (sw *Widget) newADscannerCheck() *widget.Check { - return widget.NewCheck("use AD Scanner (don't forget to add symbol)", func(b bool) { - if b { - sw.wblADScannerSymbol.Show() - } else { - sw.wblADScannerSymbol.Hide() - } - fyne.CurrentApp().Preferences().SetBool(prefsUseADScanner, b) - }) -} - -func (sw *Widget) newMeshView() *widget.Check { - return widget.NewCheck("3D Mesh on map viewing", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsMeshView, b) - }) -} - -func (sw *Widget) newAutoUpdateLoad() *widget.Check { - return widget.NewCheck("Load maps from ECU when connected", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsAutoUpdateLoadEcu, b) - }) -} - -func (sw *Widget) newAutoUpdateSave() *widget.Check { - return widget.NewCheck("Save changes automaticly if connected to ECU (requires open bin)", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsAutoUpdateSaveEcu, b) - }) -} - -func (sw *Widget) newCursorFollowCrosshair() *widget.Check { - return widget.NewCheck("Cursor follows crosshair in MapViewer (one hand mapping)", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsCursorFollowCrosshair, b) - }) -} - -func (sw *Widget) newLivePreview() *widget.Check { - return widget.NewCheck("Live preview values in symbollist (uncheck if you have a slow pc)", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsLivePreview, b) - }) -} - -func (sw *Widget) newRealtimeBars() *widget.Check { - return widget.NewCheck("Bars on live preview values (uncheck if you have a slow pc)", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsRealtimeBars, b) - }) -} - -func (sw *Widget) newUserMPH() *widget.Check { - return widget.NewCheck("Use mph instead of km/h", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsUseMPH, b) - }) -} - -func (sw *Widget) newSwapRPMandSpeed() *widget.Check { - return widget.NewCheck("Swap RPM and speed gauge position", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsSwapRPMandSpeed, b) - }) -} - -func (sw *Widget) newColorBlindMode() *widget.Select { - return widget.NewSelect(colors.SupportedColorBlindModes[:], func(s string) { - fyne.CurrentApp().Preferences().SetString(prefsColorBlindMode, s) - ebus.Publish(ebus.TOPIC_COLORBLINDMODE, float64(sw.colorBlindMode.SelectedIndex())) - }) -} - -func (sw *Widget) newPlotRendererSelect() *widget.Select { - return widget.NewSelect([]string{"Software", "Shader"}, func(selection string) { - var mode int - switch selection { - case "Software": - mode = 0 - case "Shader": - mode = 1 - } - fyne.CurrentApp().Preferences().SetInt(prefsPlotterRenderer, mode) - }) -} - -func (sw *Widget) newMeshRendererSelect() *widget.Select { - return widget.NewSelect([]string{"Shader", "Polygons", "Software"}, func(selection string) { - var mode int - switch selection { - case "Shader": - mode = 0 - case "Polygons": - mode = 1 - case "Software": - mode = 2 - } - fyne.CurrentApp().Preferences().SetInt(prefsMeshRenderer, mode) - }) -} - -func (sw *Widget) newAdapterSelector() *widget.Select { - return widget.NewSelect(gocan.ListAdapterNames(), func(s string) { - if info, found := sw.adapters[s]; found { - fyne.CurrentApp().Preferences().SetString(prefsAdapter, s) - if info.RequiresSerialPort { - sw.portSelector.Enable() - sw.speedSelector.Enable() - return - } else { - sw.portDescription.SetText("") - } - sw.portSelector.Disable() - sw.speedSelector.Disable() - } - }) -} - -func (sw *Widget) newPortSelector() *widget.Select { - return widget.NewSelect(sw.ListPorts(), func(s string) { - fyne.CurrentApp().Preferences().SetString(prefsPort, s) - itm, ok := portCache[s] - if ok { - /* - var desc string - if itm.Manufacturer != "" { - desc += itm.Manufacturer - - if itm.Product != "" { - if desc != "" { - desc += " " - } - desc += itm.Product - } - if itm.SerialNumber != "" { - if desc != "" { - desc += " " - } - desc += itm.SerialNumber - } - */ - sw.portDescription.SetText(itm.SerialNumber) - } else { - sw.portDescription.SetText("") - } - }) -} - -func (sw *Widget) newSpeedSelector() *widget.Select { - return widget.NewSelect(portSpeeds, func(s string) { - fyne.CurrentApp().Preferences().SetString(prefsSpeed, s) - }) -} - -func (sw *Widget) newDebugCheckbox() *widget.Check { - return widget.NewCheck("Debug", func(b bool) { - fyne.CurrentApp().Preferences().SetBool(prefsDebug, b) - }) -} - -func (sw *Widget) newPortRefreshButton() *widget.Button { - return widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { - sw.portSelector.Options = sw.ListPorts() - sw.portSelector.Refresh() - }) -} - -func (sw *Widget) loadPreferences() { - freq := fyne.CurrentApp().Preferences().IntWithFallback(prefsFreq, 25) - sw.freqSlider.SetValue(float64(freq)) - loadPrefsCheck(sw.autoLoad, prefsAutoUpdateLoadEcu, true) - loadPrefsCheck(sw.autoSave, prefsAutoUpdateSaveEcu, false) - loadPrefsCheck(sw.cursorFollowCrosshair, prefsCursorFollowCrosshair, false) - loadPrefsCheck(sw.livePreview, prefsLivePreview, true) - loadPrefsCheck(sw.meshView, prefsMeshView, true) - loadPrefsCheck(sw.realtimeBars, prefsRealtimeBars, true) - loadPrefsSelect(sw.logFormat, prefsLogFormat, "CSV") - logPath, err := common.GetLogPath() - if err != nil { - fyne.LogError("Could not get log path", err) - } - loadPrefsText(sw.logPath, prefsLogPath, logPath) - loadPrefsText(sw.logPath, prefsLogPath, logPath) - loadPrefsSelect(sw.wblSource, prefsWblSource, "None") - loadPrefsCheck(sw.wblADscanner, prefsUseADScanner, false) - if sw.wblADscanner.Checked && sw.wblSource.Selected == "ECU" { - sw.wblADScannerSymbol.Show() - } else { - sw.wblADScannerSymbol.Hide() - } - - loadPrefsCheck(sw.useMPH, prefsUseMPH, false) - loadPrefsCheck(sw.swapRPMandSpeed, prefsSwapRPMandSpeed, false) - loadPrefsSelect(sw.wblPortSelect, prefsWBLPort, "") - loadPrefsSelect(sw.colorBlindMode, prefsColorBlindMode, "Normal") - - loadPrefsSelect(sw.adapterSelector, prefsAdapter, "") - loadPrefsSelect(sw.portSelector, prefsPort, "") - loadPrefsSelect(sw.speedSelector, prefsSpeed, "115200") - loadPrefsCheck(sw.debugCheckbox, prefsDebug, false) - - // graphics settings - - sw.plotRendererSelect.SetSelectedIndex(fyne.CurrentApp().Preferences().IntWithFallback(prefsPlotterRenderer, 0)) - sw.meshRendererSelect.SetSelectedIndex(fyne.CurrentApp().Preferences().IntWithFallback(prefsMeshRenderer, 2)) -} - -func loadPrefsSelect(s *widget.Select, prefKey string, fallback string) { - s.SetSelected(fyne.CurrentApp().Preferences().StringWithFallback(prefKey, fallback)) -} - -func loadPrefsCheck(box *widget.Check, prefKey string, fallback bool) { - box.SetChecked(fyne.CurrentApp().Preferences().BoolWithFallback(prefKey, fallback)) -} - -func loadPrefsText(obj SetText, prefKey string, fallback string) { - obj.SetText(fyne.CurrentApp().Preferences().StringWithFallback(prefKey, fallback)) -} - -/* -func positiveFloatValidator(s string) (float64, error) { - s = strings.ReplaceAll(s, ",", ".") - s = strings.TrimSuffix(s, ".") - - val, err := strconv.ParseFloat(s, 64) - if err != nil { - return 0, errors.New("invalid number") - } - if val < 0 { - return 0, errors.New("must be positive") - } - return val, nil -} -*/ diff --git a/pkg/widgets/settings/settings_tabs.go b/pkg/widgets/settings/settings_tabs.go deleted file mode 100644 index eccfa5da..00000000 --- a/pkg/widgets/settings/settings_tabs.go +++ /dev/null @@ -1,242 +0,0 @@ -package settings - -import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" - "github.com/roffe/txlogger/pkg/common" - xlayout "github.com/roffe/txlogger/pkg/layout" - "github.com/roffe/txlogger/pkg/widgets" -) - -func (sw *Widget) generalTab() *container.TabItem { - return container.NewTabItem("General", container.NewVBox( - container.NewBorder( - nil, - nil, - widget.NewLabel("WD"), - nil, - sw.workDir, - ), - container.NewBorder( - nil, - nil, - widget.NewIcon(theme.InfoIcon()), - nil, - sw.autoLoad, - ), - container.NewBorder( - nil, - nil, - widget.NewIcon(theme.WarningIcon()), - nil, - sw.autoSave, - ), - container.NewBorder( - nil, - nil, - widget.NewIcon(theme.MoveUpIcon()), - nil, - sw.cursorFollowCrosshair, - ), - container.NewBorder( - nil, - nil, - widget.NewIcon(theme.SearchIcon()), - nil, - container.NewVBox( - sw.livePreview, - sw.realtimeBars, - ), - ), - container.NewBorder( - nil, - nil, - widget.NewIcon(theme.ViewFullScreenIcon()), - nil, - sw.meshView, - ), - container.NewBorder( - nil, - nil, - widget.NewLabel("Color blind mode"), - nil, - sw.colorBlindMode, - ), - )) -} - -func (sw *Widget) graphicsTab() *container.TabItem { - return container.NewTabItem("Graphics", container.NewVBox( - container.NewBorder( - nil, - nil, - widget.NewLabel("Plot renderer"), - nil, - sw.plotRendererSelect, - ), - container.NewBorder( - nil, - nil, - widget.NewLabel("Mesh renderer"), - nil, - sw.meshRendererSelect, - ), - )) -} - -func (sw *Widget) canTab() *container.TabItem { - return container.NewTabItem("CAN", container.NewVBox( - container.NewBorder( - nil, - nil, - xlayout.NewFixedWidth(70, widget.NewLabel("Adapter")), - sw.debugCheckbox, - sw.adapterSelector, - ), - container.NewBorder( - nil, - nil, - xlayout.NewFixedWidth(70, widget.NewLabel("Port")), - sw.refreshBtn, - sw.portSelector, - ), - container.NewBorder( - nil, - nil, - xlayout.NewFixedWidth(70, widget.NewLabel("Info")), - nil, - sw.portDescription, - ), - container.NewBorder( - nil, - nil, - xlayout.NewFixedWidth(70, widget.NewLabel("Speed")), - nil, - sw.speedSelector, - ), - )) -} - -func (sw *Widget) loggingTab() *container.TabItem { - return container.NewTabItem("Logging", container.NewVBox( - container.NewBorder( - nil, - nil, - widget.NewLabel("Logging rate (Hz)"), - sw.freqValue, - sw.freqSlider, - ), - widget.NewSeparator(), - container.NewBorder( - nil, - nil, - widget.NewLabel("Log format"), - nil, - sw.logFormat, - ), - container.NewBorder( - nil, - container.NewGridWithColumns(2, - widget.NewButtonWithIcon("Reset", theme.ContentClearIcon(), func() { - logPath, err := common.GetLogPath() - if err != nil { - fyne.LogError("Could not get log path", err) - } - sw.logPath.SetText(logPath) - fyne.CurrentApp().Preferences().SetString(prefsLogPath, logPath) - }), - widget.NewButtonWithIcon("Browse", theme.FileIcon(), func() { - cb := func(dir string) { - sw.logPath.SetText(dir) - fyne.CurrentApp().Preferences().SetString(prefsLogPath, dir) - } - widgets.SelectFolder(cb) - }), - ), - widget.NewLabel("Log folder"), - nil, - sw.logPath, - ), - )) -} - -func (sw *Widget) wblTab() *container.TabItem { - sw.wblPortLabel = widget.NewLabel("WBL Port") - sw.wblPortSelect = widget.NewSelect(append([]string{"txbridge", "CAN"}, sw.ListPorts()...), func(s string) { - fyne.CurrentApp().Preferences().SetString(prefsWBLPort, s) - }) - - sw.wblPortRefreshButton = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { - sw.wblPortSelect.Options = append([]string{"txbridge", "CAN"}, sw.ListPorts()...) - sw.wblPortSelect.Refresh() - }) - - sw.wblADscanner = sw.newADscannerCheck() - - adSymbols := []string{ - "AD_EGR", - "DisplProt.AD_Scanner", - "LambdaScan.AD_Scanner", - "LambdaScan.AD_Scanner2", - } - - sw.wblADScannerSymbol = widget.NewSelect(adSymbols, func(s string) { - fyne.CurrentApp().Preferences().SetString(prefsWBLADScannerSymbol, s) - }) - sw.wblADScannerSymbol.SetSelected(sw.GetADScannerSymbolName()) - - return container.NewTabItem( - "WBL", - container.NewBorder( - container.NewHBox( - layout.NewSpacer(), - sw.images.wblImage, - layout.NewSpacer(), - ), - nil, - nil, - nil, - container.NewVBox( - sw.wblSelectContainer, - sw.wblADscanner, - container.NewBorder( - nil, - nil, - sw.wblPortLabel, - sw.wblPortRefreshButton, - sw.wblPortSelect, - ), - sw.wblADScannerSymbol, - ), - ), - ) -} - -func (sw *Widget) dashboardTab() *container.TabItem { - return container.NewTabItem("Dashboard", container.NewVBox( - widget.NewLabel("Dashboard settings"), - container.NewBorder( - nil, - nil, - widget.NewIcon(theme.InfoIcon()), - nil, - sw.swapRPMandSpeed, - ), - container.NewBorder( - nil, - nil, - widget.NewIcon(theme.InfoIcon()), - nil, - sw.useMPH, - ), - )) -} - -func (sw *Widget) adScannerTab() *container.TabItem { - sw.wbleditor = NewWBLEditor(sw.GetWBLSupportPoints(), sw.GetWBLLambdaValues()) - sw.wbleditor.Hide() - return container.NewTabItem("AD Scanner", sw.wbleditor) -} diff --git a/pkg/widgets/settings/tabs.go b/pkg/widgets/settings/tabs.go new file mode 100644 index 00000000..6154761d --- /dev/null +++ b/pkg/widgets/settings/tabs.go @@ -0,0 +1,128 @@ +package settings + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/common" + xlayout "github.com/roffe/txlogger/pkg/layout" + "github.com/roffe/txlogger/pkg/widgets" +) + +// leftLabeled places a text label to the left of content. +func leftLabeled(label string, content fyne.CanvasObject) *fyne.Container { + return container.NewBorder(nil, nil, widget.NewLabel(label), nil, content) +} + +// leftIcon places an icon to the left of content. +func leftIcon(res fyne.Resource, content fyne.CanvasObject) *fyne.Container { + return container.NewBorder(nil, nil, widget.NewIcon(res), nil, content) +} + +func (sw *Widget) generalTab() *container.TabItem { + return container.NewTabItem("General", container.NewVBox( + leftLabeled("WD", sw.workDir), + leftIcon(theme.InfoIcon(), sw.autoLoad), + leftIcon(theme.WarningIcon(), sw.autoSave), + leftIcon(theme.MoveUpIcon(), sw.cursorFollowCrosshair), + leftIcon(theme.SearchIcon(), container.NewVBox(sw.livePreview, sw.realtimeBars)), + leftIcon(theme.ViewFullScreenIcon(), sw.meshView), + leftLabeled("Color blind mode", sw.colorBlindMode), + )) +} + +func (sw *Widget) graphicsTab() *container.TabItem { + return container.NewTabItem("Graphics", container.NewVBox( + leftLabeled("Plot renderer", sw.plotRendererSelect), + leftLabeled("Mesh renderer", sw.meshRendererSelect), + )) +} + +func (sw *Widget) canTab() *container.TabItem { + fixedLabel := func(text string) fyne.CanvasObject { + return xlayout.NewFixedWidth(70, widget.NewLabel(text)) + } + return container.NewTabItem("CAN", container.NewVBox( + container.NewBorder(nil, nil, fixedLabel("Adapter"), sw.debugCheckbox, sw.adapterSelector), + container.NewBorder(nil, nil, fixedLabel("Port"), sw.refreshBtn, sw.portSelector), + container.NewBorder(nil, nil, fixedLabel("Info"), nil, sw.portDescription), + container.NewBorder(nil, nil, fixedLabel("Speed"), nil, sw.speedSelector), + )) +} + +func (sw *Widget) loggingTab() *container.TabItem { + logFolderButtons := container.NewGridWithColumns(2, + widget.NewButtonWithIcon("Reset", theme.ContentClearIcon(), func() { + logPath, err := common.GetLogPath() + if err != nil { + fyne.LogError("Could not get log path", err) + } + sw.logPath.SetText(logPath) + prefLogPath.set(logPath) + }), + widget.NewButtonWithIcon("Browse", theme.FileIcon(), func() { + widgets.SelectFolder(func(dir string) { + sw.logPath.SetText(dir) + prefLogPath.set(dir) + }) + }), + ) + + return container.NewTabItem("Logging", container.NewVBox( + container.NewBorder(nil, nil, widget.NewLabel("Logging rate (Hz)"), sw.freqValue, sw.freqSlider), + widget.NewSeparator(), + leftLabeled("Log format", sw.logFormat), + container.NewBorder(nil, logFolderButtons, widget.NewLabel("Log folder"), nil, sw.logPath), + )) +} + +func (sw *Widget) wblTab() *container.TabItem { + wblPorts := func() []string { + return append([]string{"txbridge", "CAN"}, sw.ListPorts()...) + } + + sw.wblPortLabel = widget.NewLabel("WBL Port") + sw.wblPortSelect = widget.NewSelect(wblPorts(), prefWBLPort.set) + sw.wblPortRefreshButton = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { + sw.wblPortSelect.Options = wblPorts() + sw.wblPortSelect.Refresh() + }) + + sw.wblADscanner = sw.newADscannerCheck() + + adSymbols := []string{ + "AD_EGR", + "DisplProt.AD_Scanner", + "LambdaScan.AD_Scanner", + "LambdaScan.AD_Scanner2", + } + sw.wblADScannerSymbol = widget.NewSelect(adSymbols, prefWBLADScannerSymbol.set) + sw.wblADScannerSymbol.SetSelected(sw.GetADScannerSymbolName()) + + body := container.NewVBox( + sw.wblSelectContainer, + sw.wblADscanner, + container.NewBorder(nil, nil, sw.wblPortLabel, sw.wblPortRefreshButton, sw.wblPortSelect), + sw.wblADScannerSymbol, + ) + + image := container.NewHBox(layout.NewSpacer(), sw.wblImage, layout.NewSpacer()) + + return container.NewTabItem("WBL", container.NewBorder(image, nil, nil, nil, body)) +} + +func (sw *Widget) dashboardTab() *container.TabItem { + return container.NewTabItem("Dashboard", container.NewVBox( + widget.NewLabel("Dashboard settings"), + leftIcon(theme.InfoIcon(), sw.swapRPMandSpeed), + leftIcon(theme.InfoIcon(), sw.useMPH), + )) +} + +func (sw *Widget) adScannerTab() *container.TabItem { + sw.wbleditor = NewWBLEditor(sw.GetWBLSupportPoints(), sw.GetWBLLambdaValues()) + sw.wbleditor.Hide() + return container.NewTabItem("AD Scanner", sw.wbleditor) +} diff --git a/pkg/widgets/settings/wbleditor.go b/pkg/widgets/settings/wbleditor.go index ddcf3f31..b30774fe 100644 --- a/pkg/widgets/settings/wbleditor.go +++ b/pkg/widgets/settings/wbleditor.go @@ -3,7 +3,6 @@ package settings import ( "encoding/json" "fmt" - "image/color" "os" "path/filepath" "sort" @@ -11,10 +10,8 @@ import ( "strings" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" - "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/roffe/txlogger/pkg/common" @@ -35,6 +32,20 @@ var builtInPresets = map[string]adScannerPreset{ }, } +type adScannerPreset struct { + ECU string `json:"ecu"` + Y []int `json:"y"` + Z []float64 `json:"z"` +} + +// adResolutionForECU returns the AD converter resolution for an ECU type. +func adResolutionForECU(ecu string) int { + if ecu == "T5" { + return 255 + } + return 1023 +} + type mapRow struct { y int z float64 @@ -55,7 +66,6 @@ type WBLEditor struct { presetSelect *widget.Select graph *graphView adresolution int - // lastEcu string } func NewWBLEditor(yAxis []int, zValues []float64) *WBLEditor { @@ -65,16 +75,7 @@ func NewWBLEditor(yAxis []int, zValues []float64) *WBLEditor { m.rows = append(m.rows, &mapRow{y: yAxis[i], z: zValues[i]}) } m.ExtendBaseWidget(m) - - switch fyne.CurrentApp().Preferences().String(prefsLastADScannerECU) { - case "T5": - m.adresolution = 255 - case "T7", "T8": - m.adresolution = 1023 - default: - m.adresolution = 1023 - } - + m.adresolution = adResolutionForECU(prefLastADScannerECU.get()) return m } @@ -179,6 +180,26 @@ func (m *WBLEditor) removeRow(r *mapRow) { m.refreshGraph() } +// setRows replaces all rows with the supplied y/z pairs, rebuilding their +// widgets and the rows container (used when loading presets at runtime). +func (m *WBLEditor) setRows(yAxis []int, zValues []float64) { + m.rows = nil + n := min(len(yAxis), len(zValues)) + for i := range n { + r := &mapRow{y: yAxis[i], z: zValues[i]} + m.buildRow(r) + m.rows = append(m.rows, r) + } + if m.rowsBox != nil { + m.rowsBox.Objects = nil + for _, r := range m.rows { + m.rowsBox.Add(r.hb) + } + m.rowsBox.Refresh() + } + m.refreshGraph() +} + func (m *WBLEditor) refreshGraph() { if m.graph != nil { m.graph.Refresh() @@ -186,8 +207,8 @@ func (m *WBLEditor) refreshGraph() { } func (m *WBLEditor) save() { - fyne.CurrentApp().Preferences().SetIntList(prefsWBLSupportPoints, m.YAxis()) - fyne.CurrentApp().Preferences().SetFloatList(prefsWBLLambdaValues, m.ZValues()) + prefWBLSupportPoints.set(m.YAxis()) + prefWBLLambdaValues.set(m.ZValues()) } // updateRowEntries writes a row's current y/z to its entries without @@ -240,20 +261,13 @@ func (m *WBLEditor) CreateRenderer() fyne.WidgetRenderer { m.graph = newGraphView(m) m.presetSelect = widget.NewSelect(m.listPresets(), m.loadPreset) - - lastPreset := fyne.CurrentApp().Preferences().String(prefsLastADScannerPreset) - if lastPreset != "" { + if lastPreset := prefLastADScannerPreset.get(); lastPreset != "" { m.presetSelect.Selected = lastPreset } m.ecuSelect = widget.NewSelect([]string{"T5", "T7", "T8"}, func(s string) { - switch s { - case "T5": - m.adresolution = 255 - case "T7", "T8": - m.adresolution = 1023 - } - fyne.CurrentApp().Preferences().SetString(prefsLastADScannerECU, s) + m.adresolution = adResolutionForECU(s) + prefLastADScannerECU.set(s) for _, r := range m.rows { if r.vo != nil { r.vo.Text = fmt.Sprintf("%.2f", m.voltFromY(r.y)) @@ -261,7 +275,7 @@ func (m *WBLEditor) CreateRenderer() fyne.WidgetRenderer { } } }) - m.ecuSelect.Selected = fyne.CurrentApp().Preferences().StringWithFallback(prefsLastADScannerECU, "T7") + m.ecuSelect.Selected = prefLastADScannerECU.get() top := container.NewBorder( nil, @@ -275,11 +289,7 @@ func (m *WBLEditor) CreateRenderer() fyne.WidgetRenderer { m.deletePreset(m.presetSelect.Selected) m.refreshPresets() }), - widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { - presets := m.listPresets() - m.presetSelect.Options = presets - m.presetSelect.Refresh() - }), + widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), m.refreshPresets), ), m.presetSelect, ) @@ -290,10 +300,7 @@ func (m *WBLEditor) CreateRenderer() fyne.WidgetRenderer { bottom, nil, nil, - container.NewHSplit( - left, - m.graph, - ), + container.NewHSplit(left, m.graph), ) return widget.NewSimpleRenderer(view) } @@ -329,12 +336,6 @@ func (m *WBLEditor) listPresets() []string { return presets } -type adScannerPreset struct { - ECU string `json:"ecu"` - Y []int `json:"y"` - Z []float64 `json:"z"` -} - func (m *WBLEditor) savePreset() { name := widget.NewEntry() @@ -351,23 +352,20 @@ func (m *WBLEditor) savePreset() { debug.Log(err.Error()) return } - fname := filepath.Join(layoutPath, name.Text+".json") - var preset adScannerPreset - - preset.ECU = m.ecuSelect.Selected - preset.Y = m.YAxis() - preset.Z = m.ZValues() + preset := adScannerPreset{ + ECU: m.ecuSelect.Selected, + Y: m.YAxis(), + Z: m.ZValues(), + } - f, err := os.Create(fname) + f, err := os.Create(filepath.Join(layoutPath, name.Text+".json")) if err != nil { debug.Log(err.Error()) return } defer f.Close() - encoder := json.NewEncoder(f) - // encoder.SetIndent("", " ") - if err := encoder.Encode(preset); err != nil { + if err := json.NewEncoder(f).Encode(preset); err != nil { debug.Log(err.Error()) return } @@ -386,24 +384,9 @@ func (m *WBLEditor) loadPreset(name string) { debug.Log("loading AD preset: " + name) if preset, ok := builtInPresets[name]; ok { - m.rows = nil - for i := range preset.Y { - r := &mapRow{y: preset.Y[i], z: preset.Z[i]} - m.buildRow(r) - m.rows = append(m.rows, r) - } - if m.rowsBox != nil { - m.rowsBox.Objects = nil - for _, r := range m.rows { - m.rowsBox.Add(r.hb) - } - m.rowsBox.Refresh() - } - m.refreshGraph() - + m.setRows(preset.Y, preset.Z) m.ecuSelect.SetSelected(preset.ECU) - - fyne.CurrentApp().Preferences().SetString(prefsLastADScannerPreset, name) + prefLastADScannerPreset.set(name) m.save() return } @@ -428,21 +411,8 @@ func (m *WBLEditor) loadPreset(name string) { return } - m.rows = nil - for i := range preset.Y { - r := &mapRow{y: preset.Y[i], z: preset.Z[i]} - m.buildRow(r) - m.rows = append(m.rows, r) - } - if m.rowsBox != nil { - m.rowsBox.Objects = nil - for _, r := range m.rows { - m.rowsBox.Add(r.hb) - } - m.rowsBox.Refresh() - } - m.refreshGraph() - fyne.CurrentApp().Preferences().SetString(prefsLastADScannerPreset, name) + m.setRows(preset.Y, preset.Z) + prefLastADScannerPreset.set(name) m.save() } @@ -452,325 +422,8 @@ func (m *WBLEditor) deletePreset(name string) { fyne.LogError("Could not get layout path", err) return } - err = os.Remove(filepath.Join(layoutPath, name+".json")) - if err != nil { + if err := os.Remove(filepath.Join(layoutPath, name+".json")); err != nil { fyne.LogError("Could not delete preset file", err) return } } - -// --- graph view (native fyne primitives) ----------------------------------- - -var ( - bgColor = color.NRGBA{R: 24, G: 24, B: 28, A: 255} - gridColor = color.NRGBA{R: 60, G: 60, B: 68, A: 255} - axisColor = color.NRGBA{R: 140, G: 140, B: 150, A: 255} - lineColor = color.NRGBA{R: 80, G: 200, B: 120, A: 255} - pointColor = color.NRGBA{R: 240, G: 200, B: 60, A: 255} - pointEdge = color.NRGBA{R: 0, G: 0, B: 0, A: 255} -) - -const ( - graphMargin = 16 - pointSize = 12 - minGraph = 280 - - yMin = 0 - yMax = 1023 - zMin = 0.0 - zMax = 1.5 -) - -type graphView struct { - widget.BaseWidget - editor *WBLEditor - r *graphRenderer -} - -func newGraphView(editor *WBLEditor) *graphView { - g := &graphView{editor: editor} - g.ExtendBaseWidget(g) - return g -} - -func (g *graphView) CreateRenderer() fyne.WidgetRenderer { - r := &graphRenderer{g: g} - r.bg = canvas.NewRectangle(bgColor) - g.r = r - r.rebuild() - return r -} - -type graphRenderer struct { - g *graphView - size fyne.Size - - bg *canvas.Rectangle - gridLines []*canvas.Line - axes []*canvas.Line - dataLines []*canvas.Line - points []*draggablePoint - - // cached mapping from last layout (used by drag math) - x0, y0, x1, y1 float32 - minYv, maxYv int - minZv, maxZv float64 - - objects []fyne.CanvasObject -} - -func (r *graphRenderer) rebuild() { - if len(r.gridLines) == 0 { - for range 6 { - l := canvas.NewLine(gridColor) - l.StrokeWidth = 1 - r.gridLines = append(r.gridLines, l) - } - } - if len(r.axes) == 0 { - for range 2 { - l := canvas.NewLine(axisColor) - l.StrokeWidth = 1 - r.axes = append(r.axes, l) - } - } - - rows := r.g.editor.rows - wantLines := 0 - if n := len(rows); n > 1 { - wantLines = n - 1 - } - for len(r.dataLines) < wantLines { - l := canvas.NewLine(lineColor) - l.StrokeWidth = 2 - r.dataLines = append(r.dataLines, l) - } - r.dataLines = r.dataLines[:wantLines] - - for len(r.points) < len(rows) { - p := newDraggablePoint(r.g) - r.points = append(r.points, p) - } - r.points = r.points[:len(rows)] - for i, row := range rows { - r.points[i].row = row - } - - // dataLines/points may have changed length; force Objects() to rebuild. - r.objects = nil -} - -func (r *graphRenderer) Layout(size fyne.Size) { - r.size = size - r.bg.Resize(size) - r.layoutGraph() -} - -func (r *graphRenderer) layoutGraph() { - w := r.size.Width - h := r.size.Height - if w <= 0 || h <= 0 { - return - } - - r.x0 = float32(graphMargin) - r.y0 = float32(graphMargin) - r.x1 = w - graphMargin - r.y1 = h - graphMargin - - for i := range 3 { - gy := r.y0 + (r.y1-r.y0)*float32(i+1)/4 - gh := r.gridLines[i] - gh.Position1 = fyne.NewPos(r.x0, gy) - gh.Position2 = fyne.NewPos(r.x1, gy) - gh.Refresh() - - gx := r.x0 + (r.x1-r.x0)*float32(i+1)/4 - gv := r.gridLines[3+i] - gv.Position1 = fyne.NewPos(gx, r.y0) - gv.Position2 = fyne.NewPos(gx, r.y1) - gv.Refresh() - } - - r.axes[0].Position1 = fyne.NewPos(r.x0, r.y1) - r.axes[0].Position2 = fyne.NewPos(r.x1, r.y1) - r.axes[0].Refresh() - r.axes[1].Position1 = fyne.NewPos(r.x0, r.y0) - r.axes[1].Position2 = fyne.NewPos(r.x0, r.y1) - r.axes[1].Refresh() - - rows := r.g.editor.rows - if len(rows) == 0 { - r.minYv, r.maxYv = yMin, yMax - r.minZv, r.maxZv = zMin, zMax - return - } - - minY, maxY := rows[0].y, rows[0].y - minZ, maxZ := rows[0].z, rows[0].z - for _, row := range rows { - if row.y < minY { - minY = row.y - } - if row.y > maxY { - maxY = row.y - } - if row.z < minZ { - minZ = row.z - } - if row.z > maxZ { - maxZ = row.z - } - } - // Enforce a minimum visible span so dragging stays responsive when - // points are clustered and so the graph doesn't degenerate to a point. - const minYSpan, minZSpan = 50, 0.2 - if maxY-minY < minYSpan { - mid := (maxY + minY) / 2 - minY = clampInt(mid-minYSpan/2, yMin, yMax-minYSpan) - maxY = minY + minYSpan - } - if maxZ-minZ < minZSpan { - mid := (maxZ + minZ) / 2 - minZ = clampFloat(mid-minZSpan/2, zMin, zMax-minZSpan) - maxZ = minZ + minZSpan - } - r.minYv, r.maxYv = minY, maxY - r.minZv, r.maxZv = minZ, maxZ - - type pt struct{ x, y float32 } - pts := make([]pt, len(rows)) - for i, row := range rows { - fx := float32(row.y-minY) / float32(maxY-minY) - fy := float32((row.z - minZ) / (maxZ - minZ)) - pts[i].x = r.x0 + fx*(r.x1-r.x0) - pts[i].y = r.y1 - fy*(r.y1-r.y0) - } - - for i := 1; i < len(pts); i++ { - l := r.dataLines[i-1] - l.Position1 = fyne.NewPos(pts[i-1].x, pts[i-1].y) - l.Position2 = fyne.NewPos(pts[i].x, pts[i].y) - l.Refresh() - } - - half := float32(pointSize) / 2 - for i, p := range pts { - dp := r.points[i] - dp.Resize(fyne.NewSize(pointSize, pointSize)) - dp.Move(fyne.NewPos(p.x-half, p.y-half)) - dp.Refresh() - } -} - -func (r *graphRenderer) Refresh() { - r.bg.FillColor = bgColor - r.bg.Refresh() - r.rebuild() - r.layoutGraph() - canvas.Refresh(r.g) -} - -func (r *graphRenderer) Objects() []fyne.CanvasObject { - if r.objects == nil { - - r.objects = make([]fyne.CanvasObject, 0, 1+len(r.gridLines)+len(r.axes)+len(r.dataLines)+len(r.points)) - r.objects = append(r.objects, r.bg) - for _, l := range r.gridLines { - r.objects = append(r.objects, l) - } - for _, l := range r.axes { - r.objects = append(r.objects, l) - } - for _, l := range r.dataLines { - r.objects = append(r.objects, l) - } - for _, p := range r.points { - r.objects = append(r.objects, p) - } - } - return r.objects -} - -func (r *graphRenderer) MinSize() fyne.Size { - return fyne.NewSize(minGraph, minGraph) -} - -func (r *graphRenderer) Destroy() {} - -// --- draggable point ------------------------------------------------------- - -type draggablePoint struct { - widget.BaseWidget - g *graphView - row *mapRow - accY float64 // fractional accumulator for integer y axis -} - -func newDraggablePoint(g *graphView) *draggablePoint { - p := &draggablePoint{g: g} - p.ExtendBaseWidget(p) - return p -} - -func (p *draggablePoint) CreateRenderer() fyne.WidgetRenderer { - rect := canvas.NewRectangle(pointColor) - rect.StrokeColor = pointEdge - rect.StrokeWidth = 1 - return widget.NewSimpleRenderer(rect) -} - -func (p *draggablePoint) Cursor() desktop.Cursor { - return desktop.PointerCursor -} - -func (p *draggablePoint) Dragged(e *fyne.DragEvent) { - r := p.g.r - if r == nil || p.row == nil { - return - } - w := r.x1 - r.x0 - h := r.y1 - r.y0 - if w <= 0 || h <= 0 { - return - } - - // pixel delta → value delta using last layout's data range - dY := float64(e.Dragged.DX) / float64(w) * float64(r.maxYv-r.minYv) - dZ := -float64(e.Dragged.DY) / float64(h) * (r.maxZv - r.minZv) - - p.accY += dY - step := int(p.accY) - p.accY -= float64(step) - - p.row.y = clampInt(p.row.y+step, yMin, yMax) - p.row.z = clampFloat(p.row.z+dZ, zMin, zMax) - - p.g.editor.updateRowEntries(p.row) - p.g.Refresh() -} - -func (p *draggablePoint) DragEnd() { - p.accY = 0 - p.g.editor.save() -} - -func clampInt(v, lo, hi int) int { - if v < lo { - return lo - } - if v > hi { - return hi - } - return v -} - -func clampFloat(v, lo, hi float64) float64 { - if v < lo { - return lo - } - if v > hi { - return hi - } - return v -} diff --git a/pkg/widgets/settings/wblgraph.go b/pkg/widgets/settings/wblgraph.go new file mode 100644 index 00000000..d311822a --- /dev/null +++ b/pkg/widgets/settings/wblgraph.go @@ -0,0 +1,325 @@ +package settings + +import ( + "image/color" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/widget" +) + +// --- graph view (native fyne primitives) ----------------------------------- + +var ( + bgColor = color.NRGBA{R: 24, G: 24, B: 28, A: 255} + gridColor = color.NRGBA{R: 60, G: 60, B: 68, A: 255} + axisColor = color.NRGBA{R: 140, G: 140, B: 150, A: 255} + lineColor = color.NRGBA{R: 80, G: 200, B: 120, A: 255} + pointColor = color.NRGBA{R: 240, G: 200, B: 60, A: 255} + pointEdge = color.NRGBA{R: 0, G: 0, B: 0, A: 255} +) + +const ( + graphMargin = 16 + pointSize = 12 + minGraph = 280 + + yMin = 0 + yMax = 1023 + zMin = 0.0 + zMax = 1.5 +) + +type graphView struct { + widget.BaseWidget + editor *WBLEditor + r *graphRenderer +} + +func newGraphView(editor *WBLEditor) *graphView { + g := &graphView{editor: editor} + g.ExtendBaseWidget(g) + return g +} + +func (g *graphView) CreateRenderer() fyne.WidgetRenderer { + r := &graphRenderer{g: g} + r.bg = canvas.NewRectangle(bgColor) + g.r = r + r.rebuild() + return r +} + +type graphRenderer struct { + g *graphView + size fyne.Size + + bg *canvas.Rectangle + gridLines []*canvas.Line + axes []*canvas.Line + dataLines []*canvas.Line + points []*draggablePoint + + // cached mapping from last layout (used by drag math) + x0, y0, x1, y1 float32 + minYv, maxYv int + minZv, maxZv float64 + + objects []fyne.CanvasObject +} + +func (r *graphRenderer) rebuild() { + if len(r.gridLines) == 0 { + for range 6 { + l := canvas.NewLine(gridColor) + l.StrokeWidth = 1 + r.gridLines = append(r.gridLines, l) + } + } + if len(r.axes) == 0 { + for range 2 { + l := canvas.NewLine(axisColor) + l.StrokeWidth = 1 + r.axes = append(r.axes, l) + } + } + + rows := r.g.editor.rows + wantLines := 0 + if n := len(rows); n > 1 { + wantLines = n - 1 + } + for len(r.dataLines) < wantLines { + l := canvas.NewLine(lineColor) + l.StrokeWidth = 2 + r.dataLines = append(r.dataLines, l) + } + r.dataLines = r.dataLines[:wantLines] + + for len(r.points) < len(rows) { + p := newDraggablePoint(r.g) + r.points = append(r.points, p) + } + r.points = r.points[:len(rows)] + for i, row := range rows { + r.points[i].row = row + } + + // dataLines/points may have changed length; force Objects() to rebuild. + r.objects = nil +} + +func (r *graphRenderer) Layout(size fyne.Size) { + r.size = size + r.bg.Resize(size) + r.layoutGraph() +} + +func (r *graphRenderer) layoutGraph() { + w := r.size.Width + h := r.size.Height + if w <= 0 || h <= 0 { + return + } + + r.x0 = float32(graphMargin) + r.y0 = float32(graphMargin) + r.x1 = w - graphMargin + r.y1 = h - graphMargin + + for i := range 3 { + gy := r.y0 + (r.y1-r.y0)*float32(i+1)/4 + gh := r.gridLines[i] + gh.Position1 = fyne.NewPos(r.x0, gy) + gh.Position2 = fyne.NewPos(r.x1, gy) + gh.Refresh() + + gx := r.x0 + (r.x1-r.x0)*float32(i+1)/4 + gv := r.gridLines[3+i] + gv.Position1 = fyne.NewPos(gx, r.y0) + gv.Position2 = fyne.NewPos(gx, r.y1) + gv.Refresh() + } + + r.axes[0].Position1 = fyne.NewPos(r.x0, r.y1) + r.axes[0].Position2 = fyne.NewPos(r.x1, r.y1) + r.axes[0].Refresh() + r.axes[1].Position1 = fyne.NewPos(r.x0, r.y0) + r.axes[1].Position2 = fyne.NewPos(r.x0, r.y1) + r.axes[1].Refresh() + + rows := r.g.editor.rows + if len(rows) == 0 { + r.minYv, r.maxYv = yMin, yMax + r.minZv, r.maxZv = zMin, zMax + return + } + + minY, maxY := rows[0].y, rows[0].y + minZ, maxZ := rows[0].z, rows[0].z + for _, row := range rows { + if row.y < minY { + minY = row.y + } + if row.y > maxY { + maxY = row.y + } + if row.z < minZ { + minZ = row.z + } + if row.z > maxZ { + maxZ = row.z + } + } + // Enforce a minimum visible span so dragging stays responsive when + // points are clustered and so the graph doesn't degenerate to a point. + const minYSpan, minZSpan = 50, 0.2 + if maxY-minY < minYSpan { + mid := (maxY + minY) / 2 + minY = clampInt(mid-minYSpan/2, yMin, yMax-minYSpan) + maxY = minY + minYSpan + } + if maxZ-minZ < minZSpan { + mid := (maxZ + minZ) / 2 + minZ = clampFloat(mid-minZSpan/2, zMin, zMax-minZSpan) + maxZ = minZ + minZSpan + } + r.minYv, r.maxYv = minY, maxY + r.minZv, r.maxZv = minZ, maxZ + + type pt struct{ x, y float32 } + pts := make([]pt, len(rows)) + for i, row := range rows { + fx := float32(row.y-minY) / float32(maxY-minY) + fy := float32((row.z - minZ) / (maxZ - minZ)) + pts[i].x = r.x0 + fx*(r.x1-r.x0) + pts[i].y = r.y1 - fy*(r.y1-r.y0) + } + + for i := 1; i < len(pts); i++ { + l := r.dataLines[i-1] + l.Position1 = fyne.NewPos(pts[i-1].x, pts[i-1].y) + l.Position2 = fyne.NewPos(pts[i].x, pts[i].y) + l.Refresh() + } + + half := float32(pointSize) / 2 + for i, p := range pts { + dp := r.points[i] + dp.Resize(fyne.NewSize(pointSize, pointSize)) + dp.Move(fyne.NewPos(p.x-half, p.y-half)) + dp.Refresh() + } +} + +func (r *graphRenderer) Refresh() { + r.bg.FillColor = bgColor + r.bg.Refresh() + r.rebuild() + r.layoutGraph() + canvas.Refresh(r.g) +} + +func (r *graphRenderer) Objects() []fyne.CanvasObject { + if r.objects == nil { + r.objects = make([]fyne.CanvasObject, 0, 1+len(r.gridLines)+len(r.axes)+len(r.dataLines)+len(r.points)) + r.objects = append(r.objects, r.bg) + for _, l := range r.gridLines { + r.objects = append(r.objects, l) + } + for _, l := range r.axes { + r.objects = append(r.objects, l) + } + for _, l := range r.dataLines { + r.objects = append(r.objects, l) + } + for _, p := range r.points { + r.objects = append(r.objects, p) + } + } + return r.objects +} + +func (r *graphRenderer) MinSize() fyne.Size { + return fyne.NewSize(minGraph, minGraph) +} + +func (r *graphRenderer) Destroy() {} + +// --- draggable point ------------------------------------------------------- + +type draggablePoint struct { + widget.BaseWidget + g *graphView + row *mapRow + accY float64 // fractional accumulator for integer y axis +} + +func newDraggablePoint(g *graphView) *draggablePoint { + p := &draggablePoint{g: g} + p.ExtendBaseWidget(p) + return p +} + +func (p *draggablePoint) CreateRenderer() fyne.WidgetRenderer { + rect := canvas.NewRectangle(pointColor) + rect.StrokeColor = pointEdge + rect.StrokeWidth = 1 + return widget.NewSimpleRenderer(rect) +} + +func (p *draggablePoint) Cursor() desktop.Cursor { + return desktop.PointerCursor +} + +func (p *draggablePoint) Dragged(e *fyne.DragEvent) { + r := p.g.r + if r == nil || p.row == nil { + return + } + w := r.x1 - r.x0 + h := r.y1 - r.y0 + if w <= 0 || h <= 0 { + return + } + + // pixel delta → value delta using last layout's data range + dY := float64(e.Dragged.DX) / float64(w) * float64(r.maxYv-r.minYv) + dZ := -float64(e.Dragged.DY) / float64(h) * (r.maxZv - r.minZv) + + p.accY += dY + step := int(p.accY) + p.accY -= float64(step) + + p.row.y = clampInt(p.row.y+step, yMin, yMax) + p.row.z = clampFloat(p.row.z+dZ, zMin, zMax) + + p.g.editor.updateRowEntries(p.row) + p.g.Refresh() +} + +func (p *draggablePoint) DragEnd() { + p.accY = 0 + p.g.editor.save() +} + +func clampInt(v, lo, hi int) int { + if v < lo { + return lo + } + if v > hi { + return hi + } + return v +} + +func clampFloat(v, lo, hi float64) float64 { + if v < lo { + return lo + } + if v > hi { + return hi + } + return v +} From 55422fef2f06cec0fa4df75334231de8f7b9b3ec Mon Sep 17 00:00:00 2001 From: roffe Date: Sun, 14 Jun 2026 00:28:28 +0200 Subject: [PATCH 041/102] more settings --- go.mod | 2 +- go.sum | 4 +- pkg/widgets/newsettings/can.go | 54 ------------ pkg/widgets/newsettings/settings.go | 24 ------ pkg/widgets/numericentry/numericentry.go | 3 +- pkg/widgets/settings/settings.go | 4 +- pkg/widgets/settings/tabs.go | 100 ++++++++++++++--------- pkg/widgets/settings/wblgraph.go | 9 +- pkg/windows/mainwindow_newgauge.go | 14 +--- 9 files changed, 78 insertions(+), 136 deletions(-) delete mode 100644 pkg/widgets/newsettings/can.go delete mode 100644 pkg/widgets/newsettings/settings.go diff --git a/go.mod b/go.mod index d90b439f..ebca1ec2 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.7.5-0.20260611121725-ccd9d3f45998 + fyne.io/fyne/v2 v2.7.5-0.20260613155404-ebf0c95ebbb7 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 diff --git a/go.sum b/go.sum index af30dca5..876b9002 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -fyne.io/fyne/v2 v2.7.5-0.20260611121725-ccd9d3f45998 h1:t9rEs4rXZMTSt80TC4saVmfeSxyiUw3eMeu5kBYaF68= -fyne.io/fyne/v2 v2.7.5-0.20260611121725-ccd9d3f45998/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= +fyne.io/fyne/v2 v2.7.5-0.20260613155404-ebf0c95ebbb7 h1:TNADRWLV+A9auHNWACCtB6l/5vBKBnivf/tm9OR1+C0= +fyne.io/fyne/v2 v2.7.5-0.20260613155404-ebf0c95ebbb7/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= diff --git a/pkg/widgets/newsettings/can.go b/pkg/widgets/newsettings/can.go deleted file mode 100644 index 11c9fa87..00000000 --- a/pkg/widgets/newsettings/can.go +++ /dev/null @@ -1,54 +0,0 @@ -package settings - -import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/widget" -) - -type LoggingSettingsWidget struct { - widget.BaseWidget - - container *fyne.Container -} - -func NewTest(minSize fyne.Size) *LoggingSettingsWidget { - t := &LoggingSettingsWidget{} - t.ExtendBaseWidget(t) - t.render() - return t -} - -func (t *LoggingSettingsWidget) render() { - t.container = container.NewStack() -} - -func (t *LoggingSettingsWidget) CreateRenderer() fyne.WidgetRenderer { - return &LoggingSettingsWidgetRenderer{ - t: t, - } -} - -type LoggingSettingsWidgetRenderer struct { - t *LoggingSettingsWidget -} - -func (tr *LoggingSettingsWidgetRenderer) Layout(space fyne.Size) { - tr.t.container.Resize(space) - // do stuff -} - -func (tr *LoggingSettingsWidgetRenderer) MinSize() fyne.Size { - return tr.t.container.MinSize() -} - -func (tr *LoggingSettingsWidgetRenderer) Refresh() { - -} - -func (tr *LoggingSettingsWidgetRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{tr.t.container} -} - -func (tr *LoggingSettingsWidgetRenderer) Destroy() { -} diff --git a/pkg/widgets/newsettings/settings.go b/pkg/widgets/newsettings/settings.go deleted file mode 100644 index 8420f40a..00000000 --- a/pkg/widgets/newsettings/settings.go +++ /dev/null @@ -1,24 +0,0 @@ -package settings - -import "fyne.io/fyne/v2" - -type SettingsWidget interface { -} - -type SettingsDefinition struct { - Name string - Description string - Type string -} - -type Settings struct { - app fyne.App - CAN fyne.Widget -} - -func NewSettings(app fyne.App) *Settings { - w := &Settings{ - app: app, - } - return w -} diff --git a/pkg/widgets/numericentry/numericentry.go b/pkg/widgets/numericentry/numericentry.go index 3e78d7e7..ddb879a8 100644 --- a/pkg/widgets/numericentry/numericentry.go +++ b/pkg/widgets/numericentry/numericentry.go @@ -11,9 +11,10 @@ type Widget struct { widget.Entry } -func New() *Widget { +func New(text string) *Widget { entry := &Widget{} entry.ExtendBaseWidget(entry) + entry.SetText(text) return entry } diff --git a/pkg/widgets/settings/settings.go b/pkg/widgets/settings/settings.go index a312ef6a..a6536ca8 100644 --- a/pkg/widgets/settings/settings.go +++ b/pkg/widgets/settings/settings.go @@ -11,6 +11,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/roffe/gocan" "github.com/roffe/gocan/proto" @@ -140,10 +141,9 @@ func (sw *Widget) CreateRenderer() fyne.WidgetRenderer { tabs.Append(sw.graphicsTab()) tabs.Append(sw.canTab()) tabs.Append(sw.loggingTab()) - tabs.Append(sw.dashboardTab()) tabs.Append(sw.wblTab()) tabs.Append(sw.adScannerTab()) - tabs.Append(container.NewTabItem("txbridge", txconfigurator.NewConfigurator())) + tabs.Append(container.NewTabItemWithIcon("txbridge", theme.DownloadIcon(), txconfigurator.NewConfigurator())) sw.loadPreferences() return widget.NewSimpleRenderer(tabs) diff --git a/pkg/widgets/settings/tabs.go b/pkg/widgets/settings/tabs.go index 6154761d..1bf538a8 100644 --- a/pkg/widgets/settings/tabs.go +++ b/pkg/widgets/settings/tabs.go @@ -21,35 +21,63 @@ func leftIcon(res fyne.Resource, content fyne.CanvasObject) *fyne.Container { return container.NewBorder(nil, nil, widget.NewIcon(res), nil, content) } +// section groups related controls under a titled card, the main visual +// building block of the settings panel. +func section(title string, rows ...fyne.CanvasObject) *widget.Card { + return widget.NewCard(title, "", container.NewVBox(rows...)) +} + +// tab wraps a stack of cards in a padded page. +func tab(title string, icon fyne.Resource, cards ...fyne.CanvasObject) *container.TabItem { + page := container.NewPadded(container.NewVBox(cards...)) + return container.NewTabItemWithIcon(title, icon, page) +} + func (sw *Widget) generalTab() *container.TabItem { - return container.NewTabItem("General", container.NewVBox( - leftLabeled("WD", sw.workDir), - leftIcon(theme.InfoIcon(), sw.autoLoad), - leftIcon(theme.WarningIcon(), sw.autoSave), - leftIcon(theme.MoveUpIcon(), sw.cursorFollowCrosshair), - leftIcon(theme.SearchIcon(), container.NewVBox(sw.livePreview, sw.realtimeBars)), - leftIcon(theme.ViewFullScreenIcon(), sw.meshView), - leftLabeled("Color blind mode", sw.colorBlindMode), - )) + return tab("General", theme.SettingsIcon(), + section("ECU connection", + leftIcon(theme.InfoIcon(), sw.autoLoad), + leftIcon(theme.WarningIcon(), sw.autoSave), + ), + section("Map editor & preview", + leftIcon(theme.MoveUpIcon(), sw.cursorFollowCrosshair), + leftIcon(theme.ViewFullScreenIcon(), sw.meshView), + leftIcon(theme.SearchIcon(), container.NewVBox(sw.livePreview, sw.realtimeBars)), + ), + section("Appearance", + leftLabeled("Color blind mode", sw.colorBlindMode), + ), + section("Working directory", + sw.workDir, + ), + ) } func (sw *Widget) graphicsTab() *container.TabItem { - return container.NewTabItem("Graphics", container.NewVBox( - leftLabeled("Plot renderer", sw.plotRendererSelect), - leftLabeled("Mesh renderer", sw.meshRendererSelect), - )) + return tab("Graphics", theme.ColorPaletteIcon(), + section("Renderers", + leftLabeled("Plot renderer", sw.plotRendererSelect), + leftLabeled("Mesh renderer", sw.meshRendererSelect), + ), + section("Dashboard", + leftIcon(theme.MoveUpIcon(), sw.swapRPMandSpeed), + leftIcon(theme.InfoIcon(), sw.useMPH), + ), + ) } func (sw *Widget) canTab() *container.TabItem { fixedLabel := func(text string) fyne.CanvasObject { return xlayout.NewFixedWidth(70, widget.NewLabel(text)) } - return container.NewTabItem("CAN", container.NewVBox( - container.NewBorder(nil, nil, fixedLabel("Adapter"), sw.debugCheckbox, sw.adapterSelector), - container.NewBorder(nil, nil, fixedLabel("Port"), sw.refreshBtn, sw.portSelector), - container.NewBorder(nil, nil, fixedLabel("Info"), nil, sw.portDescription), - container.NewBorder(nil, nil, fixedLabel("Speed"), nil, sw.speedSelector), - )) + return tab("CAN", theme.ComputerIcon(), + section("Adapter", + container.NewBorder(nil, nil, fixedLabel("Adapter"), sw.debugCheckbox, sw.adapterSelector), + container.NewBorder(nil, nil, fixedLabel("Port"), sw.refreshBtn, sw.portSelector), + container.NewBorder(nil, nil, fixedLabel("Info"), nil, sw.portDescription), + container.NewBorder(nil, nil, fixedLabel("Speed"), nil, sw.speedSelector), + ), + ) } func (sw *Widget) loggingTab() *container.TabItem { @@ -70,12 +98,15 @@ func (sw *Widget) loggingTab() *container.TabItem { }), ) - return container.NewTabItem("Logging", container.NewVBox( - container.NewBorder(nil, nil, widget.NewLabel("Logging rate (Hz)"), sw.freqValue, sw.freqSlider), - widget.NewSeparator(), - leftLabeled("Log format", sw.logFormat), - container.NewBorder(nil, logFolderButtons, widget.NewLabel("Log folder"), nil, sw.logPath), - )) + return tab("Logging", theme.DocumentSaveIcon(), + section("Capture", + container.NewBorder(nil, nil, widget.NewLabel("Logging rate (Hz)"), sw.freqValue, sw.freqSlider), + leftLabeled("Log format", sw.logFormat), + ), + section("Storage", + container.NewBorder(nil, logFolderButtons, widget.NewLabel("Log folder"), nil, sw.logPath), + ), + ) } func (sw *Widget) wblTab() *container.TabItem { @@ -101,28 +132,21 @@ func (sw *Widget) wblTab() *container.TabItem { sw.wblADScannerSymbol = widget.NewSelect(adSymbols, prefWBLADScannerSymbol.set) sw.wblADScannerSymbol.SetSelected(sw.GetADScannerSymbolName()) - body := container.NewVBox( + image := container.NewHBox(layout.NewSpacer(), sw.wblImage, layout.NewSpacer()) + + settings := section("Wideband source", sw.wblSelectContainer, sw.wblADscanner, container.NewBorder(nil, nil, sw.wblPortLabel, sw.wblPortRefreshButton, sw.wblPortSelect), sw.wblADScannerSymbol, ) - image := container.NewHBox(layout.NewSpacer(), sw.wblImage, layout.NewSpacer()) - - return container.NewTabItem("WBL", container.NewBorder(image, nil, nil, nil, body)) -} - -func (sw *Widget) dashboardTab() *container.TabItem { - return container.NewTabItem("Dashboard", container.NewVBox( - widget.NewLabel("Dashboard settings"), - leftIcon(theme.InfoIcon(), sw.swapRPMandSpeed), - leftIcon(theme.InfoIcon(), sw.useMPH), - )) + page := container.NewPadded(container.NewVBox(image, settings)) + return container.NewTabItemWithIcon("WBL", theme.MediaRecordIcon(), page) } func (sw *Widget) adScannerTab() *container.TabItem { sw.wbleditor = NewWBLEditor(sw.GetWBLSupportPoints(), sw.GetWBLLambdaValues()) sw.wbleditor.Hide() - return container.NewTabItem("AD Scanner", sw.wbleditor) + return container.NewTabItemWithIcon("AD Scanner", theme.SearchIcon(), sw.wbleditor) } diff --git a/pkg/widgets/settings/wblgraph.go b/pkg/widgets/settings/wblgraph.go index d311822a..b6f72f2b 100644 --- a/pkg/widgets/settings/wblgraph.go +++ b/pkg/widgets/settings/wblgraph.go @@ -21,9 +21,10 @@ var ( ) const ( - graphMargin = 16 - pointSize = 12 - minGraph = 280 + graphMargin = 16 + pointSize = 12 + minGraphWidth = 360 + minGraphHeight = 280 yMin = 0 yMax = 1023 @@ -242,7 +243,7 @@ func (r *graphRenderer) Objects() []fyne.CanvasObject { } func (r *graphRenderer) MinSize() fyne.Size { - return fyne.NewSize(minGraph, minGraph) + return fyne.NewSize(minGraphWidth, minGraphHeight) } func (r *graphRenderer) Destroy() {} diff --git a/pkg/windows/mainwindow_newgauge.go b/pkg/windows/mainwindow_newgauge.go index c8f21fd3..19183ffe 100644 --- a/pkg/windows/mainwindow_newgauge.go +++ b/pkg/windows/mainwindow_newgauge.go @@ -58,17 +58,11 @@ func NewGaugeCreator(mw *MainWindow) *GaugeCreator { g.entries.symbolNameSecondary = widget.NewSelect(symbols, func(s string) {}) g.entries.symbolNameSecondary.Disable() - g.entries.min = numericentry.New() - g.entries.min.SetText("0") - g.entries.max = numericentry.New() - g.entries.max.SetText("100") - - g.entries.center = numericentry.New() - g.entries.center.SetText("50") + g.entries.min = numericentry.New("0") + g.entries.max = numericentry.New("100") + g.entries.center = numericentry.New("50") g.entries.center.Disable() - - g.entries.steps = numericentry.New() - g.entries.steps.SetText("10") + g.entries.steps = numericentry.New("10") g.entries.typ = widget.NewSelect([]string{"Dial", "DualDial", "VBar", "HBar", "CBar"}, func(s string) { switch s { From 8762b8c60be3bed146a220684df70375726739d8 Mon Sep 17 00:00:00 2001 From: roffe Date: Sun, 14 Jun 2026 01:49:27 +0200 Subject: [PATCH 042/102] update secret --- pkg/widgets/secrettext/secrettext.go | 45 ++++-- pkg/widgets/tunnel/logo.png | Bin 0 -> 32575 bytes pkg/widgets/tunnel/tunnel.go | 104 ++++++++++++ pkg/widgets/tunnel/tunnel_crawl.go | 94 +++++++++++ pkg/widgets/tunnel/tunnel_shader.go | 233 +++++++++++++++++++++++++++ pkg/widgets/tunnel/tunnel_test.go | 91 +++++++++++ 6 files changed, 554 insertions(+), 13 deletions(-) create mode 100644 pkg/widgets/tunnel/logo.png create mode 100644 pkg/widgets/tunnel/tunnel.go create mode 100644 pkg/widgets/tunnel/tunnel_crawl.go create mode 100644 pkg/widgets/tunnel/tunnel_shader.go create mode 100644 pkg/widgets/tunnel/tunnel_test.go diff --git a/pkg/widgets/secrettext/secrettext.go b/pkg/widgets/secrettext/secrettext.go index 78e13041..40ef47b9 100644 --- a/pkg/widgets/secrettext/secrettext.go +++ b/pkg/widgets/secrettext/secrettext.go @@ -3,17 +3,15 @@ package secrettext import ( "bytes" "sync" - "time" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" "github.com/hajimehoshi/go-mp3" "github.com/roffe/txlogger/pkg/assets" "github.com/roffe/txlogger/pkg/sound" + "github.com/roffe/txlogger/pkg/widgets/tunnel" ) var _ fyne.Tappable = (*SecretText)(nil) @@ -37,10 +35,12 @@ func (s *SecretText) Tapped(*fyne.PointEvent) { // log.Println("tapped", s.tappedTimes) if s.tappedTimes >= 10 { - t := fyne.NewStaticResource("taz.png", assets.Taz) - cv := canvas.NewImageFromResource(t) - cv.ScaleMode = canvas.ImageScaleFastest - cv.SetMinSize(fyne.NewSize(0, 0)) + /* + t := fyne.NewStaticResource("taz.png", assets.Taz) + cv := canvas.NewImageFromResource(t) + cv.ScaleMode = canvas.ImageScaleFastest + cv.SetMinSize(fyne.NewSize(0, 0)) + */ fileBytesReader := bytes.NewReader(assets.Korvring) @@ -59,17 +59,36 @@ func (s *SecretText) Tapped(*fyne.PointEvent) { f() } - cont := container.NewStack(cv) - d := dialog.NewCustom("You found the secret", "Leif", cont, fyne.CurrentApp().Driver().AllWindows()[0]) + t := tunnel.New() + t.SetCredits([]string{ + "SAAB", + "MattiasC", + "Dilemma", + "J.K Nilsson", + "Manick", + "Artursson", + "Schottis", + "Chriva", + "Myrtilos", + "Mackan", + "Kalej", + "Bojer", + "TrionicTuning", + "o2o Crew", + }) + + d := dialog.NewCustom("You found the secret", "Leif", t, fyne.CurrentApp().Driver().AllWindows()[0]) d.SetOnClosed(func() { player.Pause() }) d.Show() - an := canvas.NewSizeAnimation(fyne.NewSize(0, 0), fyne.NewSize(370, 386), time.Second, func(size fyne.Size) { - cv.Resize(size) - }) + /* + an := canvas.NewSizeAnimation(fyne.NewSize(0, 0), fyne.NewSize(370, 386), time.Second, func(size fyne.Size) { + cv.Resize(size) + }) - an.Start() + an.Start() + */ } } diff --git a/pkg/widgets/tunnel/logo.png b/pkg/widgets/tunnel/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..31f60b004c533d838c6723647bf86d06f97d6847 GIT binary patch literal 32575 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 literal 0 HcmV?d00001 diff --git a/pkg/widgets/tunnel/tunnel.go b/pkg/widgets/tunnel/tunnel.go new file mode 100644 index 00000000..62ba9a00 --- /dev/null +++ b/pkg/widgets/tunnel/tunnel.go @@ -0,0 +1,104 @@ +// Package tunnel renders an endless psychedelic 3D tunnel ride entirely on the +// GPU with a single canvas.Shader, modelled on the meshgrid shader backend. +// The viewer rides a retrowave wiremesh vortex like a rollercoaster; all motion +// comes from the "time" uniform that canvas.NewShaderAnimation advances, so the +// CPU does no per-frame work. +package tunnel + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" +) + +var _ fyne.Widget = (*Tunnel)(nil) + +// defaultSize is the requested 500x500 viewport; the widget keeps it as its +// minimum and fills whatever a container hands it, staying circular via the +// short-axis normalisation in the shader. +const defaultSize = 700 + +type Tunnel struct { + widget.BaseWidget + + shader *canvas.Shader + anim *fyne.Animation + + running bool +} + +// New builds a tunnel widget. The animation starts automatically once the +// widget is shown (CreateRenderer) and stops when it is removed (Destroy). +func New() *Tunnel { + t := &Tunnel{} + t.ExtendBaseWidget(t) + t.initShader() + t.anim = canvas.NewShaderAnimation(t.shader) + return t +} + +// Start begins (or resumes) the ride. Safe to call repeatedly. +func (t *Tunnel) Start() { + if t.running || t.anim == nil { + return + } + t.running = true + t.anim.Start() +} + +// Stop freezes the tunnel on its current frame. +func (t *Tunnel) Stop() { + if !t.running || t.anim == nil { + return + } + t.running = false + t.anim.Stop() +} + +// SetSpeed scales how fast the viewer rides into the tunnel. +func (t *Tunnel) SetSpeed(speed float32) { + if t.shader == nil { + return + } + t.shader.Uniforms["speed"] = speed + t.shader.Refresh() +} + +// SetLogoScale sets the bouncing logo's half-size in short-axis units (the +// short axis spans -1..1), e.g. 0.30 makes the logo ~30% of half the viewport. +func (t *Tunnel) SetLogoScale(scale float32) { + if t.shader == nil { + return + } + t.shader.Uniforms["logo_scale"] = scale + t.shader.Refresh() +} + +func (t *Tunnel) CreateRenderer() fyne.WidgetRenderer { + t.Start() + return &tunnelRenderer{t: t} +} + +type tunnelRenderer struct { + t *Tunnel +} + +func (r *tunnelRenderer) Layout(size fyne.Size) { + r.t.shader.Resize(size) +} + +func (r *tunnelRenderer) MinSize() fyne.Size { + return fyne.NewSize(defaultSize, defaultSize) +} + +func (r *tunnelRenderer) Refresh() { + r.t.shader.Refresh() +} + +func (r *tunnelRenderer) Destroy() { + r.t.Stop() +} + +func (r *tunnelRenderer) Objects() []fyne.CanvasObject { + return []fyne.CanvasObject{r.t.shader} +} diff --git a/pkg/widgets/tunnel/tunnel_crawl.go b/pkg/widgets/tunnel/tunnel_crawl.go new file mode 100644 index 00000000..0035fdde --- /dev/null +++ b/pkg/widgets/tunnel/tunnel_crawl.go @@ -0,0 +1,94 @@ +package tunnel + +import ( + "image" + "image/color" + "log" + + "fyne.io/fyne/v2/theme" + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + "golang.org/x/image/math/fixed" +) + +// Credits-strip texture layout. Each line gets a fixed-height band; the shader +// samples the strip with a perspective crawl and loops it with fract. +const ( + crawlTexWidth = 640 + crawlLineH = 56 + crawlFontSize = 40 +) + +// SetCredits sets the lines shown as a Star Wars-style crawl that rises from +// the bottom into the tunnel centre and loops forever. An empty string renders +// as a blank line, useful for spacing and section breaks - including a blank +// first/last line, which hides the loop seam. Passing an empty slice removes +// the crawl. +func (t *Tunnel) SetCredits(lines []string) { + if t.shader == nil { + return + } + img := renderCrawl(lines) + if img == nil { + delete(t.shader.Textures, "crawl_tex") + t.shader.Uniforms["crawl_lines"] = 0 + t.shader.Refresh() + return + } + if t.shader.Textures == nil { + t.shader.Textures = make(map[string]image.Image, 2) + } + t.shader.Textures["crawl_tex"] = img + t.shader.Uniforms["crawl_lines"] = float32(len(lines)) + t.shader.Refresh() +} + +// renderCrawl rasterises the credit lines into a tall, centred, transparent +// strip: band i holds line i, with band 0 (image row 0, texture t = 0) the +// first line. The painter uploads image rows verbatim, so the first line sits +// at the far end of the crawl. Returns nil for an empty list. +func renderCrawl(lines []string) *image.RGBA { + if len(lines) == 0 { + return nil + } + face, err := crawlFace() + if err != nil { + log.Printf("tunnel: crawl font: %v", err) + return nil + } + defer face.Close() + + img := image.NewRGBA(image.Rect(0, 0, crawlTexWidth, len(lines)*crawlLineH)) + + m := face.Metrics() + textH := (m.Ascent + m.Descent).Ceil() + topPad := (crawlLineH - textH) / 2 + + d := &font.Drawer{Dst: img, Src: image.NewUniform(color.White), Face: face} + for i, line := range lines { + if line == "" { + continue // blank spacer line + } + adv := font.MeasureString(face, line) + d.Dot = fixed.Point26_6{ + X: (fixed.I(crawlTexWidth) - adv) / 2, // centre horizontally + Y: fixed.I(i*crawlLineH+topPad) + m.Ascent, + } + d.DrawString(line) + } + return img +} + +// crawlFace builds a font face from the app's bundled text font so the crawl +// matches the rest of the UI. +func crawlFace() (font.Face, error) { + ft, err := opentype.Parse(theme.DefaultTextFont().Content()) + if err != nil { + return nil, err + } + return opentype.NewFace(ft, &opentype.FaceOptions{ + Size: crawlFontSize, + DPI: 72, + Hinting: font.HintingFull, + }) +} diff --git a/pkg/widgets/tunnel/tunnel_shader.go b/pkg/widgets/tunnel/tunnel_shader.go new file mode 100644 index 00000000..a0486477 --- /dev/null +++ b/pkg/widgets/tunnel/tunnel_shader.go @@ -0,0 +1,233 @@ +package tunnel + +import ( + "bytes" + _ "embed" + "fmt" + "image" + "image/draw" + _ "image/png" + "log" + "sync/atomic" + + "fyne.io/fyne/v2/canvas" +) + +//go:embed logo.png +var logoPNG []byte + +// The whole effect is a single procedural fragment shader: no geometry, no +// textures, no per-frame CPU work beyond the "time" uniform that +// canvas.NewShaderAnimation advances. Each pixel reconstructs an infinite +// tube in polar coordinates - angle around the tube, 1/radius into it - and +// draws a glowing retrowave wireframe of rings and ribs that streams toward +// the viewer. A swaying vanishing point plus a depth-dependent twist give the +// rollercoaster ride; hue cycling over depth and time keeps it psychedelic. + +// tunnelSeq makes each widget's Shader.Name unique so the painter caches one +// compiled program per live widget instead of evicting a shared one. +var tunnelSeq atomic.Int64 + +const tunnelPreludeGL = "#version 110\n" + +const tunnelPreludeES = `#version 100 +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +` + +const tunnelBody = ` +#define PI 3.14159265359 + +uniform vec2 frame_size; // output frame size, device px +uniform vec4 rect_coords; // this object's bounds (x1, x2, y1, y2), device px +uniform float time; // elapsed animation seconds + +// optional tuning knobs (default sensibly if left at 0 by the Go side) +uniform float speed; // forward ride speed +uniform float ring_freq; // rings per depth unit +uniform float rib_freq; // longitudinal ribs around the tube +uniform float logo_scale; // logo billboard half-size, short-axis units + +uniform float crawl_lines; // number of credit lines (0 = no crawl) +uniform float crawl_persp; // floor depth at the bottom edge +uniform float crawl_width; // text block half-width in world units +uniform float crawl_zlines; // credit lines spanned per unit of depth +uniform float crawl_speed; // scroll rate, lines per second + +uniform sampler2D logo_tex; // txlogger logo, premultiplied RGBA +uniform sampler2D crawl_tex; // credits strip, line 0 at the top (t = 0) + +vec3 hsv2rgb(vec3 c) { + vec3 p = abs(fract(c.xxx + vec3(0.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0); + return c.z * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), c.y); +} + +// glowing line at the nearest multiple of 1.0 of x, half width hw +float grid_line(float x, float hw) { + float f = fract(x); + float d = min(f, 1.0 - f); + return smoothstep(hw, 0.0, d); +} + +// triangle wave in [-1, 1] with period 1, for DVD-screensaver bouncing +float tri(float x) { + return abs(fract(x) - 0.5) * 4.0 - 1.0; +} + +void main() { + vec2 size = vec2(rect_coords.y - rect_coords.x, rect_coords.w - rect_coords.z); + vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; + + // the painter expands the quad slightly for edge softness; stay inside + if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > size.x || p_dev.y > size.y) { + discard; + } + + float spd = speed > 0.0 ? speed : 1.1; + float rings = ring_freq > 0.0 ? ring_freq : 5.0; + float ribs = rib_freq > 0.0 ? rib_freq : 16.0; + + // centered, aspect-correct coordinates, roughly [-1, 1] on the short axis. + // sc stays pristine for the logo billboard; uv is warped into the tunnel. + vec2 sc = (p_dev - 0.5 * size) / min(size.x, size.y) * 2.0; + vec2 uv = sc; + + // rollercoaster: drift the vanishing point along a couple of detuned + // sinusoids so the track appears to bank and weave + uv -= vec2(sin(time * 0.7) * 0.34 + sin(time * 1.31) * 0.11, + cos(time * 0.53) * 0.30 + cos(time * 1.79) * 0.09); + + // bank the whole frame into the curves + float bank = sin(time * 0.61) * 0.55; + float cb = cos(bank), sb = sin(bank); + uv = mat2(cb, -sb, sb, cb) * uv; + + float r = length(uv); + float a = atan(uv.y, uv.x); + + // tube coordinates: depth grows toward the centre, ride forward over time, + // and add a spiral twist with depth for the psytrance vortex + float depth = 1.0 / max(r, 0.0008); + float v = depth + time * spd; + float u = a / PI + depth * 0.18 + time * 0.04; + + // wireframe: cyan rings across the tube, magenta ribs along it. The line + // width shrinks with depth so far geometry naturally thins toward the + // vanishing point. + float lw = clamp(0.02 + r * 0.10, 0.02, 0.22); + float ring = grid_line(v * rings, lw); + float rib = grid_line(u * ribs, lw * 0.9); + + float hue = fract(0.58 + v * 0.025 + u * 0.05 + time * 0.05); + vec3 ringCol = hsv2rgb(vec3(hue, 0.85, 1.0)); + vec3 ribCol = hsv2rgb(vec3(fract(hue + 0.45), 0.9, 1.0)); + + vec3 col = ring * ringCol * 1.3 + rib * ribCol * 1.0; + + // fade the grid out right at the centre so it doesn't smear into a blob + col *= smoothstep(0.0, 0.10, r); + + // pulsing neon "light at the end of the tunnel" + float pulse = 0.55 + 0.45 * sin(time * 2.3); + col += vec3(0.75, 0.30, 1.0) * smoothstep(0.45, 0.0, r) * pulse; + + // dark retrowave haze between the lines so the tube reads as a surface + vec3 haze = mix(vec3(0.03, 0.0, 0.07), vec3(0.12, 0.01, 0.20), + 0.5 + 0.5 * sin(v * 0.5 + time * 0.5)); + col += haze * (1.0 - clamp(ring + rib, 0.0, 1.0)) * smoothstep(0.0, 0.12, r); + + // vignette toward the outer edge + col *= 1.0 - 0.40 * smoothstep(0.7, 1.5, r); + + // --- Star Wars credits crawl: rises from the bottom into the tunnel centre --- + // A floor plane recedes from the bottom edge (sc.y ~ 1, near) toward the + // tunnel mouth (sc.y -> 0, far). z = persp/sc.y is the depth along it; the + // text narrows with depth (s uses sc.x*z) and scrolls in line units that + // wrap with fract, so a blank first/last line hides the loop seam. + if (crawl_lines > 0.5 && sc.y > 0.045) { + float cw = crawl_width > 0.0 ? crawl_width : 0.9; + float cz = crawl_zlines > 0.0 ? crawl_zlines : 3.0; + float cp = crawl_persp > 0.0 ? crawl_persp : 0.45; + float cs = crawl_speed > 0.0 ? crawl_speed : 0.8; + + float z = cp / sc.y; + float s = sc.x * z / cw + 0.5; + if (s >= 0.0 && s <= 1.0) { + float t = fract((time * cs - z * cz) / crawl_lines); + float a = texture2D(crawl_tex, vec2(s, t)).a; + a *= smoothstep(0.045, 0.18, sc.y) * smoothstep(1.5, 1.0, sc.y); + vec3 crawlCol = vec3(1.0, 0.85, 0.2); // classic crawl yellow + col = mix(col, crawlCol, a); + col += crawlCol * a * 0.25; // soft neon glow + } + } + + // --- spinning, DVD-bouncing txlogger logo billboard, composited on top --- + // DVD-screensaver bounce: detuned triangle waves keep it inside the frame + vec2 lc = vec2(tri(time * 0.27) * 0.58, tri(time * 0.21) * 0.52); + + // grow and shrink as it rides the tunnel toward and away from the viewer + float depthBob = 0.70 + 0.35 * sin(time * 0.8); + float lscale = (logo_scale > 0.0 ? logo_scale : 0.30) * depthBob; + + // rotation couples to the bounce path: the angle tracks position, so its + // spin rate and direction follow the travel heading and flip at every wall + // hit (lc.x reverses on the side walls, lc.y on the top/bottom). + float ang = lc.x * 10.0 + lc.y * 7.0; + float ca = cos(ang), sa = sin(ang); + vec2 luv = mat2(ca, -sa, sa, ca) * ((sc - lc) / lscale); + if (abs(luv.x) <= 1.0 && abs(luv.y) <= 1.0) { + vec4 logo = texture2D(logo_tex, luv * 0.5 + 0.5); + // source-over with premultiplied alpha, then a pulsing neon rim glow + col = col * (1.0 - logo.a) + logo.rgb; + col += logo.a * (0.4 + 0.4 * sin(time * 2.0)) * 0.25 * vec3(0.6, 0.25, 1.0); + } + + gl_FragColor = vec4(col, 1.0); +} +` + +func (t *Tunnel) initShader() { + t.shader = canvas.NewShader( + fmt.Sprintf("tunnel-%d", tunnelSeq.Add(1)), + []byte(tunnelPreludeGL+tunnelBody), + []byte(tunnelPreludeES+tunnelBody), + ) + t.shader.Uniforms = map[string]float32{ + "time": 0, + "speed": 1.1, + "ring_freq": 5.0, + "rib_freq": 16.0, + "logo_scale": 0.30, + "crawl_lines": 0, // no credits until SetCredits is called + "crawl_persp": 0.45, + "crawl_width": 0.9, + "crawl_zlines": 3.0, + "crawl_speed": 0.8, + } + if logo := loadLogo(); logo != nil { + t.shader.Textures = map[string]image.Image{"logo_tex": logo} + } +} + +// loadLogo decodes the embedded txlogger logo into a premultiplied *image.RGBA, +// the form the GL painter uploads verbatim (image row 0 -> texture t=0, so the +// logo samples upright). Returns nil if decoding fails, in which case the +// shader simply samples an unbound texture and draws no logo. +func loadLogo() image.Image { + img, _, err := image.Decode(bytes.NewReader(logoPNG)) + if err != nil { + log.Printf("tunnel: decoding logo.png: %v", err) + return nil + } + if rgba, ok := img.(*image.RGBA); ok { + return rgba + } + b := img.Bounds() + rgba := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy())) + draw.Draw(rgba, rgba.Bounds(), img, b.Min, draw.Src) + return rgba +} diff --git a/pkg/widgets/tunnel/tunnel_test.go b/pkg/widgets/tunnel/tunnel_test.go new file mode 100644 index 00000000..69777120 --- /dev/null +++ b/pkg/widgets/tunnel/tunnel_test.go @@ -0,0 +1,91 @@ +package tunnel + +import ( + "image" + "os" + "os/exec" + "path/filepath" + "testing" + + "fyne.io/fyne/v2/test" +) + +// New must produce a widget whose shader carries the animation-driven "time" +// uniform and tuning knobs, plus a renderer that exposes the shader. +func TestNewTunnel(t *testing.T) { + test.NewTempApp(t) // CreateRenderer starts the animation, which needs a driver + tn := New() + if tn.shader == nil { + t.Fatal("shader not initialised") + } + if _, ok := tn.shader.Uniforms["time"]; !ok { + t.Fatal("time uniform missing") + } + if tn.anim == nil { + t.Fatal("animation not created") + } + if _, ok := tn.shader.Textures["logo_tex"]; !ok { + t.Fatal("embedded logo texture not loaded") + } + + r := tn.CreateRenderer() + objs := r.Objects() + if len(objs) != 1 || objs[0] != tn.shader { + t.Fatalf("renderer objects = %v, want [shader]", objs) + } + if min := r.MinSize(); min.Width != defaultSize || min.Height != defaultSize { + t.Fatalf("MinSize = %v, want %dx%d", min, defaultSize, defaultSize) + } + r.Destroy() +} + +// SetCredits must rasterise the lines into a crawl texture sized one band per +// line (blank lines included) and record the line count for the shader. +func TestSetCredits(t *testing.T) { + test.NewTempApp(t) + tn := New() + + lines := []string{"", "txlogger thanks", "", "Alice", "Bob", ""} + tn.SetCredits(lines) + + img, ok := tn.shader.Textures["crawl_tex"].(*image.RGBA) + if !ok { + t.Fatal("crawl texture not created") + } + if w := img.Bounds().Dy(); w != len(lines)*crawlLineH { + t.Fatalf("crawl height = %d, want %d", w, len(lines)*crawlLineH) + } + if got := tn.shader.Uniforms["crawl_lines"]; got != float32(len(lines)) { + t.Fatalf("crawl_lines = %v, want %d", got, len(lines)) + } + + // An empty slice removes the crawl again. + tn.SetCredits(nil) + if _, ok := tn.shader.Textures["crawl_tex"]; ok { + t.Fatal("crawl texture should be removed for empty credits") + } + if got := tn.shader.Uniforms["crawl_lines"]; got != 0 { + t.Fatalf("crawl_lines = %v, want 0", got) + } +} + +// Both shader variants must compile; glslangValidator checks them against the +// GLSL 1.10 (desktop) and GLSL ES 1.00 specs. +func TestTunnelShaderSourcesCompile(t *testing.T) { + validator, err := exec.LookPath("glslangValidator") + if err != nil { + t.Skip("glslangValidator not installed") + } + for name, src := range map[string]string{ + "desktop.frag": tunnelPreludeGL + tunnelBody, + "es.frag": tunnelPreludeES + tunnelBody, + } { + p := filepath.Join(t.TempDir(), name) + if err := os.WriteFile(p, []byte(src), 0o644); err != nil { + t.Fatal(err) + } + if out, err := exec.Command(validator, p).CombinedOutput(); err != nil { + t.Fatalf("%s: %v\n%s", name, err, out) + } + } +} From 7c82074bacd2e84f0bcd15a3094f159ba92ae8a9 Mon Sep 17 00:00:00 2001 From: roffe Date: Sun, 14 Jun 2026 17:09:57 +0200 Subject: [PATCH 043/102] fix innovate serial logging --- pkg/wbl/innovate/innovate.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/wbl/innovate/innovate.go b/pkg/wbl/innovate/innovate.go index 8d336ad8..0918cfb0 100644 --- a/pkg/wbl/innovate/innovate.go +++ b/pkg/wbl/innovate/innovate.go @@ -96,13 +96,16 @@ func (c *ISP2Client) Start(ctx context.Context) error { // openPort opens the serial port and stores it on the client. func (c *ISP2Client) openPort() error { mode := &serial.Mode{ - BaudRate: 9600, + BaudRate: 19200, } sp, err := serial.Open(c.port, mode) if err != nil { - return err + return fmt.Errorf("failed to open serial port: %w", err) + } + if err := sp.SetReadTimeout(5 * time.Millisecond); err != nil { + sp.Close() + return fmt.Errorf("failed to set read timeout: %w", err) } - sp.SetReadTimeout(20 * time.Millisecond) c.mu.Lock() c.sp = sp @@ -126,7 +129,7 @@ func (c *ISP2Client) closePort() { c.mu.Unlock() if sp != nil { if err := sp.Close(); err != nil { - c.log("isp2: " + err.Error()) + c.log("isp2: failed to close serial port: " + err.Error()) } } } From e4a44f7f324794920d4afef5c0fa00665e09318e Mon Sep 17 00:00:00 2001 From: roffe Date: Sun, 14 Jun 2026 18:14:59 +0200 Subject: [PATCH 044/102] save tunnel shader --- pkg/widgets/tunnel/tunnel_shader.go | 49 ++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/pkg/widgets/tunnel/tunnel_shader.go b/pkg/widgets/tunnel/tunnel_shader.go index a0486477..0a8818a4 100644 --- a/pkg/widgets/tunnel/tunnel_shader.go +++ b/pkg/widgets/tunnel/tunnel_shader.go @@ -96,9 +96,13 @@ void main() { vec2 uv = sc; // rollercoaster: drift the vanishing point along a couple of detuned - // sinusoids so the track appears to bank and weave - uv -= vec2(sin(time * 0.7) * 0.34 + sin(time * 1.31) * 0.11, - cos(time * 0.53) * 0.30 + cos(time * 1.79) * 0.09); + // sinusoids so the track appears to bank and weave. vp is the hole's + // position in screen (sc) space; the bank below rotates around it, so the + // tunnel mouth always sits at sc == vp. The crawl reuses vp as the far + // point its text converges into. + vec2 vp = vec2(sin(time * 0.7) * 0.34 + sin(time * 1.31) * 0.11, + cos(time * 0.53) * 0.30 + cos(time * 1.79) * 0.09); + uv -= vp; // bank the whole frame into the curves float bank = sin(time * 0.61) * 0.55; @@ -142,23 +146,40 @@ void main() { // vignette toward the outer edge col *= 1.0 - 0.40 * smoothstep(0.7, 1.5, r); - // --- Star Wars credits crawl: rises from the bottom into the tunnel centre --- - // A floor plane recedes from the bottom edge (sc.y ~ 1, near) toward the - // tunnel mouth (sc.y -> 0, far). z = persp/sc.y is the depth along it; the - // text narrows with depth (s uses sc.x*z) and scrolls in line units that - // wrap with fract, so a blank first/last line hides the loop seam. - if (crawl_lines > 0.5 && sc.y > 0.045) { + // --- Star Wars credits crawl: flows up the floor into the moving hole --- + // A floor plane recedes from the bottom edge (near) toward the tunnel + // mouth (far), but its far end now converges on the drifting hole vp + // instead of the screen centre. yh is depth below the moving horizon + // (the floor lives where sc.y > vp.y); z = persp/yh is depth along it. + // The lane centre slides from x = 0 at the bottom to vp.x at the horizon, + // so the text stays anchored where it emerges yet leans and sways toward + // the wandering hole. s narrows the text with depth, and lf scrolls in + // line units that wrap with fract (a blank first/last line hides the loop + // seam) while staying < 0 on the first pass so the strip rises in from the + // bottom. + float yh = sc.y - vp.y; + if (crawl_lines > 0.5 && yh > 0.045) { float cw = crawl_width > 0.0 ? crawl_width : 0.9; float cz = crawl_zlines > 0.0 ? crawl_zlines : 3.0; float cp = crawl_persp > 0.0 ? crawl_persp : 0.45; float cs = crawl_speed > 0.0 ? crawl_speed : 0.8; - float z = cp / sc.y; - float s = sc.x * z / cw + 0.5; - if (s >= 0.0 && s <= 1.0) { - float t = fract((time * cs - z * cz) / crawl_lines); + float z = cp / yh; + // g runs 0 at the bottom anchor to 1 at the horizon. The lane centre + // first leans on a straight line from x = 0 to the hole (vp.x)... + float g = clamp((1.0 - sc.y) / (1.0 - vp.y), 0.0, 1.0); + float centerX = vp.x * g; + // ...then bows sideways so the path curves into the mouth along with + // the tunnel. g*(1-g) pins both ends (bottom anchor and hole) while the + // shared bank phase, plus a depth twist, make deeper sections lead the + // curve the same way the rings bank and spiral. + centerX += 0.9 * g * (1.0 - g) * sin(bank * 2.0 - g * 2.5); + float s = (sc.x - centerX) * z / cw + 0.5; + float lf = (time * cs - z * cz) / crawl_lines; + if (s >= 0.0 && s <= 1.0 && lf >= 0.0) { + float t = fract(lf); float a = texture2D(crawl_tex, vec2(s, t)).a; - a *= smoothstep(0.045, 0.18, sc.y) * smoothstep(1.5, 1.0, sc.y); + a *= smoothstep(0.045, 0.18, yh) * smoothstep(1.5, 1.0, sc.y); vec3 crawlCol = vec3(1.0, 0.85, 0.2); // classic crawl yellow col = mix(col, crawlCol, a); col += crawlCol * a * 0.25; // soft neon glow From 3f2c791635da3fb1b4ebc066d82c852734edaa72 Mon Sep 17 00:00:00 2001 From: roffe Date: Mon, 15 Jun 2026 12:05:31 +0200 Subject: [PATCH 045/102] tidy --- console_linux.go | 3 +-- main.go | 8 -------- main_windows.go | 5 ++--- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/console_linux.go b/console_linux.go index dad6f1a4..7c7d7d9e 100644 --- a/console_linux.go +++ b/console_linux.go @@ -1,4 +1,3 @@ package main -func InitConsole() { -} +func InitConsole() {} diff --git a/main.go b/main.go index 864c732b..949ede54 100644 --- a/main.go +++ b/main.go @@ -121,14 +121,6 @@ func main() { mw.ShowAndRun() } -/* -func startpprof() { - go func() { - debug.Log(http.ListenAndServe("localhost:6060", nil)) - }() -} -*/ - func killProcess(p *os.Process) { if p != nil { p.Kill() diff --git a/main_windows.go b/main_windows.go index fbb883a9..eec22213 100644 --- a/main_windows.go +++ b/main_windows.go @@ -1,5 +1,4 @@ package main -func runFileChild() { - // This is a no-op on Windows and will never be called because the FP environment variable is only set in the Linux build, but we need it to exist to satisfy the linker. -} +// This is a no-op on Windows and will never be called because the FP environment variable is only set in the Linux build, but we need it to exist to satisfy the linker. +func runFileChild() {} From 96d49a279ac86dd990af5a51c165eeb7acb6b0c3 Mon Sep 17 00:00:00 2001 From: roffe Date: Mon, 15 Jun 2026 12:42:28 +0200 Subject: [PATCH 046/102] improved selection and copy paste --- pkg/assets/WHATSNEW.md | 2 + pkg/widgets/mapviewer/mapviewer.go | 155 +++++++---------- pkg/widgets/mapviewer/mapviewer_keyhandler.go | 112 ++++++++---- pkg/widgets/mapviewer/mapviewer_mouse.go | 162 +++++++----------- 4 files changed, 200 insertions(+), 231 deletions(-) diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index 259030a9..85bb4f81 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -18,6 +18,8 @@ - Rewrote logplayer plotter to use about 50% less CPU on zoomed out views - Big refactor of the log writing logic to be simpler to maintain and be more performant - Dial and Dual Dial now uses shaders to draw the faces, dials and pips +- Improved cell selection in mapviewer +- Improved copy paste in mapviewer, added paste here function # 2.1.9 - Updated default T7 preset to include MAF.m_AirFromp_AirInlet diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index b92b9494..c5336782 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -55,14 +55,15 @@ type MapViewer struct { content fyne.CanvasObject - innerView *fyne.Container - valueRects *fyne.Container - valueTexts *fyne.Container + innerView *fyne.Container + valueRects *fyne.Container + valueTexts *fyne.Container + selectionOverlay *fyne.Container - selectionRect *canvas.Rectangle - crosshair *canvas.Rectangle + crosshair *canvas.Rectangle - zDataRects []*canvas.Rectangle + zDataRects []*canvas.Rectangle + selectionRects []*canvas.Rectangle selectedX, SelectedY int @@ -70,10 +71,11 @@ type MapViewer struct { graph *graph2d.Graph // Mouse - mousePos fyne.Position - selecting bool - lastModifier fyne.KeyModifier - selectedCells []int + mousePos fyne.Position + selecting bool + dragCornerX, dragCornerY int + lastModifier fyne.KeyModifier + selectedCells []int // Keyboard inputBuffer strings.Builder @@ -92,13 +94,12 @@ type MapViewer struct { func New(config *Config) (*MapViewer, error) { mv := &MapViewer{ - cfg: config, - crosshair: NewCrosshair(color.RGBA{165, 55, 253, 180}, 3), - selectionRect: NewRectangle(color.RGBA{0x30, 0x70, 0xFF, 0xFF}, 3), - numColumns: len(config.XData), - numRows: len(config.YData), - numData: len(config.ZData), - colorMode: config.ColorblindMode, + cfg: config, + crosshair: NewCrosshair(color.RGBA{165, 55, 253, 180}, 3), + numColumns: len(config.XData), + numRows: len(config.YData), + numData: len(config.ZData), + colorMode: config.ColorblindMode, } mv.ExtendBaseWidget(mv) @@ -131,7 +132,11 @@ func (mv *MapViewer) CreateRenderer() fyne.WidgetRenderer { mv.createYAxis() mv.createXAxis() mv.createZdata() + mv.createSelectionOverlay() mv.createTextValues() + // Start with cell 0,0 selected so keyboard editing works before any click. + mv.selectedCells = []int{0} + mv.drawSelectionVisual() mv.content = mv.render() return widget.NewSimpleRenderer(mv.content) // return &mapViewerRenderer{mv: mv} @@ -173,8 +178,9 @@ func (mr *movingRectsLayout) Layout(_ []fyne.CanvasObject, size fyne.Size) { float32(float64(mr.mv.numRows)-1-mr.mv.yIndex)*mr.mv.heightFactor, ), ) - mr.mv.resizeSelectionRect() - mr.mv.updateCursor(false) + // The selection lives in selectionOverlay, which shares the cell grid and is + // repositioned automatically by the layout, so there is nothing to recompute + // for it here. } func (mv *MapViewer) render() fyne.CanvasObject { @@ -182,15 +188,11 @@ func (mv *MapViewer) render() fyne.CanvasObject { mv.crosshair.Resize(fyne.NewSize(34, 14)) mv.crosshair.Hide() - // mv.selectionRect.CornerRadius = 4 - mv.selectionRect.Resize(fyne.NewSize(34, 14)) - // mv.selectionRect.Hide() - mv.innerView = container.NewStack( mv.valueRects, + mv.selectionOverlay, container.New(&movingRectsLayout{mv: mv}, mv.crosshair, - mv.selectionRect, ), mv.valueTexts, ) @@ -426,6 +428,42 @@ func (mv *MapViewer) createZdata() { mv.valueRects = container.New(layout.NewGrid(mv.numColumns, mv.numRows, 1.32), objs...) } +// createSelectionOverlay builds a dedicated highlight layer with one +// translucent rectangle per cell. The rectangles are hidden by default and +// only shown for cells present in mv.selectedCells. Keeping selection in its +// own layer decouples it from the value rects, so editing a cell's value never +// clears the selection visual and vice versa. +func (mv *MapViewer) createSelectionOverlay() { + mv.selectionRects = make([]*canvas.Rectangle, mv.numData) + objs := make([]fyne.CanvasObject, mv.numData) + for i := range mv.selectionRects { + rect := canvas.NewRectangle(color.RGBA{0xDE, 0xDF, 0xE4, 0xFF}) + rect.Hide() + mv.selectionRects[i] = rect + objs[i] = rect + } + mv.selectionOverlay = container.New(layout.NewGrid(mv.numColumns, mv.numRows, 1.32), objs...) +} + +// clearSelectionVisual hides the highlight for every currently selected cell. +// Call it before mutating mv.selectedCells, then call drawSelectionVisual after. +func (mv *MapViewer) clearSelectionVisual() { + for _, cell := range mv.selectedCells { + if cell >= 0 && cell < len(mv.selectionRects) { + mv.selectionRects[cell].Hide() + } + } +} + +// drawSelectionVisual shows the highlight for every currently selected cell. +func (mv *MapViewer) drawSelectionVisual() { + for _, cell := range mv.selectedCells { + if cell >= 0 && cell < len(mv.selectionRects) { + mv.selectionRects[cell].Show() + } + } +} + func (mv *MapViewer) setXY() error { xIdx, yIdx, err := interpolate.Interpolate64S(mv.cfg.XData, mv.cfg.YData, mv.cfg.ZData, mv.xValue, mv.yValue) if err != nil { @@ -483,66 +521,6 @@ func (mv *MapViewer) createButtons() *fyne.Container { } } -func (mv *MapViewer) resizeSelectionRect() { - // Early return if no selection - if mv.selectedX < 0 { - return - } - - var pos fyne.Position - var size fyne.Size - - // Handle multiple cell selection - if len(mv.selectedCells) > 1 { - // Initialize bounds using first cell to avoid unnecessary comparisons - firstCell := mv.selectedCells[0] - minX := firstCell % mv.numColumns - maxX := minX - minY := firstCell / mv.numColumns - maxY := minY - - // Find bounds in a single pass - for i := 1; i < len(mv.selectedCells); i++ { - cell := mv.selectedCells[i] - x := cell % mv.numColumns - y := cell / mv.numColumns - - if x < minX { - minX = x - } else if x > maxX { - maxX = x - } - - if y < minY { - minY = y - } else if y > maxY { - maxY = y - } - } - - // Calculate position and size once - topLeftX := float32(minX) * mv.widthFactor - topLeftY := float32(mv.numRows-1-maxY) * mv.heightFactor - width := float32(maxX-minX+1) * mv.widthFactor - height := float32(maxY-minY+1) * mv.heightFactor - - pos = fyne.NewPos(topLeftX-1, topLeftY) - size = fyne.NewSize(width+1, height+1) - } else { - // Single cell selection - pos = fyne.NewPos( - (float32(mv.selectedX)*mv.widthFactor)-1, - (float32(mv.numRows-1-mv.SelectedY) * mv.heightFactor), - ) - size = fyne.NewSize(mv.widthFactor+1, mv.heightFactor+1) - } - - // Batch UI updates - mv.selectionRect.Move(pos) - mv.selectionRect.Resize(size) - // mv.selectionRect.MoveAndResize(pos, size) -} - /* var _ fyne.WidgetRenderer = (*mapViewerRenderer)(nil) @@ -598,12 +576,3 @@ func NewCrosshair(strokeColor color.RGBA, strokeWidth float32) *canvas.Rectangle CornerRadius: 4, } } - -func NewRectangle(strokeColor color.RGBA, strokeWidth float32) *canvas.Rectangle { - return &canvas.Rectangle{ - FillColor: color.RGBA{0, 0, 0, 0}, - StrokeColor: strokeColor, - StrokeWidth: strokeWidth, - CornerRadius: 4, - } -} diff --git a/pkg/widgets/mapviewer/mapviewer_keyhandler.go b/pkg/widgets/mapviewer/mapviewer_keyhandler.go index 27363c11..94958acd 100644 --- a/pkg/widgets/mapviewer/mapviewer_keyhandler.go +++ b/pkg/widgets/mapviewer/mapviewer_keyhandler.go @@ -52,18 +52,22 @@ func (mv *MapViewer) copy() { fyne.CurrentApp().Clipboard().SetContent(copyString.String()) } -func (mv *MapViewer) paste() { - if !mv.cfg.Editable { - return - } - cb := fyne.CurrentApp().Clipboard().Content() +type clipboardCell struct { + x, y int // storage coordinates (y already converted to storage row) + value float64 +} + +// parseClipboardCells parses the internal copy format into cells with storage +// coordinates. The first cell is the anchor; its x carries the +20/+200 marker, +// which is stripped here. +func (mv *MapViewer) parseClipboardCells(cb string) []clipboardCell { split := strings.Split(cb, copyPasteSeparator) + cells := make([]clipboardCell, 0, len(split)) for i, part := range split { if len(part) < 3 { continue } pp := strings.Split(part, ":") - if len(pp) < 3 { continue } @@ -73,7 +77,6 @@ func (mv *MapViewer) paste() { log.Println(err) continue } - if i == 0 && x >= 200 { x -= 200 } else if i == 0 && x >= 20 { @@ -85,27 +88,71 @@ func (mv *MapViewer) paste() { log.Println(err) continue } - value, err := strconv.Atoi(pp[2]) + value, err := strconv.ParseFloat(pp[2], 64) if err != nil { log.Println(err) continue } - y = mv.numRows - 1 - y + cells = append(cells, clipboardCell{x: x, y: mv.numRows - 1 - y, value: value}) + } + return cells +} +// applyPaste writes the parsed cells into ZData, offset by shiftX/shiftY. Cells +// that fall outside the map bounds are skipped. +func (mv *MapViewer) applyPaste(cells []clipboardCell, shiftX, shiftY int) { + changed := false + for _, c := range cells { + x := c.x + shiftX + y := c.y + shiftY + if x < 0 || x >= mv.numColumns || y < 0 || y >= mv.numRows { + continue + } index := y*mv.numColumns + x if index < 0 || index >= len(mv.cfg.ZData) { - log.Printf("Index out of range: %d", index) continue } - mv.cfg.ZData[index] = float64(value) - //if len(split) < 30 { - // mv.cfg.UpdateECUFunc(index, []float64{mv.cfg.ZData[index]}) - //} - } - //if len(split) >= 30 { - // mv.cfg.SaveECUFunc(mv.cfg.ZData) - //} - mv.Refresh() + mv.cfg.ZData[index] = c.value + changed = true + } + if changed { + mv.Refresh() + } +} + +// paste writes the clipboard back to the same coordinates it was copied from. +func (mv *MapViewer) paste() { + if !mv.cfg.Editable { + return + } + cells := mv.parseClipboardCells(fyne.CurrentApp().Clipboard().Content()) + mv.applyPaste(cells, 0, 0) +} + +// pasteHere writes the clipboard with its anchor cell landing on the currently +// selected cell, so the block is pasted starting where the user right-clicked. +func (mv *MapViewer) pasteHere() { + if !mv.cfg.Editable { + return + } + cells := mv.parseClipboardCells(fyne.CurrentApp().Clipboard().Content()) + if len(cells) == 0 { + return + } + // Anchor on the visual top-left of the copied block (smallest column, + // topmost row). Data row 0 is drawn at the bottom, so the topmost row is the + // largest storage y. This makes the block fill down and to the right from + // the clicked cell, matching the on-screen orientation. + minX, maxY := cells[0].x, cells[0].y + for _, c := range cells[1:] { + if c.x < minX { + minX = c.x + } + if c.y > maxY { + maxY = c.y + } + } + mv.applyPaste(cells, mv.selectedX-minX, mv.SelectedY-maxY) } func (mv *MapViewer) smooth() { @@ -136,28 +183,23 @@ func (mv *MapViewer) smooth() { mv.Refresh() } +// updateCursor collapses the selection to the single cell at selectedX/SelectedY +// and redraws the overlay highlight. Used by keyboard navigation and the +// crosshair-follow cursor. Pass goroutine=true when called off the main thread. func (mv *MapViewer) updateCursor(goroutine bool) { cell := mv.SelectedY*mv.numColumns + mv.selectedX - if len(mv.selectedCells) != 1 || mv.selectedCells[0] != cell { - mv.selectedCells = append(mv.selectedCells[:0], cell) - } - xPosFactor := float32(mv.selectedX) - yPosFactor := float32(float64(mv.numRows-1) - float64(mv.SelectedY)) - xPos := xPosFactor * mv.widthFactor - yPos := yPosFactor * mv.heightFactor - size := fyne.Size{Width: mv.widthFactor + 1, Height: mv.heightFactor + 1} - pos := fyne.Position{X: xPos - 1, Y: yPos - 1} - if mv.selectionRect.Size() == size && mv.selectionRect.Position() == pos { + if len(mv.selectedCells) == 1 && mv.selectedCells[0] == cell { return } + apply := func() { + mv.clearSelectionVisual() + mv.selectedCells = append(mv.selectedCells[:0], cell) + mv.drawSelectionVisual() + } if goroutine { - fyne.Do(func() { - mv.selectionRect.Resize(size) - mv.selectionRect.Move(pos) - }) + fyne.Do(apply) } else { - mv.selectionRect.Resize(size) - mv.selectionRect.Move(pos) + apply() } } diff --git a/pkg/widgets/mapviewer/mapviewer_mouse.go b/pkg/widgets/mapviewer/mapviewer_mouse.go index c667cf70..e07055f6 100644 --- a/pkg/widgets/mapviewer/mapviewer_mouse.go +++ b/pkg/widgets/mapviewer/mapviewer_mouse.go @@ -1,12 +1,10 @@ package mapviewer import ( - "math" "slices" "fyne.io/fyne/v2" "fyne.io/fyne/v2/driver/desktop" - "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -38,8 +36,13 @@ func (mv *MapViewer) MouseMoved(event *desktop.MouseEvent) { } mv.mousePos = event.Position - nselectedX, nSelectedY := mv.calculateSelectionBounds(event.Position) - mv.updateSelection(nselectedX, nSelectedY) + nx, ny := mv.calculateSelectionBounds(event.Position) + // Only rebuild the selection when the drag crosses into a different cell. + if nx == mv.dragCornerX && ny == mv.dragCornerY { + return + } + mv.dragCornerX, mv.dragCornerY = nx, ny + mv.selectRegion(nx, ny) } // MouseDown is called when a mouse button is pressed. @@ -54,37 +57,16 @@ func (mv *MapViewer) MouseDown(event *desktop.MouseEvent) { return } - if event.Modifier != fyne.KeyModifierControl { - for _, rect := range mv.zDataRects { - if rect.FillColor != rect.StrokeColor { - rect.FillColor = rect.StrokeColor - rect.Refresh() - } - } - } - mv.handleFocusAndInputBuffer() switch { case event.Button == desktop.MouseButtonPrimary && event.Modifier == 0: - if mv.selectionRect.Hidden { - mv.selectionRect.Resize(fyne.NewSize(mv.widthFactor, mv.heightFactor)) - mv.selectionRect.Show() - } mv.handlePrimaryClick(event) case event.Button == desktop.MouseButtonPrimary && event.Modifier == fyne.KeyModifierShift: - if mv.selectionRect.Hidden { - mv.selectionRect.Resize(fyne.NewSize(mv.widthFactor, mv.heightFactor)) - mv.selectionRect.Show() - } mv.handlePrimaryClickWithShift(event) case event.Button == desktop.MouseButtonPrimary && event.Modifier == fyne.KeyModifierControl: - if mv.selectionRect.Hidden { - mv.selectionRect.Resize(fyne.NewSize(mv.widthFactor, mv.heightFactor)) - mv.selectionRect.Show() - } mv.handlePrimaryCtrlClick(event) case event.Button == desktop.MouseButtonSecondary && event.Modifier == 0: @@ -95,57 +77,77 @@ func (mv *MapViewer) MouseDown(event *desktop.MouseEvent) { // MouseUp is called when a mouse button is released. func (mv *MapViewer) MouseUp(event *desktop.MouseEvent) { // log.Println("MouseUp", event) - mv.selectionRect.Hide() if event.Button == desktop.MouseButtonPrimary && mv.selecting { mv.finalizeSelection(event.Position) } } -// handlePrimaryClick handles the primary click action. +// handlePrimaryClick starts a fresh single-cell selection and begins a drag. func (mv *MapViewer) handlePrimaryClick(event *desktop.MouseEvent) { + mv.clearSelectionVisual() mv.selectedX, mv.SelectedY = mv.calculateSelectionBounds(event.Position) - - cellWidth, cellHeight := mv.calculateCellDimensions() - x := (float32(mv.selectedX) * cellWidth) - y := (float32(mv.numRows-mv.SelectedY-1) * cellHeight) - - mv.updateCursorPositionAndSize(x, y, cellWidth, cellHeight) - mv.selectedCells = []int{mv.SelectedY*mv.numColumns + mv.selectedX} + mv.dragCornerX, mv.dragCornerY = mv.selectedX, mv.SelectedY + mv.selectedCells = append(mv.selectedCells[:0], mv.SelectedY*mv.numColumns+mv.selectedX) + mv.drawSelectionVisual() mv.selecting = true } func (mv *MapViewer) handlePrimaryCtrlClick(event *desktop.MouseEvent) { mv.selectedX, mv.SelectedY = mv.calculateSelectionBounds(event.Position) - - cellWidth, cellHeight := mv.calculateCellDimensions() - x := (float32(mv.selectedX) * cellWidth) - y := (float32(mv.numRows-mv.SelectedY-1) * cellHeight) - - mv.updateCursorPositionAndSize(x, y, cellWidth, cellHeight) - newCell := mv.SelectedY*mv.numColumns + mv.selectedX - // Check if cell is already selected + // Toggle the clicked cell while keeping the rest of the selection. if index := slices.Index(mv.selectedCells, newCell); index != -1 { - // Remove cell if already selected mv.selectedCells = append(mv.selectedCells[:index], mv.selectedCells[index+1:]...) - mv.zDataRects[newCell].FillColor = mv.zDataRects[newCell].StrokeColor + mv.selectionRects[newCell].Hide() } else { - // Add new cell mv.selectedCells = append(mv.selectedCells, newCell) - mv.zDataRects[newCell].FillColor = theme.Color(theme.ColorNameForegroundOnPrimary) + mv.selectionRects[newCell].Show() } - mv.zDataRects[newCell].Refresh() } -// handlePrimaryClickWithShift handles the primary click with shift action. +// handlePrimaryClickWithShift extends the selection from the current anchor to +// the clicked cell and begins a drag. func (mv *MapViewer) handlePrimaryClickWithShift(event *desktop.MouseEvent) { - nselectedX, nSelectedY := mv.calculateSelectionBounds(event.Position) - mv.updateSelection(nselectedX, nSelectedY) + nx, ny := mv.calculateSelectionBounds(event.Position) + mv.dragCornerX, mv.dragCornerY = nx, ny + mv.selectRegion(nx, ny) mv.selecting = true } +// selectRegion replaces the current selection with the rectangular block +// between the anchor cell (selectedX/SelectedY) and the given corner, updating +// the overlay highlight live. +func (mv *MapViewer) selectRegion(cornerX, cornerY int) { + minX, maxX := min(mv.selectedX, cornerX), max(mv.selectedX, cornerX) + minY, maxY := min(mv.SelectedY, cornerY), max(mv.SelectedY, cornerY) + + mv.clearSelectionVisual() + mv.selectedCells = mv.selectedCells[:0] + for y := minY; y <= maxY; y++ { + for x := minX; x <= maxX; x++ { + mv.selectedCells = append(mv.selectedCells, y*mv.numColumns+x) + } + } + mv.drawSelectionVisual() +} + func (mv *MapViewer) handleSecondaryClick(event *desktop.MouseEvent) { + x, y := mv.calculateSelectionBounds(event.Position) + clickedCell := y*mv.numColumns + x + + // The clicked cell becomes the anchor for "Paste here" regardless. + mv.selectedX, mv.SelectedY = x, y + + // Right-clicking inside the current selection keeps it (so Copy/Smooth act + // on the whole block). Right-clicking outside it, or with no selection, + // selects just that cell. + if !slices.Contains(mv.selectedCells, clickedCell) { + mv.clearSelectionVisual() + mv.selectedCells = []int{clickedCell} + mv.drawSelectionVisual() + } + mv.showPopupMenu(event.AbsolutePosition) } @@ -172,24 +174,6 @@ func (mv *MapViewer) calculateSelectionBounds(eventPos fyne.Position) (int, int) return nselectedX, nSelectedY } -// updateSelection updates the selection based on the new cursor position. -func (mv *MapViewer) updateSelection(nselectedX, nSelectedY int) { - cellWidth, cellHeight := mv.calculateCellDimensions() - difX := int(math.Abs(float64(nselectedX - mv.selectedX))) - difY := int(math.Abs(float64(nSelectedY - mv.SelectedY))) - - topLeftX := float32(min(mv.selectedX, nselectedX)) * cellWidth - topLeftY := float32(mv.numRows-1-max(mv.SelectedY, nSelectedY)) * cellHeight - - mv.updateCursorPositionAndSize(topLeftX, topLeftY, float32(difX+1)*cellWidth, float32(difY+1)*cellHeight) -} - -// updateCursorPositionAndSize updates the cursor's position and size on the screen. -func (mv *MapViewer) updateCursorPositionAndSize(topLeftX, topLeftY, width, height float32) { - mv.selectionRect.Resize(fyne.NewSize(width+2, height+1)) - mv.selectionRect.Move(fyne.NewPos(topLeftX-1, topLeftY)) -} - // handleFocusAndInputBuffer focuses the MapViewer and clears the input buffer if necessary. func (mv *MapViewer) handleFocusAndInputBuffer() { if mv.inputBuffer.Len() > 0 { @@ -198,44 +182,13 @@ func (mv *MapViewer) handleFocusAndInputBuffer() { } } -// finalizeSelection finalizes the selection process. +// finalizeSelection ends a drag. The selection was already built live during +// MouseMoved, so this just snaps to the release position and stops selecting. func (mv *MapViewer) finalizeSelection(eventPos fyne.Position) { // log.Println("finalizeSelection") mv.selecting = false - - nselectedX, nSelectedY := mv.calculateSelectionBounds(eventPos) - mv.updateSelection(nselectedX, nSelectedY) - - topLeftX := min(mv.selectedX, nselectedX) - bottomRightX := max(mv.selectedX, nselectedX) - topLeftY := min(mv.SelectedY, nSelectedY) - bottomRightY := max(mv.SelectedY, nSelectedY) - - // For Ctrl selections, we don't want to clear existing selections - if mv.lastModifier != fyne.KeyModifierControl { - mv.selectedCells = make([]int, 0, (bottomRightX-topLeftX+1)*(bottomRightY-topLeftY+1)) - } - - selectedColor := theme.Color(theme.ColorNameForegroundOnPrimary) - for y := topLeftY; y <= bottomRightY; y++ { - for x := topLeftX; x <= bottomRightX; x++ { - zIndex := y*mv.numColumns + x - if mv.lastModifier == fyne.KeyModifierControl { - // For Ctrl, toggle selection - if index := slices.Index(mv.selectedCells, zIndex); index != -1 { - mv.selectedCells = append(mv.selectedCells[:index], mv.selectedCells[index+1:]...) - mv.zDataRects[zIndex].FillColor = mv.zDataRects[zIndex].StrokeColor - } else { - mv.selectedCells = append(mv.selectedCells, zIndex) - mv.zDataRects[zIndex].FillColor = selectedColor - } - } else { - mv.selectedCells = append(mv.selectedCells, zIndex) - mv.zDataRects[zIndex].FillColor = selectedColor - } - mv.zDataRects[zIndex].Refresh() - } - } + nx, ny := mv.calculateSelectionBounds(eventPos) + mv.selectRegion(nx, ny) } func (mv *MapViewer) showPopupMenu(pos fyne.Position) { @@ -250,6 +203,9 @@ func (mv *MapViewer) showPopupMenu(pos fyne.Position) { fyne.NewMenuItem("Paste", func() { mv.paste() }), + fyne.NewMenuItem("Paste here", func() { + mv.pasteHere() + }), fyne.NewMenuItem("Smooth", func() { mv.smooth() }), From a51146e18c723d8bd5f61b3af3d118dde10e0674 Mon Sep 17 00:00:00 2001 From: roffe Date: Mon, 15 Jun 2026 21:20:27 +0200 Subject: [PATCH 047/102] fancier meshgrid --- pkg/widgets/meshgrid/meshgrid_axis.go | 373 +++++++++++++++---- pkg/widgets/meshgrid/meshgrid_draw.go | 4 +- pkg/widgets/meshgrid/meshgrid_poly.go | 7 +- pkg/widgets/meshgrid/meshgrid_render_test.go | 81 +++- pkg/widgets/meshgrid/meshgrid_widget.go | 66 +++- 5 files changed, 432 insertions(+), 99 deletions(-) diff --git a/pkg/widgets/meshgrid/meshgrid_axis.go b/pkg/widgets/meshgrid/meshgrid_axis.go index bcb8c626..e7525ded 100644 --- a/pkg/widgets/meshgrid/meshgrid_axis.go +++ b/pkg/widgets/meshgrid/meshgrid_axis.go @@ -3,6 +3,8 @@ package meshgrid import ( "image" "image/color" + "math" + "strconv" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" @@ -11,107 +13,322 @@ import ( "golang.org/x/image/math/fixed" ) -// initAxisObjects creates the axis-indicator overlay (lines and labels) used -// by the shader and polygon backends; the image backend instead draws the -// indicator into its raster (drawAxisIndicator below). +// T7Suite-style axis scales: instead of a small orientation gizmo in the +// corner, the X (column), Y (row) and Z (value) scales are drawn along three +// edges of the mesh's own bounding box, labeled with the table's real axis +// values. The geometry is computed once per refresh in original (untransformed) +// coordinates, projected with projectOriginal, and consumed either as canvas +// overlay objects (shader/polygon backends) or rasterized into the image +// (image backend). Both paths share computeAxisGeometry so the projection and +// the label thinning live in one place. + +const ( + axisTextSize float32 = 11 + // axisCharW is a rough per-character width at axisTextSize, used only to + // decide how many tick labels fit before they overlap. + axisCharW = 7.0 + // axisEdgeOffset lifts the whole axis (line, ticks, labels) off the mesh + // edge so it doesn't sit directly on the surface. The distances below are + // measured outward from this shifted line. + axisEdgeOffset = 8.0 + axisTickLen = 7.0 // tick-mark length, px, drawn outward from the edge + axisLabelPad = 24.0 // outward distance of a value label's center from the lifted edge + axisNameGap = 16.0 // extra clearance of the axis-name label past the value labels + // zDivisions is the number of intervals on the vertical value scale, so it + // shows zDivisions+1 ticks from zmin to zmax. + zDivisions = 5 +) + +// axisSeg is one screen-space line: a box edge or a tick mark. +type axisSeg struct { + x1, y1, x2, y2 float32 + col color.RGBA +} + +// axisLabel is one screen-space text placed centered on (x, y). +type axisLabel struct { + text string + x, y float32 + col color.RGBA +} + +// initAxisObjects allocates the canvas pools for the overlay backends. The +// pools are sized to the worst case (no thinning) so the per-frame update only +// has to position the active entries and hide the rest; nothing here needs a +// driver, so it is safe from the constructor and from tests. func (m *Meshgrid) initAxisObjects() { - axisColors := [3]color.RGBA{ - {R: 255, A: 255}, // X red - {G: 255, A: 255}, // Y green - {B: 255, A: 255}, // Z blue - } - labels := [3]string{m.xlabel, m.ylabel, m.zlabel} - for i := range m.axisLines { - m.axisLines[i] = &canvas.Line{StrokeColor: axisColors[i], StrokeWidth: 1} - t := canvas.NewText(labels[i], axisColors[i]) - t.TextSize = 11 - m.axisLabels[i] = t + // Worst case: every column/row/Z tick visible, plus a small headroom for + // the always-included last tick on each axis. + maxTicks := m.cols + m.rows + (zDivisions + 1) + 3 + maxLines := 3 + maxTicks // three labeled edges + one tick mark each + maxTexts := maxTicks + 3 // one value label each + three axis names + + m.axisLinePool = make([]*canvas.Line, maxLines) + for i := range m.axisLinePool { + m.axisLinePool[i] = &canvas.Line{StrokeWidth: 1, Hidden: true} + } + m.axisTextPool = make([]*canvas.Text, maxTexts) + for i := range m.axisTextPool { + t := canvas.NewText("", color.White) + t.TextSize = axisTextSize + t.Hidden = true + m.axisTextPool[i] = t } } -// updateAxisObjects mirrors drawAxisIndicator with canvas primitives. +// updateAxisObjects drives the canvas pools from the current axis geometry: +// active entries get positioned and shown, the rest hidden. func (m *Meshgrid) updateAxisObjects() { - const indicatorScale = 60.0 - origin := fyne.NewPos(60, m.size.Height-m.size.Height/4) - - r := m.cameraRotation - ends := [3][3]float64{ - r.MultiplyVector([3]float64{indicatorScale, 0, 0}), - r.MultiplyVector([3]float64{0, -indicatorScale, 0}), - r.MultiplyVector([3]float64{0, 0, indicatorScale}), - } - for i, e := range ends { - end := fyne.NewPos(origin.X+float32(e[0]), origin.Y+float32(e[1])) - m.axisLines[i].Position1 = origin - m.axisLines[i].Position2 = end - // Text positions by its top-left; the image path draws at the - // baseline, so nudge up to roughly match. - m.axisLabels[i].Move(fyne.NewPos(end.X+5, end.Y-8)) + segs, labels := m.computeAxisGeometry() + + for i, l := range m.axisLinePool { + if i < len(segs) { + s := segs[i] + l.StrokeColor = s.col + l.Position1 = fyne.NewPos(s.x1, s.y1) + l.Position2 = fyne.NewPos(s.x2, s.y2) + if l.Hidden { + l.Show() + } + } else if !l.Hidden { + l.Hide() + } + } + + for i, t := range m.axisTextPool { + if i < len(labels) { + lb := labels[i] + t.Text = lb.text + t.Color = lb.col + sz := t.MinSize() + t.Resize(sz) + // canvas.Text positions by its top-left; center it on the anchor. + t.Move(fyne.NewPos(lb.x-sz.Width/2, lb.y-sz.Height/2)) + if t.Hidden { + t.Show() + } + } else if !t.Hidden { + t.Hide() + } } } -func (m *Meshgrid) drawAxisIndicator(img *image.RGBA) { - cornerOffset := 60.0 - indicatorScale := 60.0 +// drawAxisScales rasterizes the same axis geometry into the image backend's +// frame, drawing edges/ticks as Bresenham lines and labels with the bitmap font. +func (m *Meshgrid) drawAxisScales(img *image.RGBA) { + segs, labels := m.computeAxisGeometry() + for _, s := range segs { + drawBresenhamLine(img, int(s.x1), int(s.y1), int(s.x2), int(s.y2), s.col, s.col) + } + for _, l := range labels { + // basicfont.Face7x13 is 7px wide per glyph; the drawer anchors at the + // baseline, so nudge so the label reads roughly centered on its anchor. + w := len(l.text) * 7 + m.drawText(img, l.text, int(l.x)-w/2, int(l.y)+4, l.col) + } +} - // Create the indicator at corner position - origin := Vertex{ - X: cornerOffset, - Y: float64(m.size.Height) - float64(m.size.Height/4), +// computeAxisGeometry builds the screen-space edges, tick marks and labels for +// the three axis scales. It reuses the scratch slices and returns them. +func (m *Meshgrid) computeAxisGeometry() ([]axisSeg, []axisLabel) { + segs := m.scratchAxisSegs[:0] + labels := m.scratchAxisLabels[:0] + if m.size.Width <= 0 || m.size.Height <= 0 { + m.scratchAxisSegs, m.scratchAxisLabels = segs, labels + return segs, labels } - // Instead of using just the rotation matrix, we should use the same - // camera transformation that's applied to the mesh vertices + cw, ch := float64(m.cellWidth), float64(m.cellHeight) + xMax := float64(m.cols) * cw + yMin, yMax := ch, float64(m.rows+1)*ch + zTop := m.depth + + // The four floor corners (z=0). The front-most (largest screen Y) carries + // the X and Y scales on its two outgoing floor edges. The vertical Z scale + // goes on the most side-on of the remaining corners (the leftmost), so its + // edge rides the silhouette in open space instead of being buried behind + // the surface the way the back corner was. + type pt struct{ ox, oy float64 } + floor := [4]pt{{0, yMin}, {xMax, yMin}, {0, yMax}, {xMax, yMax}} + frontIdx := 0 + frontY := float32(math.Inf(-1)) + for i, c := range floor { + _, sy, _ := m.projectOriginal(c.ox, c.oy, 0) + if sy > frontY { + frontY, frontIdx = sy, i + } + } + zIdx := -1 + zX := float32(math.Inf(1)) + for i, c := range floor { + if i == frontIdx { + continue + } + sx, _, _ := m.projectOriginal(c.ox, c.oy, 0) + if sx < zX { + zX, zIdx = sx, i + } + } + front, zCorner := floor[frontIdx], floor[zIdx] - // Use the camera's view matrix (same as in updateVertexPositions) - viewMatrix := m.cameraRotation + // "Inside" reference: screen centroid of all eight box corners. Labels are + // pushed outward from it so they sit outside the surface. + var sumX, sumY float32 + for _, oz := range [2]float64{0, zTop} { + for _, c := range floor { + sx, sy, _ := m.projectOriginal(c.ox, c.oy, oz) + sumX += sx + sumY += sy + } + } + inside := fyne.NewPos(sumX/8, sumY/8) - // Transform axis endpoints using the camera's view matrix - transformedX := viewMatrix.MultiplyVector([3]float64{indicatorScale, 0, 0}) - transformedY := viewMatrix.MultiplyVector([3]float64{0, -indicatorScale, 0}) // Negative Y scale - transformedZ := viewMatrix.MultiplyVector([3]float64{0, 0, indicatorScale}) + xCol := color.RGBA{R: 255, G: 90, B: 90, A: 255} + yCol := color.RGBA{R: 90, G: 220, B: 90, A: 255} + zCol := color.RGBA{R: 120, G: 170, B: 255, A: 255} - // Calculate endpoints - xEnd := Vertex{ - X: origin.X + transformedX[0], - Y: origin.Y + transformedX[1], + // X scale: front edge at constant Y, value per column at the cell center. + nx := 0 + if len(m.xData) >= m.cols { + nx = m.cols } - yEnd := Vertex{ - X: origin.X + transformedY[0], - Y: origin.Y + transformedY[1], + segs, labels = m.appendAxis(segs, labels, inside, xCol, + [3]float64{0, front.oy, 0}, [3]float64{xMax, front.oy, 0}, m.xlabel, nx, + func(k int) [3]float64 { return [3]float64{(float64(k) + 0.5) * cw, front.oy, 0} }, + func(k int) string { return strconv.FormatFloat(m.xData[k], 'f', m.xPrec, 64) }) + + // Y scale: side edge at constant X. Data row 0 sits at the high-Y (far) + // end, so row k maps to Oy = (rows+0.5-k)*ch. + ny := 0 + if len(m.yData) >= m.rows { + ny = m.rows } - zEnd := Vertex{ - X: origin.X + transformedZ[0], - Y: origin.Y + transformedZ[1], + segs, labels = m.appendAxis(segs, labels, inside, yCol, + [3]float64{front.ox, yMin, 0}, [3]float64{front.ox, yMax, 0}, m.ylabel, ny, + func(k int) [3]float64 { return [3]float64{front.ox, (float64(m.rows) + 0.5 - float64(k)) * ch, 0} }, + func(k int) string { return strconv.FormatFloat(m.yData[k], 'f', m.yPrec, 64) }) + + // Z scale: vertical edge at the side corner, zmin..zmax mapped to 0..zTop. + nz := 0 + if m.zrange > 0 && zTop > 0 { + nz = zDivisions + 1 } + segs, labels = m.appendAxis(segs, labels, inside, zCol, + [3]float64{zCorner.ox, zCorner.oy, 0}, [3]float64{zCorner.ox, zCorner.oy, zTop}, m.zlabel, nz, + func(k int) [3]float64 { return [3]float64{zCorner.ox, zCorner.oy, float64(k) / zDivisions * zTop} }, + func(k int) string { + return strconv.FormatFloat(m.zmin+float64(k)/zDivisions*m.zrange, 'f', m.zPrec, 64) + }) - // Draw the axes - ox, oy := int(origin.X), int(origin.Y) + m.scratchAxisSegs, m.scratchAxisLabels = segs, labels + return segs, labels +} + +// appendAxis appends one labeled axis: the edge line from p0 to p1 (original +// coords) lifted off the mesh by axisEdgeOffset, the axis name centered on the +// middle of that edge, and a thinned set of tick marks plus value labels at the +// original-space points returned by pointAt(k), k in [0,n). Everything is +// offset along one outward edge normal so the ticks stay parallel and the whole +// scale sits clear of the surface. The name rides the middle so it doesn't +// collide with the corner tick values. +func (m *Meshgrid) appendAxis(segs []axisSeg, labels []axisLabel, inside fyne.Position, col color.RGBA, + p0, p1 [3]float64, name string, n int, pointAt func(int) [3]float64, valueAt func(int) string, +) ([]axisSeg, []axisLabel) { + sx0, sy0, _ := m.projectOriginal(p0[0], p0[1], p0[2]) + sx1, sy1, _ := m.projectOriginal(p1[0], p1[1], p1[2]) + + // One outward normal for the whole edge keeps the ticks parallel. The mesh + // transform is affine, so the tick points stay collinear with the edge and + // land on the offset line after the same shift. + nx, ny := edgeOutwardNormal(sx0, sy0, sx1, sy1, inside) + ox, oy := nx*axisEdgeOffset, ny*axisEdgeOffset + + ex0, ey0 := sx0+ox, sy0+oy + ex1, ey1 := sx1+ox, sy1+oy + segs = append(segs, axisSeg{ex0, ey0, ex1, ey1, col}) + + if name != "" { + // Sit the name on the edge midpoint, just past the value-label band so + // it never overlaps the corner ticks (and tracks axisLabelPad changes). + mx, my := (sx0+sx1)*0.5, (sy0+sy1)*0.5 + nameDist := float32(axisEdgeOffset + axisLabelPad + axisNameGap) + labels = append(labels, axisLabel{name, mx + nx*nameDist, my + ny*nameDist, col}) + } + if n <= 0 { + return segs, labels + } - // X axis (red) - ex, ey := int(xEnd.X), int(xEnd.Y) - drawBresenhamLine(img, ox, oy, ex, ey, color.RGBA{R: 255, G: 0, B: 0, A: 255}, color.RGBA{R: 255, G: 0, B: 0, A: 255}) + // Thin labels to the count that fits along the projected edge length. + L := math.Hypot(float64(ex1-ex0), float64(ey1-ey0)) + maxChars := 1 + for k := 0; k < n; k++ { + if c := len(valueAt(k)); c > maxChars { + maxChars = c + } + } + step := axisLabelStep(n, L, maxChars) - m.drawText(img, m.xlabel, - int(ex+5), int(ey), - color.RGBA{R: 255, G: 0, B: 0, A: 255}) + appendTick := func(k int) { + p := pointAt(k) + sx, sy, _ := m.projectOriginal(p[0], p[1], p[2]) + bx, by := sx+ox, sy+oy // tick base sits on the lifted edge line + segs = append(segs, axisSeg{bx, by, bx + nx*axisTickLen, by + ny*axisTickLen, col}) + labels = append(labels, axisLabel{valueAt(k), bx + nx*axisLabelPad, by + ny*axisLabelPad, col}) + } + for k := 0; k < n; k += step { + appendTick(k) + } + // Always label the last tick so the axis' full extent is annotated. + if last := n - 1; last%step != 0 { + appendTick(last) + } + return segs, labels +} - // Y axis (green) - ey = int(yEnd.Y) - ex = int(yEnd.X) - drawBresenhamLine(img, ox, oy, ex, ey, color.RGBA{R: 0, G: 255, B: 0, A: 255}, color.RGBA{R: 0, G: 255, B: 0, A: 255}) +// edgeOutwardNormal returns the unit screen-space normal of edge (s0->s1) that +// points away from the inside reference, used to lift the axis and its ticks +// outward in one consistent direction. +func edgeOutwardNormal(sx0, sy0, sx1, sy1 float32, inside fyne.Position) (float32, float32) { + dx, dy := float64(sx1-sx0), float64(sy1-sy0) + L := math.Hypot(dx, dy) + if L < 1e-6 { + return outward(sx0, sy0, inside) // degenerate edge: fall back to radial + } + nx, ny := -dy/L, dx/L + mx, my := float64(sx0+sx1)*0.5, float64(sy0+sy1)*0.5 + if nx*(mx-float64(inside.X))+ny*(my-float64(inside.Y)) < 0 { + nx, ny = -nx, -ny + } + return float32(nx), float32(ny) +} - m.drawText(img, m.ylabel, - int(ex+5), int(ey), - color.RGBA{R: 0, G: 255, B: 0, A: 255}) +// axisLabelStep returns the index stride that keeps drawn labels at least one +// label-width apart along a projected edge of screen length L. +func axisLabelStep(n int, L float64, maxChars int) int { + if n <= 1 || L <= 0 { + return 1 + } + minSpacing := float64(maxChars)*axisCharW + 8 + fit := int(L / minSpacing) + if fit < 1 { + fit = 1 + } + step := (n + fit - 1) / fit + if step < 1 { + step = 1 + } + return step +} - // Z axis (blue) - ex = int(zEnd.X) - ey = int(zEnd.Y) - drawBresenhamLine(img, ox, oy, ex, ey, color.RGBA{R: 0, G: 0, B: 255, A: 255}, color.RGBA{R: 0, G: 0, B: 255, A: 255}) - m.drawText(img, m.zlabel, - int(ex+5), int(ey), - color.RGBA{R: 0, G: 0, B: 255, A: 255}) +// outward returns the unit screen-space direction from the inside reference +// toward (px, py), i.e. the direction to push a label so it clears the surface. +func outward(px, py float32, inside fyne.Position) (float32, float32) { + dx, dy := float64(px-inside.X), float64(py-inside.Y) + d := math.Hypot(dx, dy) + if d < 1e-3 { + return 0, 1 + } + return float32(dx / d), float32(dy / d) } func (m *Meshgrid) drawText(img *image.RGBA, text string, x, y int, col color.RGBA) { diff --git a/pkg/widgets/meshgrid/meshgrid_draw.go b/pkg/widgets/meshgrid/meshgrid_draw.go index 47db30fc..49abf247 100644 --- a/pkg/widgets/meshgrid/meshgrid_draw.go +++ b/pkg/widgets/meshgrid/meshgrid_draw.go @@ -89,7 +89,7 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { if mode != RenderModeWireframe { m.drawSurface(img, projX, projY, vertCol, mode == RenderModeSolidWireframe) - m.drawAxisIndicator(img) + m.drawAxisScales(img) return img } @@ -150,7 +150,7 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { drawBresenhamLine(img, s.x1, s.y1, s.x2, s.y2, c1, c2) } - m.drawAxisIndicator(img) + m.drawAxisScales(img) return img } diff --git a/pkg/widgets/meshgrid/meshgrid_poly.go b/pkg/widgets/meshgrid/meshgrid_poly.go index fd78cb3c..7728d1cb 100644 --- a/pkg/widgets/meshgrid/meshgrid_poly.go +++ b/pkg/widgets/meshgrid/meshgrid_poly.go @@ -213,8 +213,11 @@ func (m *Meshgrid) updatePolygons() { objs = append(objs, m.polys[q.i*m.cols+q.j]) } m.updateAxisObjects() - for i := range m.axisLines { - objs = append(objs, m.axisLines[i], m.axisLabels[i]) + for _, l := range m.axisLinePool { + objs = append(objs, l) + } + for _, t := range m.axisTextPool { + objs = append(objs, t) } objs = append(objs, m.cursor) m.polyObjects = objs diff --git a/pkg/widgets/meshgrid/meshgrid_render_test.go b/pkg/widgets/meshgrid/meshgrid_render_test.go index 3b04bf0e..5dad007e 100644 --- a/pkg/widgets/meshgrid/meshgrid_render_test.go +++ b/pkg/widgets/meshgrid/meshgrid_render_test.go @@ -21,7 +21,8 @@ func testGrid(t testing.TB) *Meshgrid { values[i*cols+j] = 100 / (1 + x*x + y*y) // central hump } } - m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal, backendFromEnv()) + xData, yData := axisValues(cols, rows) + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, xData, yData, 0, 0, 0, colors.ModeNormal, backendFromEnv()) if err != nil { t.Fatal(err) } @@ -29,6 +30,19 @@ func testGrid(t testing.TB) *Meshgrid { return m } +// axisValues builds simple monotonic column/row axis ticks for tests. +func axisValues(cols, rows int) (xData, yData []float64) { + xData = make([]float64, cols) + for j := range xData { + xData[j] = float64(j * 500) + } + yData = make([]float64, rows) + for i := range yData { + yData[i] = float64(i * 10) + } + return xData, yData +} + // TestRenderRotated renders an asymmetric surface (tall corner spike) from // four yaw angles so painter's-order mistakes show up as the spike being // overdrawn by cells that are behind it. @@ -46,7 +60,8 @@ func TestRenderRotated(t *testing.T) { values[i*cols+j] = 10 + 100/(1+x*x+y*y) // spike near one corner } } - m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal, backendFromEnv()) + xData, yData := axisValues(cols, rows) + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, xData, yData, 0, 0, 0, colors.ModeNormal, backendFromEnv()) if err != nil { t.Fatal(err) } @@ -82,6 +97,65 @@ func TestCursorScreenPosition(t *testing.T) { } } +// the axis-scale geometry must contain the three labeled box edges, the axis +// names and the real first/last tick values for each axis, all projected inside +// a sane neighborhood of the widget. +func TestAxisGeometry(t *testing.T) { + m := testGrid(t) // 16x16, xData[j]=j*500, yData[i]=i*10, "RPM"/"Load"/"Fuel" + segs, labels := m.computeAxisGeometry() + + if len(segs) < 3 { + t.Fatalf("got %d axis segments, want at least the 3 box edges", len(segs)) + } + + have := make(map[string]bool, len(labels)) + for _, l := range labels { + have[l.text] = true + } + // axis names plus the first/last X and Y tick values, which appendAxis + // always labels regardless of thinning + for _, want := range []string{"RPM", "Load", "Fuel", "7500", "150"} { + if !have[want] { + t.Errorf("axis labels missing %q; have %v", want, have) + } + } + + // every label and tick endpoint must land near the widget (a broad sanity + // bound: outward-offset labels may sit a little outside the frame) + const margin = 200 + for _, l := range labels { + if l.x < -margin || l.x > m.size.Width+margin || l.y < -margin || l.y > m.size.Height+margin { + t.Errorf("label %q at (%v,%v) far outside widget %v", l.text, l.x, l.y, m.size) + } + } +} + +// updateAxisObjects must drive the canvas pools without a driver panic and +// leave at least the three edges and three axis names visible. +func TestUpdateAxisObjectsOverlay(t *testing.T) { + test.NewApp() // canvas.Text.MinSize needs a (test) driver + m := testGrid(t) + m.updateAxisObjects() + + visibleLines, visibleTexts := 0, 0 + for _, l := range m.axisLinePool { + if !l.Hidden { + visibleLines++ + } + } + for _, tx := range m.axisTextPool { + if !tx.Hidden { + visibleTexts++ + } + } + if visibleLines < 3 { + t.Errorf("want at least the 3 box-edge lines visible, got %d", visibleLines) + } + if visibleTexts < 3 { + t.Errorf("want at least the 3 axis-name labels visible, got %d", visibleTexts) + } +} + func BenchmarkDrawSurface(b *testing.B) { m := testGrid(b) m.renderMode = RenderModeSolidWireframe @@ -129,7 +203,8 @@ func TestPolygonDegenerateQuads(t *testing.T) { values[i*cols+j] = float64((i / 4) * 100) } } - m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, colors.ModeNormal, backendFromEnv()) + xData, yData := axisValues(cols, rows) + m, err := NewMeshgrid("RPM", "Load", "Fuel", values, cols, rows, xData, yData, 0, 0, 0, colors.ModeNormal, backendFromEnv()) if err != nil { t.Fatal(err) } diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index a637a94f..9c330c0a 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -76,6 +76,11 @@ type Meshgrid struct { scratchLines []lineSegment scratchQuads []quadRef + // Scratch geometry for the axis-scale overlay, rebuilt each refresh by + // computeAxisGeometry and consumed by either the canvas pools or the raster. + scratchAxisSegs []axisSeg + scratchAxisLabels []axisLabel + backend RenderBackend // Shader backend (meshgrid_shader.go): the whole mesh in one object. @@ -88,10 +93,13 @@ type Meshgrid struct { scratchFX []float32 scratchFY []float32 - // Axis-indicator overlay shared by the shader and polygon backends (the - // image backend draws its own into the raster). - axisLines [3]*canvas.Line - axisLabels [3]*canvas.Text + // Axis-scale overlay shared by the shader and polygon backends (the image + // backend draws the same geometry into the raster). The line pool holds the + // three labeled box edges plus a tick mark per visible tick; the text pool + // holds a value label per visible tick plus the three axis-name labels. + // Both pools are sized to the worst case at init and Show/Hide per frame. + axisLinePool []*canvas.Line + axisTextPool []*canvas.Text renderMode RenderMode @@ -125,6 +133,14 @@ type Meshgrid struct { xlabel, ylabel, zlabel string + // Axis tick values (one per column / row) and their display precision, + // used to draw the T7Suite-style scales along the mesh edges. xData has + // cols entries, yData has rows; either may be nil/short, in which case that + // axis' value labels are skipped. zPrec formats the height (Z) scale, whose + // values run from zmin to zmax. + xData, yData []float64 + xPrec, yPrec, zPrec int + refreshPending bool colorMode colors.ColorBlindMode @@ -144,7 +160,10 @@ var ( const cursorRadius = 6 // NewMeshgrid creates a new Meshgrid given width, height, depth and spacing. -func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int, colorBlindMode colors.ColorBlindMode, backend RenderBackend) (*Meshgrid, error) { +// xData/yData carry the per-column/row axis tick values (with xPrec/yPrec/zPrec +// display precision) used to draw the axis scales along the mesh edges; either +// may be nil to skip that axis' value labels. +func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int, xData, yData []float64, xPrec, yPrec, zPrec int, colorBlindMode colors.ColorBlindMode, backend RenderBackend) (*Meshgrid, error) { cols = max(1, cols) rows = max(1, rows) // Check if the provided values slice has the correct number of elements @@ -176,6 +195,12 @@ func NewMeshgrid(xlabel, ylabel, zlabel string, values []float64, cols, rows int ylabel: ylabel, zlabel: zlabel, + xData: xData, + yData: yData, + xPrec: xPrec, + yPrec: yPrec, + zPrec: zPrec, + colorMode: colorBlindMode, backend: backend, @@ -492,6 +517,23 @@ func (m *Meshgrid) updateVertexPositions() { } } +// projectOriginal maps a point in the mesh's original (untransformed) coordinate +// space to screen pixels, applying the same camera transform as +// updateVertexPositions and the same screen mapping as cursorScreenPosition / +// updatePolygons. It returns the screen x/y and the view-space depth (z), where +// a larger z is nearer the viewer. This lets the axis overlay project arbitrary +// box-edge points, not just stored vertices. +func (m *Meshgrid) projectOriginal(ox, oy, oz float64) (sx, sy, vz float32) { + vx := (ox - m.centerX) * m.scale + vy := (oy - m.centerY) * m.scale + vz3 := (oz - m.centerZ) * m.scale + r := m.cameraRotation + x := r[0][0]*vx + r[0][1]*vy + r[0][2]*vz3 - m.cameraPosition[0] + y := r[1][0]*vx + r[1][1]*vy + r[1][2]*vz3 - m.cameraPosition[1] + z := r[2][0]*vx + r[2][1]*vy + r[2][2]*vz3 - m.cameraPosition[2] + return float32(float64(m.size.Width)*0.5 + x), float32(float64(m.size.Height)*0.5 + y), float32(z) +} + // SetFloat64 updates a single cell value. The whole mesh is rebuilt since a // new value can shift zmin/zmax and with it every vertex's normalized height. func (m *Meshgrid) SetFloat64(idx int, value float64) { @@ -620,13 +662,6 @@ func (m *Meshgrid) throttledRefresh() { } func (m *Meshgrid) CreateRenderer() fyne.WidgetRenderer { - if m.backend != BackendImage { - // Text measuring needs a driver, so the labels can't be sized in the - // constructor (tests build widgets without an app). - for _, t := range m.axisLabels { - t.Resize(t.MinSize()) - } - } return &meshgridRenderer{MG: m} } @@ -670,8 +705,11 @@ func (m *meshgridRenderer) Objects() []fyne.CanvasObject { if m.objects == nil { m.MG.updateAxisObjects() objs := []fyne.CanvasObject{m.MG.shader} - for i := range m.MG.axisLines { - objs = append(objs, m.MG.axisLines[i], m.MG.axisLabels[i]) + for _, l := range m.MG.axisLinePool { + objs = append(objs, l) + } + for _, t := range m.MG.axisTextPool { + objs = append(objs, t) } m.objects = append(objs, m.MG.cursor) } From 1c641d86f971cbab9682ef073605a8f68525d1e1 Mon Sep 17 00:00:00 2001 From: roffe Date: Mon, 15 Jun 2026 21:20:43 +0200 Subject: [PATCH 048/102] better copy paste on mapviewer --- pkg/widgets/mapviewer/mapviewer.go | 5 ++++ pkg/widgets/mapviewer/mapviewer_keyhandler.go | 28 +++++++++++++++---- pkg/widgets/mapviewer/mapviewer_mouse.go | 20 ++++++++----- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index c5336782..8eb82724 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -259,6 +259,11 @@ func (mv *MapViewer) render() fyne.CanvasObject { mv.cfg.ZData, mv.numColumns, mv.numRows, + mv.cfg.XData, + mv.cfg.YData, + mv.cfg.XPrecision, + mv.cfg.YPrecision, + mv.cfg.ZPrecision, mv.colorMode, mv.cfg.MeshRenderer, ) diff --git a/pkg/widgets/mapviewer/mapviewer_keyhandler.go b/pkg/widgets/mapviewer/mapviewer_keyhandler.go index 94958acd..772995ee 100644 --- a/pkg/widgets/mapviewer/mapviewer_keyhandler.go +++ b/pkg/widgets/mapviewer/mapviewer_keyhandler.go @@ -25,7 +25,9 @@ func (mv *MapViewer) TypedShortcut(shortcut fyne.Shortcut) { case "Copy": mv.copy() case "Paste": - mv.paste() + // Ctrl+V pastes at the current cursor/selection position rather than + // the coordinates the data was originally copied from. + mv.pasteHere() } } @@ -99,8 +101,10 @@ func (mv *MapViewer) parseClipboardCells(cb string) []clipboardCell { } // applyPaste writes the parsed cells into ZData, offset by shiftX/shiftY. Cells -// that fall outside the map bounds are skipped. -func (mv *MapViewer) applyPaste(cells []clipboardCell, shiftX, shiftY int) { +// that fall outside the map bounds are skipped. When bounds is non-nil, only +// destination cells present in the set are written, so the paste stays inside +// the current selection. +func (mv *MapViewer) applyPaste(cells []clipboardCell, shiftX, shiftY int, bounds map[int]struct{}) { changed := false for _, c := range cells { x := c.x + shiftX @@ -112,6 +116,11 @@ func (mv *MapViewer) applyPaste(cells []clipboardCell, shiftX, shiftY int) { if index < 0 || index >= len(mv.cfg.ZData) { continue } + if bounds != nil { + if _, ok := bounds[index]; !ok { + continue + } + } mv.cfg.ZData[index] = c.value changed = true } @@ -126,7 +135,7 @@ func (mv *MapViewer) paste() { return } cells := mv.parseClipboardCells(fyne.CurrentApp().Clipboard().Content()) - mv.applyPaste(cells, 0, 0) + mv.applyPaste(cells, 0, 0, nil) } // pasteHere writes the clipboard with its anchor cell landing on the currently @@ -152,7 +161,16 @@ func (mv *MapViewer) pasteHere() { maxY = c.y } } - mv.applyPaste(cells, mv.selectedX-minX, mv.SelectedY-maxY) + // When more than a single cell is selected, confine the paste to that + // selection so values can't spill outside the highlighted block. + var bounds map[int]struct{} + if len(mv.selectedCells) > 1 { + bounds = make(map[int]struct{}, len(mv.selectedCells)) + for _, cell := range mv.selectedCells { + bounds[cell] = struct{}{} + } + } + mv.applyPaste(cells, mv.selectedX-minX, mv.SelectedY-maxY, bounds) } func (mv *MapViewer) smooth() { diff --git a/pkg/widgets/mapviewer/mapviewer_mouse.go b/pkg/widgets/mapviewer/mapviewer_mouse.go index e07055f6..60a5ebf4 100644 --- a/pkg/widgets/mapviewer/mapviewer_mouse.go +++ b/pkg/widgets/mapviewer/mapviewer_mouse.go @@ -26,7 +26,7 @@ func (mv *MapViewer) MouseOut() {} // MouseMoved is called when the mouse is moved over the map viewer. func (mv *MapViewer) MouseMoved(event *desktop.MouseEvent) { - //log.Println("MouseMoved", event) + // log.Println("MouseMoved", event) if !mv.selecting { return } @@ -47,7 +47,7 @@ func (mv *MapViewer) MouseMoved(event *desktop.MouseEvent) { // MouseDown is called when a mouse button is pressed. func (mv *MapViewer) MouseDown(event *desktop.MouseEvent) { - //log.Println("MouseDown") + // log.Println("MouseDown") mv.lastModifier = event.Modifier if mv.cfg.OnMouseDown != nil { mv.cfg.OnMouseDown() @@ -161,8 +161,8 @@ func (mv *MapViewer) calculateCellDimensions() (float32, float32) { // calculateSelectionBounds computes the bounding box of the selection area. func (mv *MapViewer) calculateSelectionBounds(eventPos fyne.Position) (int, int) { cellWidth, cellHeight := mv.calculateCellDimensions() - //xAxisOffset := mv.yAxisLabelContainer.Size().Width - //yAxisOffset := mv.xAxisLabelContainer.Size().Height + // xAxisOffset := mv.yAxisLabelContainer.Size().Width + // yAxisOffset := mv.xAxisLabelContainer.Size().Height // Adjust for inner view position relative to the parent container // This accounts for any extra padding or layout adjustments @@ -199,13 +199,19 @@ func (mv *MapViewer) showPopupMenu(pos fyne.Position) { }), ) if mv.cfg.Editable { - menu.Items = append(menu.Items, - fyne.NewMenuItem("Paste", func() { + + pasteMenu := fyne.NewMenuItem("Paste", nil) + pasteMenu.ChildMenu = fyne.NewMenu("Paste Options", + fyne.NewMenuItem("At original position", func() { mv.paste() }), - fyne.NewMenuItem("Paste here", func() { + fyne.NewMenuItem("At currently selected location", func() { mv.pasteHere() }), + ) + + menu.Items = append(menu.Items, + pasteMenu, fyne.NewMenuItem("Smooth", func() { mv.smooth() }), From 84653f1419212633e731c87a8c0db4007c238a00 Mon Sep 17 00:00:00 2001 From: roffe Date: Tue, 16 Jun 2026 00:12:11 +0200 Subject: [PATCH 049/102] save --- go.mod | 2 +- go.sum | 4 +- main_linux.go | 8 +- pkg/assets/WHATSNEW.md | 1 + pkg/common/common.go | 9 + pkg/native/dialog_linux.go | 51 +- pkg/native/dialog_windows.go | 94 +- pkg/native/native.go | 5 +- pkg/widgets/mapviewer/mapviewer_mouse.go | 7 + pkg/widgets/matrixbuilder/matrixbuilder.go | 978 +++++++++++++++++++++ pkg/widgets/plotter/plotter.go | 12 +- pkg/widgets/widget.go | 27 + pkg/widgets/widget_linux.go | 26 +- pkg/widgets/widget_windows.go | 5 + pkg/windows/mainWindow_menu.go | 73 +- pkg/windows/mainWindow_toolbar.go | 15 + 16 files changed, 1249 insertions(+), 68 deletions(-) create mode 100644 pkg/widgets/matrixbuilder/matrixbuilder.go diff --git a/go.mod b/go.mod index ebca1ec2..77979766 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.7.5-0.20260613155404-ebf0c95ebbb7 + fyne.io/fyne/v2 v2.7.5-0.20260614063241-4c0c29f7d5a7 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 diff --git a/go.sum b/go.sum index 876b9002..4299a795 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -fyne.io/fyne/v2 v2.7.5-0.20260613155404-ebf0c95ebbb7 h1:TNADRWLV+A9auHNWACCtB6l/5vBKBnivf/tm9OR1+C0= -fyne.io/fyne/v2 v2.7.5-0.20260613155404-ebf0c95ebbb7/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= +fyne.io/fyne/v2 v2.7.5-0.20260614063241-4c0c29f7d5a7 h1:2auph/jcheGuecJjdA5JahXIFFeLjglROzorkhmLxiU= +fyne.io/fyne/v2 v2.7.5-0.20260614063241-4c0c29f7d5a7/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= diff --git a/main_linux.go b/main_linux.go index ba83a19e..b40bb54e 100644 --- a/main_linux.go +++ b/main_linux.go @@ -24,6 +24,7 @@ func runFileChild() { } var path string + var paths []string switch req.Op { case "select_folder": path, err = native.OpenFolderDialog(req.Title) @@ -37,6 +38,11 @@ func runFileChild() { Description: req.Desc, Extensions: req.Exts, }) + case "open_files": + paths, err = native.OpenFilesDialog(req.Title, native.FileFilter{ + Description: req.Desc, + Extensions: req.Exts, + }) case "quit": return default: @@ -44,7 +50,7 @@ func runFileChild() { return } - resp := native.FileResponse{Path: path} + resp := native.FileResponse{Path: path, Paths: paths} if err != nil { resp.Err = err.Error() } diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index 85bb4f81..e78a686b 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -20,6 +20,7 @@ - Dial and Dual Dial now uses shaders to draw the faces, dials and pips - Improved cell selection in mapviewer - Improved copy paste in mapviewer, added paste here function +- Added a Matrix builder from logfiles # 2.1.9 - Updated default T7 preset to include MAF.m_AirFromp_AirInlet diff --git a/pkg/common/common.go b/pkg/common/common.go index 20b591cf..27e35eef 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -89,6 +89,15 @@ func GetLayoutPath() (string, error) { return layoutPath, createDirIfNotExists(layoutPath) } +func GetMatrixBuilderPath() (string, error) { + dir, err := GetUserHomeDir() + if err != nil { + return "", err + } + matrixBuilderPath := GetComponentPath(dir, "matrixbuilder") + return matrixBuilderPath, createDirIfNotExists(matrixBuilderPath) +} + func GetBinPath() (string, error) { dir, err := GetUserHomeDir() if err != nil { diff --git a/pkg/native/dialog_linux.go b/pkg/native/dialog_linux.go index d955fc5b..da214046 100644 --- a/pkg/native/dialog_linux.go +++ b/pkg/native/dialog_linux.go @@ -46,9 +46,17 @@ func buildPortalFilters(filters []FileFilter) dbus.Variant { } func portalCall(method string, options map[string]dbus.Variant, args ...interface{}) (string, error) { + paths, err := portalCallMulti(method, options, args...) + if err != nil { + return "", err + } + return paths[0], nil +} + +func portalCallMulti(method string, options map[string]dbus.Variant, args ...interface{}) ([]string, error) { conn, err := dbus.SessionBus() if err != nil { - return "", fmt.Errorf("failed to connect to session bus: %w", err) + return nil, fmt.Errorf("failed to connect to session bus: %w", err) } defer conn.Close() @@ -65,7 +73,7 @@ func portalCall(method string, options map[string]dbus.Variant, args ...interfac var handle dbus.ObjectPath if err := obj.Call(method, 0, callArgs...).Store(&handle); err != nil { - return "", fmt.Errorf("portal call %s failed: %w", method, err) + return nil, fmt.Errorf("portal call %s failed: %w", method, err) } if err := conn.AddMatchSignal( @@ -73,7 +81,7 @@ func portalCall(method string, options map[string]dbus.Variant, args ...interfac dbus.WithMatchInterface("org.freedesktop.portal.Request"), dbus.WithMatchMember("Response"), ); err != nil { - return "", fmt.Errorf("failed to add match signal: %w", err) + return nil, fmt.Errorf("failed to add match signal: %w", err) } c := make(chan *dbus.Signal, 10) @@ -85,27 +93,35 @@ func portalCall(method string, options map[string]dbus.Variant, args ...interfac continue } if len(sig.Body) < 2 { - return "", errors.New("unexpected signal body") + return nil, errors.New("unexpected signal body") } response, ok := sig.Body[0].(uint32) if !ok { - return "", fmt.Errorf("unexpected response type: %T", sig.Body[0]) + return nil, fmt.Errorf("unexpected response type: %T", sig.Body[0]) } if response != 0 { - return "", ErrCancelled + return nil, ErrCancelled } results, ok := sig.Body[1].(map[string]dbus.Variant) if !ok { - return "", fmt.Errorf("unexpected results type: %T", sig.Body[1]) + return nil, fmt.Errorf("unexpected results type: %T", sig.Body[1]) } uris, ok := results["uris"].Value().([]string) if !ok || len(uris) == 0 { - return "", errors.New("no files selected") + return nil, errors.New("no files selected") } - return uriToPath(uris[0]) + paths := make([]string, 0, len(uris)) + for _, uri := range uris { + p, err := uriToPath(uri) + if err != nil { + return nil, err + } + paths = append(paths, p) + } + return paths, nil } - return "", errors.New("signal channel closed unexpectedly") + return nil, errors.New("signal channel closed unexpectedly") } func uriToPath(uri string) (string, error) { @@ -131,6 +147,21 @@ func OpenFileDialog(title string, filters ...FileFilter) (string, error) { ) } +func OpenFilesDialog(title string, filters ...FileFilter) ([]string, error) { + options := map[string]dbus.Variant{ + "handle_token": dbus.MakeVariant(GenerateDBusToken()), + "multiple": dbus.MakeVariant(true), + } + if len(filters) > 0 { + options["filters"] = buildPortalFilters(filters) + } + return portalCallMulti( + "org.freedesktop.portal.FileChooser.OpenFile", + options, + title, + ) +} + func OpenFolderDialog(title string) (string, error) { options := map[string]dbus.Variant{ "handle_token": dbus.MakeVariant(GenerateDBusToken()), diff --git a/pkg/native/dialog_windows.go b/pkg/native/dialog_windows.go index 1fa25846..ac3c1619 100644 --- a/pkg/native/dialog_windows.go +++ b/pkg/native/dialog_windows.go @@ -2,6 +2,7 @@ package native import ( "fmt" + "path/filepath" "reflect" "strings" "syscall" @@ -26,12 +27,13 @@ var ( ) const ( - MAX_PATH = 260 - OFN_EXPLORER = 0x00080000 - OFN_FILEMUSTEXIST = 0x00001000 - OFN_PATHMUSTEXIST = 0x00000800 - OFN_OVERWRITEPROMPT = 0x00000002 - OFN_NOCHANGEDIR = 0x00000008 + MAX_PATH = 260 + OFN_EXPLORER = 0x00080000 + OFN_FILEMUSTEXIST = 0x00001000 + OFN_PATHMUSTEXIST = 0x00000800 + OFN_OVERWRITEPROMPT = 0x00000002 + OFN_NOCHANGEDIR = 0x00000008 + OFN_ALLOWMULTISELECT = 0x00000200 ) type openfilenameW struct { @@ -108,6 +110,86 @@ func OpenFileDialog(title string, filters ...FileFilter) (string, error) { return windows.UTF16PtrToString(ofn.lpstrFile), nil } +// OpenFilesDialog shows a native open dialog allowing multiple selections and +// returns the chosen paths. +func OpenFilesDialog(title string, filters ...FileFilter) ([]string, error) { + // Multi-select needs a large buffer: the result holds the directory plus + // every selected filename, NUL-separated, terminated by a double NUL. + fileBuf := make([]uint16, 64*1024) + + var filter []uint16 + for _, filt := range filters { + desc := fmt.Sprintf("%s (%s)", filt.Description, strings.Join(filt.Extensions, ",")) + filter = append(filter, utf16.Encode([]rune(desc))...) + filter = append(filter, 0x00) + for _, ext := range filt.Extensions { + s := fmt.Sprintf("*.%s;", ext) + filter = append(filter, utf16.Encode([]rune(s))...) + } + filter = append(filter, 0x00) + } + + filterPtr := utf16ptr(filter) + titlePtr, err := windows.UTF16PtrFromString(title) + if err != nil { + return nil, err + } + + ofn := openfilenameW{ + lStructSize: uint32(unsafe.Sizeof(openfilenameW{})), + lpstrFilter: filterPtr, + lpstrFile: &fileBuf[0], + nMaxFile: uint32(len(fileBuf)), + lpstrTitle: titlePtr, + Flags: OFN_EXPLORER | OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR, + nFilterIndex: 1, + } + + ret, _, err := procGetOpenFileNameW.Call(uintptr(unsafe.Pointer(&ofn))) + if ret == 0 { + if err != syscall.Errno(0) { + return nil, err + } + return nil, ErrCancelled + } + + paths := parseMultiSelect(fileBuf) + if len(paths) == 0 { + return nil, ErrCancelled + } + return paths, nil +} + +// parseMultiSelect decodes the OFN_ALLOWMULTISELECT result buffer. When a single +// file is chosen the buffer holds its full path; when several are chosen the +// first entry is the directory and the rest are bare filenames to join onto it. +func parseMultiSelect(buf []uint16) []string { + var parts []string + start := 0 + for i := 0; i < len(buf); i++ { + if buf[i] == 0 { + if i == start { + break // empty entry => terminating double NUL + } + parts = append(parts, string(utf16.Decode(buf[start:i]))) + start = i + 1 + } + } + switch len(parts) { + case 0: + return nil + case 1: + return parts + default: + dir := parts[0] + out := make([]string, 0, len(parts)-1) + for _, name := range parts[1:] { + out = append(out, filepath.Join(dir, name)) + } + return out + } +} + type browseinfoW struct { HwndOwner uintptr PidlRoot uintptr diff --git a/pkg/native/native.go b/pkg/native/native.go index f950ac59..a52de2f0 100644 --- a/pkg/native/native.go +++ b/pkg/native/native.go @@ -22,6 +22,7 @@ type FileRequest struct { } type FileResponse struct { - Path string - Err string + Path string + Paths []string + Err string } diff --git a/pkg/widgets/mapviewer/mapviewer_mouse.go b/pkg/widgets/mapviewer/mapviewer_mouse.go index 60a5ebf4..bb7fda1e 100644 --- a/pkg/widgets/mapviewer/mapviewer_mouse.go +++ b/pkg/widgets/mapviewer/mapviewer_mouse.go @@ -4,6 +4,7 @@ import ( "slices" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" ) @@ -104,6 +105,12 @@ func (mv *MapViewer) handlePrimaryCtrlClick(event *desktop.MouseEvent) { mv.selectedCells = append(mv.selectedCells, newCell) mv.selectionRects[newCell].Show() } + // Show()/Hide() only flip the Hidden flag, and canvas.Refresh on a rect + // that has never been painted (a hidden overlay cell) is a no-op because the + // object isn't in the canvas cache. Refresh the always-visible value rect for + // this cell instead to dirty the canvas and force an immediate repaint that + // draws the toggled highlight. + canvas.Refresh(mv.zDataRects[newCell]) } // handlePrimaryClickWithShift extends the selection from the current anchor to diff --git a/pkg/widgets/matrixbuilder/matrixbuilder.go b/pkg/widgets/matrixbuilder/matrixbuilder.go new file mode 100644 index 00000000..32e58984 --- /dev/null +++ b/pkg/widgets/matrixbuilder/matrixbuilder.go @@ -0,0 +1,978 @@ +// Package matrixbuilder provides a widget that learns a 2D map (matrix) from +// one or more log files. The widget loads the logs itself and merges their +// series, then builds the matrix from three selected series: one drives the X +// axis, one the Y axis and one supplies the Z value written into the cell the +// X/Y pair lands on. Every sample that maps to a cell is accumulated and the +// cell's final value is the average of all its hits. The resulting matrix is +// shown with a mapviewer (colored grid + 3D meshgrid), and both the axis +// breakpoints and the series can be edited or typed by hand. +package matrixbuilder + +import ( + "encoding/json" + "fmt" + "io" + "log" + "math" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + xlayout "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/common" + "github.com/roffe/txlogger/pkg/layout" + "github.com/roffe/txlogger/pkg/logfile" + "github.com/roffe/txlogger/pkg/widgets" + "github.com/roffe/txlogger/pkg/widgets/mapviewer" + "github.com/roffe/txlogger/pkg/widgets/progressmodal" +) + +const ( + minAxis = 1 + maxAxis = 40 + defaultCols = 8 + defaultRows = 8 + + // Tolerance is expressed as a percentage of a cell's half-spacing (the + // distance from a breakpoint to the midpoint between it and its neighbor). + // At 100% the whole nearest-neighbor region counts as a hit (the original + // behaviour); lower values reject samples that fall near the cell edges, + // keeping only those close to the breakpoint. + minTolerance = 1 + defaultTolerance = 100 +) + +var _ fyne.Widget = (*MatrixBuilder)(nil) + +type MatrixBuilder struct { + widget.BaseWidget + + // values holds every series merged across all loaded log files. All series + // share the same length (nrecords); gaps are padded with NaN so samples + // from different files stay row-aligned. + values map[string][]float64 + order []string + loadedFiles []string + nrecords int + + xSeries, ySeries, zSeries string + + cols, rows int + xAxis []float64 + yAxis []float64 + zData []float64 + + // xTolerance/yTolerance gate how close (as a percentage of the cell's + // half-spacing) a sample must be to its nearest breakpoint to count as a + // Z-hit on that axis. A sample is mapped only if it passes on both axes. + xTolerance, yTolerance float64 + + // built becomes true after the first successful analysis; until then the + // display area shows a placeholder instead of an all-zero grid. + built bool + + // widgets + colsLabel, rowsLabel *widget.Label + status *widget.Label + logsLabel *widget.Label + xBox, yBox *fyne.Container + xEntries, yEntries []*widget.Entry + xSel, ySel, zSel *widget.SelectEntry + xTolSlider, yTolSlider *widget.Slider + xTolLabel, yTolLabel *widget.Label + presetSelect *widget.Select + nameEntry *widget.Entry + display *fyne.Container + + content fyne.CanvasObject +} + +// Preset is the on-disk representation of a matrix builder configuration. It +// holds only settings (series, dimensions and axis breakpoints); the learned +// matrix values are never stored. +type Preset struct { + XSeries string `json:"x_series"` + YSeries string `json:"y_series"` + ZSeries string `json:"z_series"` + Cols int `json:"cols"` + Rows int `json:"rows"` + XAxis []float64 `json:"x_axis"` + YAxis []float64 `json:"y_axis"` + // XTolerance/YTolerance are percentages (1..100). Omitted in older presets, + // which decode to 0 and are treated as the default (no filtering) on load. + XTolerance float64 `json:"x_tolerance,omitempty"` + YTolerance float64 `json:"y_tolerance,omitempty"` +} + +// New creates an empty MatrixBuilder. Log files are loaded from within the +// widget via a native file dialog. +func New() *MatrixBuilder { + mb := &MatrixBuilder{ + values: make(map[string][]float64), + cols: defaultCols, + rows: defaultRows, + xTolerance: defaultTolerance, + yTolerance: defaultTolerance, + } + mb.ExtendBaseWidget(mb) + mb.xAxis = make([]float64, mb.cols) + mb.yAxis = make([]float64, mb.rows) + mb.zData = make([]float64, mb.cols*mb.rows) + mb.buildUI() + return mb +} + +func (mb *MatrixBuilder) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(mb.content) +} + +func (mb *MatrixBuilder) buildUI() { + // SelectEntry lets the user pick a loaded series from the dropdown or type a + // name manually. + mb.xSel = widget.NewSelectEntry(mb.order) + mb.xSel.OnChanged = func(s string) { mb.xSeries = s } + mb.ySel = widget.NewSelectEntry(mb.order) + mb.ySel.OnChanged = func(s string) { mb.ySeries = s } + mb.zSel = widget.NewSelectEntry(mb.order) + mb.zSel.OnChanged = func(s string) { mb.zSeries = s } + mb.xSel.PlaceHolder = "X series" + mb.ySel.PlaceHolder = "Y series" + mb.zSel.PlaceHolder = "Z series" + + mb.colsLabel = widget.NewLabel(strconv.Itoa(mb.cols)) + mb.rowsLabel = widget.NewLabel(strconv.Itoa(mb.rows)) + mb.status = widget.NewLabel("") + mb.status.Wrapping = fyne.TextWrapWord + + colsRow := container.NewBorder(nil, nil, + widget.NewLabel("Columns (X)"), + container.NewHBox( + widget.NewButton("-", func() { mb.setCols(mb.cols - 1) }), + mb.colsLabel, + widget.NewButton("+", func() { mb.setCols(mb.cols + 1) }), + widget.NewButton("Auto", func() { mb.autoFill(true) }), + ), + ) + rowsRow := container.NewBorder(nil, nil, + widget.NewLabel("Rows (Y)"), + container.NewHBox( + widget.NewButton("-", func() { mb.setRows(mb.rows - 1) }), + mb.rowsLabel, + widget.NewButton("+", func() { mb.setRows(mb.rows + 1) }), + widget.NewButton("Auto", func() { mb.autoFill(false) }), + ), + ) + + buildBtn := widget.NewButtonWithIcon("Build matrix", theme.GridIcon(), func() { + if err := mb.analyze(); err != nil { + mb.status.SetText(err.Error()) + return + } + mb.rebuildDisplay() + }) + buildBtn.Importance = widget.HighImportance + + mb.xBox = container.NewVBox() + mb.yBox = container.NewHBox() + mb.rebuildAxisEntries() + + controls := container.NewVBox( + // widget.NewLabelWithStyle("Series", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + colsRow, + rowsRow, + widget.NewSeparator(), + labeled("X", mb.xSel), + labeled("Y", mb.ySel), + labeled("Z", mb.zSel), + widget.NewSeparator(), + mb.buildToleranceSection(), + widget.NewSeparator(), + buildBtn, + widget.NewSeparator(), + mb.status, + widget.NewSeparator(), + xlayout.NewSpacer(), + mb.buildPresetSection(), + ) + + left := container.NewVScroll(controls) + left.SetMinSize(fyne.NewSize(240, 0)) + + right := container.NewVScroll( + mb.buildLogSection(), + ) + + mb.display = container.NewStack(mb.placeholder()) + + // The Y scale runs along the vertical axis of the map; its editor sits as a + // horizontal strip beneath the display. + yPanel := container.NewBorder(nil, nil, + widget.NewLabelWithStyle("Y axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + nil, + container.NewHScroll(mb.yBox), + ) + + mb.content = container.NewBorder( + nil, + nil, + right, + left, + container.NewBorder(nil, yPanel, container.NewVBox(widget.NewLabelWithStyle("X axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + mb.xBox), nil, mb.display), + ) +} + +func (mb *MatrixBuilder) placeholder() fyne.CanvasObject { + return container.NewCenter(widget.NewLabel("Select X, Y and Z series, then click \"Build matrix\"")) +} + +// buildToleranceSection builds the per-axis Z-hit tolerance sliders. Each +// slider sets the maximum distance (as a percentage of the cell's half-spacing) +// a sample may sit from its nearest breakpoint and still count as a hit. +func (mb *MatrixBuilder) buildToleranceSection() fyne.CanvasObject { + mb.xTolLabel = widget.NewLabel(tolText(mb.xTolerance)) + mb.yTolLabel = widget.NewLabel(tolText(mb.yTolerance)) + mb.xTolSlider = mb.newToleranceSlider(true) + mb.yTolSlider = mb.newToleranceSlider(false) + + return container.NewVBox( + widget.NewLabelWithStyle("Z-hit tolerance", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + container.NewBorder(nil, nil, widget.NewLabel("X"), layout.NewFixedWidth(44, mb.xTolLabel), mb.xTolSlider), + container.NewBorder(nil, nil, widget.NewLabel("Y"), layout.NewFixedWidth(44, mb.yTolLabel), mb.yTolSlider), + ) +} + +// newToleranceSlider builds a 1..100% slider bound to the X or Y tolerance. +// While dragging it only updates the value label; on release it re-runs the +// analysis (if a matrix is already built) so the user sees the effect live. +func (mb *MatrixBuilder) newToleranceSlider(isX bool) *widget.Slider { + cur := mb.yTolerance + if isX { + cur = mb.xTolerance + } + s := widget.NewSlider(minTolerance, 100) + s.Step = 1 + s.SetValue(cur) + s.OnChanged = func(f float64) { + if isX { + mb.xTolerance = f + mb.xTolLabel.SetText(tolText(f)) + } else { + mb.yTolerance = f + mb.yTolLabel.SetText(tolText(f)) + } + } + s.OnChangeEnded = func(float64) { + if !mb.built { + return + } + if err := mb.analyze(); err != nil { + mb.status.SetText(err.Error()) + return + } + mb.rebuildDisplay() + } + return s +} + +// rebuildAxisEntries regenerates the editable entry fields for the current +// column/row counts, seeding them from the current axis values. +func (mb *MatrixBuilder) rebuildAxisEntries() { + mb.xEntries = make([]*widget.Entry, mb.cols) + mb.xBox.Objects = mb.xBox.Objects[:0] + for i := 0; i < mb.cols; i++ { + mb.xBox.Add(mb.makeAxisEntry(true, i)) + } + mb.xBox.Refresh() + + mb.yEntries = make([]*widget.Entry, mb.rows) + mb.yBox.Objects = mb.yBox.Objects[:0] + for i := 0; i < mb.rows; i++ { + mb.yBox.Add(mb.makeAxisEntry(false, i)) + } + mb.yBox.Refresh() +} + +func (mb *MatrixBuilder) makeAxisEntry(isX bool, idx int) fyne.CanvasObject { + axis := mb.xAxis + prefix := "X" + if !isX { + axis = mb.yAxis + prefix = "Y" + } + e := widget.NewEntry() + e.SetText(formatFloat(axis[idx])) + e.OnChanged = func(s string) { + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return + } + if isX { + mb.xAxis[idx] = v + } else { + mb.yAxis[idx] = v + } + } + // Apply (relabel the displayed map) when the user commits a value. + e.OnSubmitted = func(string) { + if mb.built { + mb.rebuildDisplay() + } + } + if isX { + mb.xEntries[idx] = e + // X breakpoints stack vertically in the side panel: label beside entry. + return container.NewBorder(nil, nil, widget.NewLabel(prefix+strconv.Itoa(idx)), nil, e) + } + mb.yEntries[idx] = e + // Y breakpoints run horizontally along the bottom: label above a + // fixed-width entry so the strip stays compact. + label := widget.NewLabelWithStyle(prefix+strconv.Itoa(idx), fyne.TextAlignCenter, fyne.TextStyle{}) + return container.NewVBox(label, layout.NewFixedWidth(64, e)) +} + +func (mb *MatrixBuilder) setCols(n int) { + n = clamp(n, minAxis, maxAxis) + if n == mb.cols { + return + } + mb.xAxis = resizeAxis(mb.xAxis, n) + mb.cols = n + mb.zData = make([]float64, mb.cols*mb.rows) + mb.built = false + mb.colsLabel.SetText(strconv.Itoa(n)) + mb.rebuildAxisEntries() + mb.rebuildDisplay() +} + +func (mb *MatrixBuilder) setRows(n int) { + n = clamp(n, minAxis, maxAxis) + if n == mb.rows { + return + } + mb.yAxis = resizeAxis(mb.yAxis, n) + mb.rows = n + mb.zData = make([]float64, mb.cols*mb.rows) + mb.built = false + mb.rowsLabel.SetText(strconv.Itoa(n)) + mb.rebuildAxisEntries() + mb.rebuildDisplay() +} + +// autoFill spreads the selected series' min..max evenly across the axis +// breakpoints. isX selects the X axis, otherwise the Y axis. +func (mb *MatrixBuilder) autoFill(isX bool) { + series := mb.ySeries + axis := mb.yAxis + if isX { + series = mb.xSeries + axis = mb.xAxis + } + data, ok := mb.values[series] + if !ok || len(data) == 0 { + return + } + lo, hi := minMax(data) + n := len(axis) + for i := 0; i < n; i++ { + if n == 1 { + axis[i] = lo + continue + } + axis[i] = lo + (hi-lo)*float64(i)/float64(n-1) + } + mb.syncAxisEntries() + if mb.built { + mb.rebuildDisplay() + } +} + +// syncAxisEntries pushes the current axis values back into the entry widgets. +// The entry/axis lengths track each other via rebuildAxisEntries, but the +// guard keeps a transient desync from panicking the UI thread. +func (mb *MatrixBuilder) syncAxisEntries() { + for i, e := range mb.xEntries { + if i >= len(mb.xAxis) { + break + } + e.SetText(formatFloat(mb.xAxis[i])) + } + for i, e := range mb.yEntries { + if i >= len(mb.yAxis) { + break + } + e.SetText(formatFloat(mb.yAxis[i])) + } +} + +// analyze walks the log and learns the matrix: each sample is assigned to the +// nearest cell and the cell's value becomes the average of its hits. +func (mb *MatrixBuilder) analyze() error { + if len(mb.values) == 0 { + return fmt.Errorf("load a log file first") + } + if mb.xSeries == "" || mb.ySeries == "" || mb.zSeries == "" { + return fmt.Errorf("select X, Y and Z series first") + } + xv, okX := mb.values[mb.xSeries] + yv, okY := mb.values[mb.ySeries] + zv, okZ := mb.values[mb.zSeries] + if !okX || !okY || !okZ { + return fmt.Errorf("selected series not found in the loaded logs") + } + n := min(len(xv), min(len(yv), len(zv))) + if n == 0 { + return fmt.Errorf("selected series contain no samples") + } + + // Sort the axes ascending so the learned map reads like a normal table. + sort.Float64s(mb.xAxis) + sort.Float64s(mb.yAxis) + mb.syncAxisEntries() + + size := mb.cols * mb.rows + sum := make([]float64, size) + cnt := make([]int, size) + used := 0 + skipped := 0 + for i := 0; i < n; i++ { + // Skip rows where any of the three series is missing (NaN padding from + // merging logs with differing channel sets). + if math.IsNaN(xv[i]) || math.IsNaN(yv[i]) || math.IsNaN(zv[i]) { + continue + } + c := nearestIndex(mb.xAxis, xv[i]) + r := nearestIndex(mb.yAxis, yv[i]) + // Reject samples sitting too far from their nearest breakpoint on + // either axis, so only values close to a cell count as a Z-hit. + if !withinTolerance(mb.xAxis, c, xv[i], mb.xTolerance) || + !withinTolerance(mb.yAxis, r, yv[i], mb.yTolerance) { + skipped++ + continue + } + idx := r*mb.cols + c + sum[idx] += zv[i] + cnt[idx]++ + used++ + } + + mb.zData = make([]float64, size) + filled := 0 + for i := range sum { + if cnt[i] > 0 { + mb.zData[i] = sum[i] / float64(cnt[i]) + filled++ + } + } + mb.built = true + msg := fmt.Sprintf("Mapped %d samples, %d/%d cells filled", used, filled, size) + if skipped > 0 { + msg += fmt.Sprintf(" (%d skipped by tolerance)", skipped) + } + mb.status.SetText(msg) + return nil +} + +// rebuildDisplay swaps a freshly built mapviewer (grid + 3D mesh) into the +// display area, reflecting the current axes and learned Z data. Before the +// first build it shows the placeholder instead. +func (mb *MatrixBuilder) rebuildDisplay() { + if !mb.built { + mb.display.Objects = []fyne.CanvasObject{mb.placeholder()} + mb.display.Refresh() + return + } + + noop := func([]float64) {} + mv, err := mapviewer.New(&mapviewer.Config{ + Name: mb.zSeries, + XData: mb.xAxis, + YData: mb.yAxis, + ZData: mb.zData, + XPrecision: precisionFor(mb.xAxis), + YPrecision: precisionFor(mb.yAxis), + ZPrecision: precisionFor(mb.zData), + XLabel: mb.xSeries, + YLabel: mb.ySeries, + ZLabel: mb.zSeries, + MeshView: true, + Editable: true, + ColorblindMode: colors.ModeNormal, + // The matrix is in-memory only; editing cells just mutates zData. + SaveECUFunc: noop, + OnUpdateCell: func(int, []float64) {}, + }) + if err != nil { + mb.display.Objects = []fyne.CanvasObject{container.NewCenter(widget.NewLabel(err.Error()))} + mb.display.Refresh() + return + } + mb.display.Objects = []fyne.CanvasObject{mv} + mb.display.Refresh() +} + +// --- log files --- + +func (mb *MatrixBuilder) buildLogSection() fyne.CanvasObject { + mb.logsLabel = widget.NewLabel("No log files loaded") + mb.logsLabel.Wrapping = fyne.TextWrapWord + + addBtn := widget.NewButtonWithIcon("Add log files", theme.FolderOpenIcon(), mb.openLogDialog) + clearBtn := widget.NewButtonWithIcon("Clear", theme.ContentClearIcon(), mb.clearLogs) + + return container.NewVBox( + // widget.NewLabelWithStyle("Log files", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + container.NewGridWithColumns(2, addBtn, clearBtn), + mb.logsLabel, + ) +} + +// openLogDialog shows the native multi-file picker and loads the chosen logs. +// Parsing runs off the UI goroutine behind a progress modal, then the parsed +// series are merged on the UI goroutine. +func (mb *MatrixBuilder) openLogDialog() { + widgets.SelectFiles(func(readers []fyne.URIReadCloser) { + c := fyne.CurrentApp().Driver().CanvasForObject(mb) + if c == nil { + if wins := fyne.CurrentApp().Driver().AllWindows(); len(wins) > 0 { + c = wins[0].Canvas() + } + } + var pm *progressmodal.ProgressModal + if c != nil { + pm = progressmodal.New(c, fmt.Sprintf("Parsing %d log file(s)...", len(readers))) + pm.Show() + } + + go func() { + type parsed struct { + name string + local map[string][]float64 + n int + } + var ok []parsed + var failed int + for _, r := range readers { + name := r.URI().Name() + local, n, err := parseLog(name, r) + r.Close() + if err != nil { + log.Println("matrixbuilder:", err) + failed++ + continue + } + ok = append(ok, parsed{name, local, n}) + } + + fyne.Do(func() { + if pm != nil { + pm.Hide() + } + for _, p := range ok { + mb.mergeLog(p.name, p.local, p.n) + } + mb.rebuildOrder() + mb.refreshSeriesOptions() + mb.refreshLogList() + if failed > 0 { + mb.status.SetText(fmt.Sprintf("Loaded %d file(s), %d failed", len(ok), failed)) + } else { + mb.status.SetText(fmt.Sprintf("Loaded %d file(s), %d records total", len(ok), mb.nrecords)) + } + }) + }() + }, "logfile", "t5l", "t7l", "t8l", "csv", "bpl") +} + +// parseLog reads a single log file into a row-aligned series map, padding gaps +// with NaN. It touches no shared state, so it is safe to call off the UI +// goroutine. +func parseLog(name string, r io.Reader) (map[string][]float64, int, error) { + lf, err := logfile.Open(name, r) + if err != nil { + return nil, 0, err + } + defer lf.Close() + + local := make(map[string][]float64) + n := 0 + for { + rec := lf.Next() + if rec.EOF { + break + } + for k, v := range rec.Values { + if k == "Pgm_status" { + continue + } + arr := local[k] + for len(arr) < n { // back-fill records before this key first appeared + arr = append(arr, math.NaN()) + } + local[k] = append(arr, v) + } + n++ + for k, arr := range local { // forward-fill keys missing from this record + for len(arr) < n { + arr = append(arr, math.NaN()) + } + local[k] = arr + } + } + if n == 0 { + return nil, 0, fmt.Errorf("%s contains no records", name) + } + return local, n, nil +} + +// mergeLog merges a parsed log into the global series set, padding so all +// series stay row-aligned. Must run on the UI goroutine. +func (mb *MatrixBuilder) mergeLog(name string, local map[string][]float64, n int) { + base := mb.nrecords + for k, arr := range local { + cur, ok := mb.values[k] + if !ok { + cur = nanSlice(base) + } + mb.values[k] = append(cur, arr...) + } + for k, cur := range mb.values { + if _, ok := local[k]; !ok { + mb.values[k] = append(cur, nanSlice(n)...) + } + } + mb.nrecords = base + n + mb.loadedFiles = append(mb.loadedFiles, name) +} + +func (mb *MatrixBuilder) clearLogs() { + mb.values = make(map[string][]float64) + mb.order = nil + mb.loadedFiles = nil + mb.nrecords = 0 + mb.built = false + mb.refreshSeriesOptions() + mb.refreshLogList() + mb.rebuildDisplay() + mb.status.SetText("Cleared loaded logs") +} + +// rebuildOrder refreshes the sorted list of available series names. +func (mb *MatrixBuilder) rebuildOrder() { + mb.order = make([]string, 0, len(mb.values)) + for k := range mb.values { + mb.order = append(mb.order, k) + } + sort.Slice(mb.order, func(i, j int) bool { + return strings.ToLower(mb.order[i]) < strings.ToLower(mb.order[j]) + }) +} + +func (mb *MatrixBuilder) refreshSeriesOptions() { + mb.xSel.SetOptions(mb.order) + mb.ySel.SetOptions(mb.order) + mb.zSel.SetOptions(mb.order) +} + +func (mb *MatrixBuilder) refreshLogList() { + if len(mb.loadedFiles) == 0 { + mb.logsLabel.SetText("No log files loaded") + return + } + mb.logsLabel.SetText(fmt.Sprintf("%d file(s), %d records:\n%s", + len(mb.loadedFiles), mb.nrecords, strings.Join(mb.loadedFiles, "\n"))) +} + +func nanSlice(n int) []float64 { + s := make([]float64, n) + for i := range s { + s[i] = math.NaN() + } + return s +} + +// --- presets --- + +func (mb *MatrixBuilder) buildPresetSection() fyne.CanvasObject { + mb.presetSelect = widget.NewSelect(mb.listPresets(), func(name string) { + if name == "" { + return + } + if err := mb.loadPreset(name); err != nil { + mb.status.SetText(err.Error()) + } + }) + mb.presetSelect.PlaceHolder = "Load preset" + + refreshBtn := widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), mb.refreshPresets) + + mb.nameEntry = widget.NewEntry() + mb.nameEntry.SetPlaceHolder("preset name") + saveBtn := widget.NewButtonWithIcon("Save", theme.DocumentSaveIcon(), func() { + name := strings.TrimSpace(mb.nameEntry.Text) + if name == "" { + mb.status.SetText("enter a preset name to save") + return + } + saved, err := mb.savePreset(name) + if err != nil { + mb.status.SetText(err.Error()) + return + } + mb.refreshPresets() + // Reflect the saved name in the picker without re-triggering a load. + mb.presetSelect.Selected = saved + mb.presetSelect.Refresh() + mb.status.SetText("Saved preset " + saved) + }) + + return container.NewVBox( + // widget.NewLabelWithStyle("Presets", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + container.NewBorder(nil, nil, nil, refreshBtn, mb.presetSelect), + container.NewBorder(nil, nil, nil, saveBtn, mb.nameEntry), + ) +} + +// listPresets returns the names (without extension) of the saved presets. +func (mb *MatrixBuilder) listPresets() []string { + path, err := common.GetMatrixBuilderPath() + if err != nil { + return nil + } + files, err := common.ListFilesInPathByExtension(path, ".json") + if err != nil { + return nil + } + names := make([]string, len(files)) + for i, f := range files { + names[i] = strings.TrimSuffix(f, ".json") + } + return names +} + +func (mb *MatrixBuilder) refreshPresets() { + mb.presetSelect.Options = mb.listPresets() + mb.presetSelect.Refresh() +} + +// savePreset writes the current configuration (and learned matrix, if any) to +// name.json in the matrix builder directory. It returns the stored preset name +// (without extension), which may differ from name after sanitization. +func (mb *MatrixBuilder) savePreset(name string) (string, error) { + path, err := common.GetMatrixBuilderPath() + if err != nil { + return "", err + } + p := Preset{ + XSeries: mb.xSeries, + YSeries: mb.ySeries, + ZSeries: mb.zSeries, + Cols: mb.cols, + Rows: mb.rows, + XAxis: mb.xAxis, + YAxis: mb.yAxis, + XTolerance: mb.xTolerance, + YTolerance: mb.yTolerance, + } + b, err := json.MarshalIndent(&p, "", " ") + if err != nil { + return "", err + } + stored := common.SanitizeFilename(name + ".json") + if err := os.WriteFile(filepath.Join(path, stored), b, 0o644); err != nil { + return "", err + } + return strings.TrimSuffix(stored, ".json"), nil +} + +// loadPreset reads name.json and applies it to the builder. +func (mb *MatrixBuilder) loadPreset(name string) error { + path, err := common.GetMatrixBuilderPath() + if err != nil { + return err + } + b, err := os.ReadFile(filepath.Join(path, common.SanitizeFilename(name+".json"))) + if err != nil { + return err + } + var p Preset + if err := json.Unmarshal(b, &p); err != nil { + return fmt.Errorf("failed to decode preset: %w", err) + } + mb.applyPreset(&p) + mb.status.SetText("Loaded preset " + name) + return nil +} + +// applyPreset replaces the current state with the preset's, rebuilding the +// editor and display. +func (mb *MatrixBuilder) applyPreset(p *Preset) { + mb.cols = clamp(p.Cols, minAxis, maxAxis) + mb.rows = clamp(p.Rows, minAxis, maxAxis) + mb.xAxis = resizeAxis(p.XAxis, mb.cols) + mb.yAxis = resizeAxis(p.YAxis, mb.rows) + // A preset carries only settings, so the matrix must be rebuilt after load. + // Clear built first: Slider.SetValue below fires OnChangeEnded, and we must + // not let it re-run analyze() against the half-applied state. + mb.zData = make([]float64, mb.cols*mb.rows) + mb.built = false + + // Older presets predate the tolerance fields and decode to 0; fall back to + // the default (no filtering) rather than rejecting every sample. + mb.xTolerance = toleranceOrDefault(p.XTolerance) + mb.yTolerance = toleranceOrDefault(p.YTolerance) + mb.xTolSlider.SetValue(mb.xTolerance) + mb.yTolSlider.SetValue(mb.yTolerance) + mb.xTolLabel.SetText(tolText(mb.xTolerance)) + mb.yTolLabel.SetText(tolText(mb.yTolerance)) + + // SetText fires OnChanged, which just records the series name (no auto-fill), + // so this is safe and keeps mb.xSeries/etc. in sync. + mb.xSel.SetText(p.XSeries) + mb.ySel.SetText(p.YSeries) + mb.zSel.SetText(p.ZSeries) + + mb.colsLabel.SetText(strconv.Itoa(mb.cols)) + mb.rowsLabel.SetText(strconv.Itoa(mb.rows)) + mb.rebuildAxisEntries() + mb.rebuildDisplay() +} + +// --- helpers --- + +func labeled(label string, obj fyne.CanvasObject) fyne.CanvasObject { + return container.NewBorder(nil, nil, widget.NewLabel(label), nil, obj) +} + +func clamp(v, lo, hi int) int { + if v < lo { + return lo + } + if v > hi { + return hi + } + return v +} + +// resizeAxis grows or shrinks an axis to length n, preserving existing values +// and padding new slots with the previous value (or zero for an empty axis). +func resizeAxis(old []float64, n int) []float64 { + out := make([]float64, n) + copy(out, old) + for i := len(old); i < n; i++ { + if i > 0 { + out[i] = out[i-1] + } + } + return out +} + +// withinTolerance reports whether v is close enough to breakpoint c on a sorted +// axis to count as a hit. tolPct is the allowed distance expressed as a +// percentage of the half-spacing to the neighbouring breakpoint on v's side, so +// 100% accepts the entire nearest-neighbor region and lower values reject +// samples sitting near the cell boundary. Edge cells use their inner spacing as +// the reference, so a sample far beyond the axis range is rejected too. +func withinTolerance(axis []float64, c int, v, tolPct float64) bool { + if tolPct >= 100 || len(axis) < 2 { + return true + } + bp := axis[c] + var spacing float64 + if v >= bp { // neighbour on the high side, falling back to the low side + switch { + case c+1 < len(axis): + spacing = axis[c+1] - bp + case c-1 >= 0: + spacing = bp - axis[c-1] + } + } else { // neighbour on the low side, falling back to the high side + switch { + case c-1 >= 0: + spacing = bp - axis[c-1] + case c+1 < len(axis): + spacing = axis[c+1] - bp + } + } + if spacing <= 0 { // duplicate/degenerate breakpoints: nothing to gate on + return true + } + maxDist := tolPct / 100.0 * (spacing / 2.0) + return math.Abs(v-bp) <= maxDist +} + +// toleranceOrDefault clamps a stored tolerance into the valid range, mapping the +// zero value (older presets without the field) to the default. +func toleranceOrDefault(v float64) float64 { + if v < minTolerance { + return defaultTolerance + } + if v > 100 { + return 100 + } + return v +} + +func tolText(v float64) string { + return strconv.Itoa(int(v)) + "%" +} + +// nearestIndex returns the index of the axis breakpoint closest to v. +func nearestIndex(axis []float64, v float64) int { + best := 0 + bestDist := math.Abs(axis[0] - v) + for i := 1; i < len(axis); i++ { + d := math.Abs(axis[i] - v) + if d < bestDist { + bestDist = d + best = i + } + } + return best +} + +func minMax(data []float64) (float64, float64) { + lo, hi := data[0], data[0] + for _, v := range data[1:] { + if v < lo { + lo = v + } + if v > hi { + hi = v + } + } + return lo, hi +} + +// precisionFor picks a sensible decimal precision: 0 for all-integer data, +// otherwise more decimals for small-magnitude values. +func precisionFor(data []float64) int { + allInt := true + maxAbs := 0.0 + for _, v := range data { + if v != math.Trunc(v) { + allInt = false + } + if a := math.Abs(v); a > maxAbs { + maxAbs = a + } + } + if allInt { + return 0 + } + if maxAbs < 10 { + return 3 + } + return 2 +} + +func formatFloat(v float64) string { + return strconv.FormatFloat(v, 'f', -1, 64) +} diff --git a/pkg/widgets/plotter/plotter.go b/pkg/widgets/plotter/plotter.go index 4439f4bb..bc15e00c 100644 --- a/pkg/widgets/plotter/plotter.go +++ b/pkg/widgets/plotter/plotter.go @@ -220,13 +220,11 @@ func NewPlotter(values map[string][]float64, opts ...PlotterOpt) *Plotter { leading, container.NewBorder( nil, - container.NewGridWithColumns(1, - widget.NewButton("Toggle visible", func() { - for _, ts := range p.legendTexts { - ts.Tapped(&fyne.PointEvent{}) - } - }), - ), + widget.NewButton("Toggle visible", func() { + for _, ts := range p.legendTexts { + ts.Tapped(&fyne.PointEvent{}) + } + }), nil, nil, container.NewVScroll(p.legend), diff --git a/pkg/widgets/widget.go b/pkg/widgets/widget.go index 7b3d68bf..32cd4171 100644 --- a/pkg/widgets/widget.go +++ b/pkg/widgets/widget.go @@ -48,6 +48,33 @@ func SelectFile(callback func(r fyne.URIReadCloser), desc string, exts ...string }() } +func SelectFiles(callback func(rc []fyne.URIReadCloser), desc string, exts ...string) { + go func() { + filenames, err := selectFiles(desc, exts...) + if err != nil { + if errors.Is(err, native.ErrCancelled) || err.Error() == "Cancelled" { + return + } + log.Println("Error selecting files:", err) + return + } + readers := make([]fyne.URIReadCloser, 0, len(filenames)) + for _, filename := range filenames { + uri := storage.NewFileURI(filename) + r, err := storage.Reader(uri) + if err != nil { + log.Println("Error reading file:", err) + continue + } + readers = append(readers, r) + } + if len(readers) == 0 { + return + } + fyne.Do(func() { callback(readers) }) + }() +} + func SaveFile(callback func(str string), desc string, ext string) { go func() { //filter := native.FileFilter{Description: desc, Extensions: []string{ext}} diff --git a/pkg/widgets/widget_linux.go b/pkg/widgets/widget_linux.go index 0899b382..c5ac771b 100644 --- a/pkg/widgets/widget_linux.go +++ b/pkg/widgets/widget_linux.go @@ -14,6 +14,14 @@ func selectFile(desc string, exts ...string) (string, error) { return runChild("open_file", "Open "+desc, desc, exts...) } +func selectFiles(desc string, exts ...string) ([]string, error) { + resp, err := runChildResp("open_files", "Open "+desc, desc, exts...) + if err != nil { + return nil, err + } + return resp.Paths, nil +} + func saveFile(desc string, ext string) (string, error) { return runChild("save_file", "Save "+desc, desc, ext) } @@ -23,6 +31,14 @@ func selectFolder() (string, error) { } func runChild(op, title, desc string, exts ...string) (string, error) { + resp, err := runChildResp(op, title, desc, exts...) + if err != nil { + return resp.Path, err + } + return resp.Path, nil +} + +func runChildResp(op, title, desc string, exts ...string) (native.FileResponse, error) { child := exec.Command("/proc/self/exe") // re-exec self child.Env = append(os.Environ(), "FP=1") childIn, _ := child.StdinPipe() @@ -31,7 +47,7 @@ func runChild(op, title, desc string, exts ...string) (string, error) { defer childIn.Close() if err := child.Start(); err != nil { - return "", fmt.Errorf("failed to start child: %w\n", err) + return native.FileResponse{}, fmt.Errorf("failed to start child: %w\n", err) } enc := json.NewEncoder(childIn) @@ -44,7 +60,7 @@ func runChild(op, title, desc string, exts ...string) (string, error) { Exts: exts, } if err := enc.Encode(req); err != nil { - return "", fmt.Errorf("error decoding response: %w", err) + return native.FileResponse{}, fmt.Errorf("error decoding response: %w", err) } var resp native.FileResponse @@ -53,12 +69,12 @@ func runChild(op, title, desc string, exts ...string) (string, error) { waitErr := child.Wait() if decodeErr != nil { - return "", decodeErr + return native.FileResponse{}, decodeErr } if resp.Err != "" { - return resp.Path, errors.New(resp.Err) + return resp, errors.New(resp.Err) } - return resp.Path, waitErr + return resp, waitErr } diff --git a/pkg/widgets/widget_windows.go b/pkg/widgets/widget_windows.go index 82adaac2..af76dd50 100644 --- a/pkg/widgets/widget_windows.go +++ b/pkg/widgets/widget_windows.go @@ -7,6 +7,11 @@ func selectFile(desc string, exts ...string) (string, error) { return native.OpenFileDialog("Open file", filter) } +func selectFiles(desc string, exts ...string) ([]string, error) { + filter := native.FileFilter{Description: desc, Extensions: exts} + return native.OpenFilesDialog("Open files", filter) +} + func saveFile(desc string, ext string) (string, error) { return native.SaveFileDialog("Save "+desc, ext, native.FileFilter{ Description: desc, diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index 483a4861..f4c91367 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -174,6 +174,44 @@ func (mw *MainWindow) setupMenu() { }, } + openItem := fyne.NewMenuItemWithIcon("Open", theme.FolderIcon(), nil) + openItem.ChildMenu = fyne.NewMenu("File", + fyne.NewMenuItemWithIcon("Open binary", theme.DocumentIcon(), mw.loadBinary), + fyne.NewMenuItemWithIcon("Open log", theme.DocumentIcon(), func() { + cb := func(r fyne.URIReadCloser) { + defer r.Close() + filename := r.URI().Name() + mw.Log("opening logfile " + filename) + sz := mw.Window.Content().Size() + p := fyne.NewPos(sz.Width/2, sz.Height/2) + mw.LoadLogfile(filename, r, p) + } + widgets.SelectFile(cb, "Log file", "csv", "bpl", "t5l", "t7l", "t8l") + }), + fyne.NewMenuItemWithIcon("Open log in new window", theme.DocumentIcon(), func() { + cb := func(r fyne.URIReadCloser) { + defer r.Close() + filename := r.URI().Path() + mw.LoadLogfileCombined(filename, r, fyne.Position{}, true) + } + widgets.SelectFile(cb, "logfile", "t5l", "t7l", "t8l", "csv", "bpl") + }), + fyne.NewMenuItemWithIcon("Open log folder", theme.FolderIcon(), func() { + var cmd *exec.Cmd + switch runtime.GOOS { + case "windows": + cmd = exec.Command("explorer", mw.settings.GetLogPath()) + case "darwin": + cmd = exec.Command("open", mw.settings.GetLogPath()) + default: + cmd = exec.Command("xdg-open", mw.settings.GetLogPath()) + } + if err := cmd.Start(); err != nil { + mw.Error(err) + } + }), + ) + leading := []*fyne.Menu{ fyne.NewMenu("File", fyne.NewMenuItemWithIcon("About", theme.HelpIcon(), func() { @@ -185,40 +223,7 @@ func (mw *MainWindow) setupMenu() { inner.Icon = theme.HelpIcon() mw.wm.Add(inner) }), - fyne.NewMenuItemWithIcon("Open binary", theme.DocumentIcon(), mw.loadBinary), - fyne.NewMenuItemWithIcon("Open log", theme.DocumentIcon(), func() { - cb := func(r fyne.URIReadCloser) { - defer r.Close() - filename := r.URI().Name() - mw.Log("opening logfile " + filename) - sz := mw.Window.Content().Size() - p := fyne.NewPos(sz.Width/2, sz.Height/2) - mw.LoadLogfile(filename, r, p) - } - widgets.SelectFile(cb, "Log file", "csv", "t5l", "t7l", "t8l") - }), - fyne.NewMenuItemWithIcon("Open log in new window", theme.DocumentIcon(), func() { - cb := func(r fyne.URIReadCloser) { - defer r.Close() - filename := r.URI().Path() - mw.LoadLogfileCombined(filename, r, fyne.Position{}, true) - } - widgets.SelectFile(cb, "logfile", "t5l", "t7l", "t8l", "csv") - }), - fyne.NewMenuItemWithIcon("Open log folder", theme.FolderIcon(), func() { - var cmd *exec.Cmd - switch runtime.GOOS { - case "windows": - cmd = exec.Command("explorer", mw.settings.GetLogPath()) - case "darwin": - cmd = exec.Command("open", mw.settings.GetLogPath()) - default: - cmd = exec.Command("xdg-open", mw.settings.GetLogPath()) - } - if err := cmd.Start(); err != nil { - mw.Error(err) - } - }), + openItem, fyne.NewMenuItemWithIcon("Settings", theme.SettingsIcon(), func() { mw.openSettings() }), diff --git a/pkg/windows/mainWindow_toolbar.go b/pkg/windows/mainWindow_toolbar.go index 022424e7..24cefa6c 100644 --- a/pkg/windows/mainWindow_toolbar.go +++ b/pkg/windows/mainWindow_toolbar.go @@ -6,9 +6,23 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/roffe/txlogger/pkg/widgets/canflasher" + "github.com/roffe/txlogger/pkg/widgets/matrixbuilder" "github.com/roffe/txlogger/pkg/widgets/multiwindow" ) +// openMatrixBuilder opens (or raises) the matrix builder window. The builder +// loads its own log files, so it is independent of any open log player. +func (mw *MainWindow) openMatrixBuilder() { + if w := mw.wm.HasWindow("Matrix builder"); w != nil { + mw.wm.Raise(w) + return + } + inner := multiwindow.NewInnerWindow("Matrix builder", matrixbuilder.New()) + inner.Icon = theme.GridIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(1000, 720)) +} + func (mw *MainWindow) newToolbar() *fyne.Container { toolbar := container.NewHBox( container.NewBorder( @@ -22,6 +36,7 @@ func (mw *MainWindow) newToolbar() *fyne.Container { mw.buttons.symbolListBtn, mw.buttons.logBtn, mw.buttons.dashboardBtn, + widget.NewButtonWithIcon("Matrix", theme.GridIcon(), mw.openMatrixBuilder), widget.NewButtonWithIcon("", theme.GridIcon(), func() { mw.wm.Arrange(&multiwindow.GridArranger{}) }), From 2e22b3b26555088a790a7d980aed49483061f224 Mon Sep 17 00:00:00 2001 From: roffe Date: Tue, 16 Jun 2026 23:13:33 +0200 Subject: [PATCH 050/102] matrixbuilder and meshgrid updates --- pkg/assets/WHATSNEW.md | 9 +- pkg/widgets/mapviewer/mapviewer_mouse.go | 8 + pkg/widgets/matrixbuilder/filter_test.go | 135 ++++ pkg/widgets/matrixbuilder/matrixbuilder.go | 699 ++++++++++++++++++- pkg/widgets/matrixbuilder/query.go | 420 +++++++++++ pkg/widgets/matrixbuilder/query_test.go | 139 ++++ pkg/widgets/meshgrid/meshgrid_draw.go | 55 +- pkg/widgets/meshgrid/meshgrid_shader.go | 184 +++-- pkg/widgets/meshgrid/meshgrid_shader_test.go | 34 +- pkg/widgets/meshgrid/meshgrid_surface.go | 77 +- pkg/windows/mainWindow_toolbar.go | 2 +- 11 files changed, 1645 insertions(+), 117 deletions(-) create mode 100644 pkg/widgets/matrixbuilder/filter_test.go create mode 100644 pkg/widgets/matrixbuilder/query.go create mode 100644 pkg/widgets/matrixbuilder/query_test.go diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index e78a686b..23f33c9f 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -20,7 +20,14 @@ - Dial and Dual Dial now uses shaders to draw the faces, dials and pips - Improved cell selection in mapviewer - Improved copy paste in mapviewer, added paste here function -- Added a Matrix builder from logfiles +- Added a Matrix builder from logfiles. It learns a 2D map from one or more logs: pick which series drives the X axis, the Y axis and supplies the Z value, and every sample that lands on a cell is averaged into it. The result is shown live in a mapviewer (colored grid + 3D mesh) and the cells can be edited by hand + - Load and merge multiple log files at once (t5l, t7l, t8l, csv, bpl); series are row-aligned across files + - Pick X/Y/Z from a dropdown of the loaded series or type a name by hand + - Adjustable column/row counts and fully editable axis breakpoints, with an "Auto" button that spreads a series' min..max evenly across an axis + - Per-axis Z-hit tolerance sliders: reject samples that sit too far from a breakpoint so only values close to a cell count toward it + - Visual filter / query builder: add rules like "if " and a sample only counts as a hit when it satisfies every rule. Operators: >, >=, <, <=, ==, != and ~ (approximately equal) + - Filter query language: instead of the visual rules you can type a full query with and/or, () grouping and the same operators, e.g. "if (ActualIn.n_Engine > 3000 and Out.X_AccPedal > 50) or boost ~ 1.2". Series can be compared to numbers, to each other or to arithmetic of them; a non-empty query overrides the rules + - Save and load configurations as presets (series, dimensions, axis breakpoints, tolerances and filter rules) # 2.1.9 - Updated default T7 preset to include MAF.m_AirFromp_AirInlet diff --git a/pkg/widgets/mapviewer/mapviewer_mouse.go b/pkg/widgets/mapviewer/mapviewer_mouse.go index bb7fda1e..3982ac46 100644 --- a/pkg/widgets/mapviewer/mapviewer_mouse.go +++ b/pkg/widgets/mapviewer/mapviewer_mouse.go @@ -183,6 +183,14 @@ func (mv *MapViewer) calculateSelectionBounds(eventPos fyne.Position) (int, int) // handleFocusAndInputBuffer focuses the MapViewer and clears the input buffer if necessary. func (mv *MapViewer) handleFocusAndInputBuffer() { + // Take keyboard focus so the key handler (cell editing, increment/decrement, + // arrow-key navigation) receives events. The multiwindow manager focuses a + // map when its inner window is raised, but a MapViewer placed directly in a + // container (e.g. the matrix builder) is never focused otherwise, so clicking + // a cell would leave key presses with nowhere to go. + if c := fyne.CurrentApp().Driver().CanvasForObject(mv); c != nil { + c.Focus(mv) + } if mv.inputBuffer.Len() > 0 { mv.inputBuffer.Reset() mv.restoreSelectedValues() diff --git a/pkg/widgets/matrixbuilder/filter_test.go b/pkg/widgets/matrixbuilder/filter_test.go new file mode 100644 index 00000000..5e074495 --- /dev/null +++ b/pkg/widgets/matrixbuilder/filter_test.go @@ -0,0 +1,135 @@ +package matrixbuilder + +import "testing" + +// leaf builds a comparison condition node. +func leaf(series, op string, value float64) *FilterNode { + return &FilterNode{Series: series, Operator: op, Value: value} +} + +func TestFilterNodeToQuery(t *testing.T) { + tests := []struct { + name string + node *FilterNode + want string // empty means ok=false (nothing to contribute) + }{ + { + "single leaf, no outer parens at root", + &FilterNode{Combinator: "and", Children: []*FilterNode{leaf("rpm", ">", 3000)}}, + "rpm > 3000", + }, + { + "two anded at root", + &FilterNode{Combinator: "and", Children: []*FilterNode{ + leaf("rpm", ">", 3000), leaf("load", ">", 50), + }}, + "rpm > 3000 and load > 50", + }, + { + "nested group keeps its parens", + &FilterNode{Combinator: "or", Children: []*FilterNode{ + {Combinator: "and", Children: []*FilterNode{leaf("rpm", ">", 3000), leaf("load", ">", 50)}}, + {Series: "ECMStat.ST_ActiveAirDem", Operator: "in", Values: []float64{10, 20}}, + }}, + "(rpm > 3000 and load > 50) or ECMStat.ST_ActiveAirDem in [10, 20]", + }, + { + "negated group at root", + &FilterNode{Combinator: "and", Negate: true, Children: []*FilterNode{ + leaf("rpm", ">", 3000), leaf("load", ">", 50), + }}, + "not (rpm > 3000 and load > 50)", + }, + { + "negated leaf", + &FilterNode{Combinator: "and", Children: []*FilterNode{ + {Series: "rpm", Operator: ">", Value: 3000, Negate: true}, + }}, + "not (rpm > 3000)", + }, + { + "incomplete leaves are dropped", + &FilterNode{Combinator: "and", Children: []*FilterNode{ + leaf("rpm", ">", 3000), + {Series: "", Operator: ">", Value: 1}, // no series + {Series: "x", Operator: "in", Values: nil}, // empty list + }}, + "rpm > 3000", + }, + {"empty group contributes nothing", &FilterNode{Combinator: "and"}, ""}, + { + "group with only incomplete children contributes nothing", + &FilterNode{Combinator: "and", Children: []*FilterNode{{Series: ""}}}, + "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := tt.node.toQuery(true) + if tt.want == "" { + if ok { + t.Fatalf("toQuery() = %q, want ok=false", got) + } + return + } + if !ok { + t.Fatalf("toQuery() ok=false, want %q", tt.want) + } + if got != tt.want { + t.Errorf("toQuery() = %q, want %q", got, tt.want) + } + // Whatever the builder emits must be a valid query. + if _, err := compileQuery(got, resolverFrom(nil)); err != nil { + t.Errorf("compileQuery(%q) error: %v", got, err) + } + }) + } +} + +// The builder is just a structured query author, so a tree and its hand-written +// query equivalent must filter identically. +func TestFilterNodeMatchesQuery(t *testing.T) { + series := map[string][]float64{ + "rpm": {1000, 4000, 4000, 4000}, + "load": {10, 60, 40, 60}, + "state": {10, 20, 30, 10}, + } + resolve := resolverFrom(series) + + // (rpm > 3000 and load > 50) or state in [10, 20] + tree := &FilterNode{Combinator: "or", Children: []*FilterNode{ + {Combinator: "and", Children: []*FilterNode{leaf("rpm", ">", 3000), leaf("load", ">", 50)}}, + {Series: "state", Operator: "in", Values: []float64{10, 20}}, + }} + + src, ok := tree.toQuery(true) + if !ok { + t.Fatal("toQuery() ok=false") + } + q, err := compileQuery(src, resolve) + if err != nil { + t.Fatalf("compileQuery(%q) error: %v", src, err) + } + want := [4]bool{true, true, false, true} // row0 state=10; row1 rpm&load; row3 state=10 + for i := 0; i < 4; i++ { + if got := q.Eval(i); got != want[i] { + t.Errorf("Eval(%d) = %v, want %v (query %q)", i, got, want[i], src) + } + } +} + +func TestRulesToNodeMigration(t *testing.T) { + rules := []Rule{ + {Series: "rpm", Operator: ">", Threshold: 3000}, + {Series: "load", Operator: "<=", Threshold: 80}, + } + got, ok := rulesToNode(rules).toQuery(true) + if !ok { + t.Fatal("toQuery() ok=false") + } + want := "rpm > 3000 and load <= 80" + if got != want { + t.Errorf("migrated query = %q, want %q", got, want) + } +} diff --git a/pkg/widgets/matrixbuilder/matrixbuilder.go b/pkg/widgets/matrixbuilder/matrixbuilder.go index 32e58984..07c79982 100644 --- a/pkg/widgets/matrixbuilder/matrixbuilder.go +++ b/pkg/widgets/matrixbuilder/matrixbuilder.go @@ -31,6 +31,7 @@ import ( "github.com/roffe/txlogger/pkg/logfile" "github.com/roffe/txlogger/pkg/widgets" "github.com/roffe/txlogger/pkg/widgets/mapviewer" + "github.com/roffe/txlogger/pkg/widgets/meshgrid" "github.com/roffe/txlogger/pkg/widgets/progressmodal" ) @@ -53,6 +54,7 @@ var _ fyne.Widget = (*MatrixBuilder)(nil) type MatrixBuilder struct { widget.BaseWidget + renderMode meshgrid.RenderBackend // values holds every series merged across all loaded log files. All series // share the same length (nrecords); gaps are padded with NaN so samples @@ -91,9 +93,57 @@ type MatrixBuilder struct { nameEntry *widget.Entry display *fyne.Container + // Filter tree: a nested group/condition builder. rootGroup is the live editor + // state (read into a FilterNode tree on demand); filterHolder is the stable + // container the root group is swapped into when a preset is loaded. + rootGroup *filterGroup + filterHolder *fyne.Container + + // Filter query: an optional text query in the matrix builder query language. + // When non-empty it overrides the visual rules above (see currentFilter). + queryEntry *widget.Entry + queryStatus *widget.Label + + // controls is the scrolling left/right column that holds the rule editor. + // Adding or removing rule rows only re-lays-out the rules box itself, so we + // refresh this ancestor to reposition everything below it (see + // refreshControls). + controls *fyne.Container + content fyne.CanvasObject } +// filterChild is one editable node in the visual filter tree: either a group or +// a leaf condition. node reads the widgets into a FilterNode (returning nil for +// an incomplete leaf); object is the canvas object the parent group lays out. +type filterChild interface { + node() *FilterNode + object() fyne.CanvasObject +} + +// filterGroup is a group node in the editor: a combinator (ALL/ANY), an optional +// "not", and an ordered list of child conditions and sub-groups. +type filterGroup struct { + mb *MatrixBuilder + parent *filterGroup // nil for the root group + combinator *widget.Select + negate *widget.Check + children []filterChild + inner *fyne.Container // holds the children plus the add-buttons footer + footer fyne.CanvasObject + container *fyne.Container +} + +// filterCond is a leaf condition in the editor: " ", where +// is a single number, or a comma-separated list when is "in". +type filterCond struct { + parent *filterGroup + seriesSel *widget.SelectEntry + opSel *widget.Select + value *widget.Entry + container *fyne.Container +} + // Preset is the on-disk representation of a matrix builder configuration. It // holds only settings (series, dimensions and axis breakpoints); the learned // matrix values are never stored. @@ -109,17 +159,147 @@ type Preset struct { // which decode to 0 and are treated as the default (no filtering) on load. XTolerance float64 `json:"x_tolerance,omitempty"` YTolerance float64 `json:"y_tolerance,omitempty"` + // Filter is the root of the visual builder's group/condition tree. Omitted in + // presets saved before the tree existed; those carry Rules instead. + Filter *FilterNode `json:"filter,omitempty"` + // Rules is the legacy flat filter list. Read-only now: still decoded for + // backward compatibility (migrated into an implicit ALL-of group on load), but + // no longer written. Older presets that predate filters decode to nil. + Rules []Rule `json:"rules,omitempty"` + // Query is an optional filter written in the query language. When non-empty + // it overrides the builder (matching the live behaviour). Omitted in older + // presets. + Query string `json:"query,omitempty"` +} + +// FilterNode is one node of the visual builder's filter tree, persisted in a +// preset. It is either a group (Series empty: combines Children with Combinator, +// optionally negated) or a leaf condition (Series set: " +// ", or " in " when Operator is "in"). +type FilterNode struct { + Combinator string `json:"combinator,omitempty"` // group: "and" | "or" + Negate bool `json:"negate,omitempty"` // wrap the node in not(...) + Children []*FilterNode `json:"children,omitempty"` // group members + + Series string `json:"series,omitempty"` // leaf series name + Operator string `json:"operator,omitempty"` // leaf comparison operator + Value float64 `json:"value,omitempty"` // leaf threshold (non-"in" ops) + Values []float64 `json:"values,omitempty"` // leaf membership list ("in" op) +} + +// isGroup reports whether n is a group rather than a leaf. A leaf always names a +// series; a group never does. +func (n *FilterNode) isGroup() bool { return n.Series == "" } + +// toQuery renders n as a query-language fragment, returning ok=false when the +// node contributes nothing (an incomplete leaf, or a group with no usable +// children). top suppresses the redundant outer parentheses on the root group. +func (n *FilterNode) toQuery(top bool) (string, bool) { + if n.isGroup() { + parts := make([]string, 0, len(n.Children)) + for _, c := range n.Children { + if s, ok := c.toQuery(false); ok { + parts = append(parts, s) + } + } + if len(parts) == 0 { + return "", false + } + joiner := " and " + if n.Combinator == "or" { + joiner = " or " + } + s := strings.Join(parts, joiner) + if (len(parts) > 1 && !top) || n.Negate { + s = "(" + s + ")" + } + if n.Negate { + s = "not " + s + } + return s, true + } + + if strings.TrimSpace(n.Series) == "" { + return "", false + } + var s string + if n.Operator == "in" { + if len(n.Values) == 0 { + return "", false + } + members := make([]string, len(n.Values)) + for i, v := range n.Values { + members[i] = formatFloat(v) + } + s = fmt.Sprintf("%s in [%s]", n.Series, strings.Join(members, ", ")) + } else { + op := n.Operator + if op == "" { + op = condOperators[0] + } + s = fmt.Sprintf("%s %s %s", n.Series, op, formatFloat(n.Value)) + } + if n.Negate { + s = "not (" + s + ")" + } + return s, true +} + +// rulesToNode migrates a legacy flat rule list into an implicit ALL-of group. +func rulesToNode(rules []Rule) *FilterNode { + root := &FilterNode{Combinator: "and"} + for _, r := range rules { + root.Children = append(root.Children, &FilterNode{ + Series: r.Series, Operator: r.Operator, Value: r.Threshold, + }) + } + return root } +// Rule is a single filter condition. A sample is only counted as a Z-hit when it +// satisfies every active rule: the named series' value at that sample, compared +// against Threshold with Operator, must hold. +type Rule struct { + Series string `json:"series"` + Operator string `json:"operator"` // one of ">", ">=", "<", "<=", "==", "!=", "~" + Threshold float64 `json:"threshold"` +} + +// condOperators lists the operators offered in a condition row, in display +// order. The first entry is the default for a new condition. "~" matches values +// approximately equal to the value (see ruleEpsilonFrac); "in" tests membership +// of a comma-separated list. +var condOperators = []string{">", ">=", "<", "<=", "==", "!=", "~", "in"} + +// combinator labels for a group, mapping to the query language's and/or. +const ( + combinatorAll = "ALL of" // every child must hold -> "and" + combinatorAny = "ANY of" // any child may hold -> "or" +) + +var combinatorOptions = []string{combinatorAll, combinatorAny} + +const ( + // ruleEpsilonFrac sets the half-width of the "~" (approximately-equal) + // operator as a fraction of the threshold's magnitude, so the window scales + // with the value being matched (e.g. 1% of an RPM target vs. 1% of a lambda + // target). + ruleEpsilonFrac = 0.01 + // ruleEpsilonMin is a small absolute floor so "~" stays usable when the + // threshold is at or near zero (where a relative window would collapse to 0). + ruleEpsilonMin = 1e-6 +) + // New creates an empty MatrixBuilder. Log files are loaded from within the // widget via a native file dialog. -func New() *MatrixBuilder { +func New(renderMode meshgrid.RenderBackend) *MatrixBuilder { mb := &MatrixBuilder{ values: make(map[string][]float64), cols: defaultCols, rows: defaultRows, xTolerance: defaultTolerance, yTolerance: defaultTolerance, + renderMode: renderMode, } mb.ExtendBaseWidget(mb) mb.xAxis = make([]float64, mb.cols) @@ -183,7 +363,7 @@ func (mb *MatrixBuilder) buildUI() { mb.yBox = container.NewHBox() mb.rebuildAxisEntries() - controls := container.NewVBox( + mb.controls = container.NewVBox( // widget.NewLabelWithStyle("Series", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), colsRow, rowsRow, @@ -194,19 +374,24 @@ func (mb *MatrixBuilder) buildUI() { widget.NewSeparator(), mb.buildToleranceSection(), widget.NewSeparator(), - buildBtn, - widget.NewSeparator(), - mb.status, - widget.NewSeparator(), + mb.buildFilterSection(), xlayout.NewSpacer(), - mb.buildPresetSection(), + mb.status, ) - left := container.NewVScroll(controls) - left.SetMinSize(fyne.NewSize(240, 0)) + controlScroll := container.NewVScroll(mb.controls) + controlScroll.SetMinSize(fyne.NewSize(240, 0)) - right := container.NewVScroll( - mb.buildLogSection(), + logList := container.NewBorder( + nil, + container.NewVBox( + mb.buildPresetSection(), + ), + nil, + nil, + container.NewVScroll( + mb.buildLogSection(), + ), ) mb.display = container.NewStack(mb.placeholder()) @@ -219,14 +404,33 @@ func (mb *MatrixBuilder) buildUI() { container.NewHScroll(mb.yBox), ) - mb.content = container.NewBorder( - nil, - nil, - right, - left, - container.NewBorder(nil, yPanel, container.NewVBox(widget.NewLabelWithStyle("X axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - mb.xBox), nil, mb.display), + mainSplit := container.NewHSplit( + container.NewBorder( + yPanel, + buildBtn, + container.NewVBox( + widget.NewLabelWithStyle("X axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + mb.xBox, + ), + nil, + mb.display, + ), + logList, ) + mainSplit.Offset = 0.9 + + /* + mb.content = container.NewBorder( + nil, + nil, + controlScroll, + nil, + mainSplit, + ) + */ + split := container.NewHSplit(controlScroll, mainSplit) + split.Offset = 0.25 + mb.content = split } func (mb *MatrixBuilder) placeholder() fyne.CanvasObject { @@ -282,6 +486,400 @@ func (mb *MatrixBuilder) newToleranceSlider(isX bool) *widget.Slider { return s } +// --- filter tree --- + +// buildFilterSection builds the visual query builder: a nestable tree of groups +// (ALL-of / ANY-of, optionally negated) and leaf conditions, plus a free-text +// query box that overrides the tree when non-empty. +func (mb *MatrixBuilder) buildFilterSection() fyne.CanvasObject { + mb.rootGroup = mb.newFilterGroup(nil, &FilterNode{Combinator: "and"}) + mb.filterHolder = container.NewVBox(mb.rootGroup.object()) + + // The query box accepts the matrix builder query language: comparisons + // (> >= < <= == != ~), the "in [...]" membership test, joined with and/or/not + // and grouped with (). A leading "if" is optional. When non-empty it overrides + // the visual builder above. + mb.queryEntry = widget.NewMultiLineEntry() + mb.queryEntry.SetMinRowsVisible(2) + mb.queryEntry.Wrapping = fyne.TextWrapWord + mb.queryEntry.SetPlaceHolder("e.g. (ActualIn.n_Engine > 3000 and Out.X_AccPedal > 50) or ECMStat.ST_ActiveAirDem in [10, 20]") + mb.queryEntry.OnChanged = func(string) { mb.validateQuery() } + + mb.queryStatus = widget.NewLabel("") + mb.queryStatus.Wrapping = fyne.TextWrapWord + + fromTreeBtn := widget.NewButton("Builder->Query", func() { + if s, ok := mb.filterTree().toQuery(true); ok { + mb.queryEntry.SetText(s) + } else { + mb.queryEntry.SetText("") + } + }) + fromTreeBtn.Importance = widget.LowImportance + + return container.NewVBox( + widget.NewLabelWithStyle("Filters (count a hit when…)", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + mb.filterHolder, + widget.NewSeparator(), + widget.NewLabel("…or a query (overrides the builder when not empty):"), + mb.queryEntry, + container.NewBorder(nil, nil, nil, fromTreeBtn, mb.queryStatus), + ) +} + +// validateQuery parses the query box live and reports its status: a syntax +// error, any referenced series that aren't loaded, or "ok". An empty box defers +// to the visual builder. +func (mb *MatrixBuilder) validateQuery() { + src := strings.TrimSpace(mb.queryEntry.Text) + if src == "" { + mb.queryStatus.SetText("Empty — using the builder above") + return + } + q, err := compileQuery(src, mb.resolve) + if err != nil { + mb.queryStatus.SetText("⚠ " + err.Error()) + return + } + // Only warn about unknown series once logs are loaded; before that every + // name is "unknown" and the noise isn't helpful. + if len(mb.values) > 0 { + var unknown []string + for _, s := range q.Series() { + if _, ok := mb.values[s]; !ok { + unknown = append(unknown, s) + } + } + if len(unknown) > 0 { + mb.queryStatus.SetText("⚠ unknown series: " + strings.Join(unknown, ", ")) + return + } + } + mb.queryStatus.SetText("✓ query active") +} + +// filterTree reads the live editor into a FilterNode tree (the root group). +func (mb *MatrixBuilder) filterTree() *FilterNode { return mb.rootGroup.node() } + +// setFilterTree replaces the editor with a fresh tree built from model, swapping +// the new root group into the stable holder. A nil or non-group model becomes an +// empty (or single-condition) ALL-of root so there is always a group to edit. +func (mb *MatrixBuilder) setFilterTree(model *FilterNode) { + switch { + case model == nil: + model = &FilterNode{Combinator: "and"} + case !model.isGroup(): + model = &FilterNode{Combinator: "and", Children: []*FilterNode{model}} + } + mb.rootGroup = mb.newFilterGroup(nil, model) + mb.filterHolder.Objects = []fyne.CanvasObject{mb.rootGroup.object()} + mb.filterHolder.Refresh() + mb.refreshControls() +} + +// refreshControls re-lays-out the scrolling controls column after the filter +// tree changes shape. A nested Add/Remove only re-runs the affected group's own +// layout, leaving its ancestors (and everything below the filter section) at +// their old positions until the column is refreshed. +func (mb *MatrixBuilder) refreshControls() { + if mb.controls != nil { + mb.controls.Refresh() + } +} + +// indented wraps obj with a left margin so nested groups read as nested. +func indented(obj fyne.CanvasObject) fyne.CanvasObject { + return container.NewBorder(nil, nil, layout.NewFixedWidth(14, xlayout.NewSpacer()), nil, obj) +} + +// --- group node --- + +// newFilterGroup builds a group widget seeded from model, recursively creating +// its child conditions and sub-groups. parent is nil for the root group. +func (mb *MatrixBuilder) newFilterGroup(parent *filterGroup, model *FilterNode) *filterGroup { + g := &filterGroup{mb: mb, parent: parent} + + g.combinator = widget.NewSelect(combinatorOptions, func(string) {}) + if model != nil && model.Combinator == "or" { + g.combinator.SetSelected(combinatorAny) + } else { + g.combinator.SetSelected(combinatorAll) + } + + g.negate = widget.NewCheck("not", nil) + if model != nil { + g.negate.SetChecked(model.Negate) + } + + addCond := widget.NewButton("+ condition", func() { g.addChild(mb.newFilterCond(g, nil)) }) + addCond.Importance = widget.LowImportance + addGroup := widget.NewButton("+ group", func() { g.addChild(mb.newFilterGroup(g, &FilterNode{Combinator: "and"})) }) + addGroup.Importance = widget.LowImportance + g.footer = container.NewHBox(addCond, addGroup) + + header := container.NewHBox(layout.NewFixedWidth(96, g.combinator), g.negate) + var headerRow fyne.CanvasObject = header + if parent != nil { + remove := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() { parent.removeChild(g) }) + remove.Importance = widget.LowImportance + headerRow = container.NewBorder(nil, nil, nil, remove, header) + } + + g.inner = container.NewVBox() + g.container = container.NewVBox(headerRow, indented(g.inner)) + + if model != nil { + for _, ch := range model.Children { + if ch.isGroup() { + g.children = append(g.children, mb.newFilterGroup(g, ch)) + } else { + g.children = append(g.children, mb.newFilterCond(g, ch)) + } + } + } + g.rebuild() + return g +} + +func (g *filterGroup) object() fyne.CanvasObject { return g.container } + +// node reads the group (and, recursively, its children) into a FilterNode, +// dropping incomplete leaves. A group always yields a node, even when empty, so +// the tree's shape survives a save/load round-trip. +func (g *filterGroup) node() *FilterNode { + combinator := "and" + if g.combinator.Selected == combinatorAny { + combinator = "or" + } + n := &FilterNode{Combinator: combinator, Negate: g.negate.Checked} + for _, c := range g.children { + if cn := c.node(); cn != nil { + n.Children = append(n.Children, cn) + } + } + return n +} + +// addChild appends a new condition or sub-group and re-lays-out the group. +func (g *filterGroup) addChild(c filterChild) { + g.children = append(g.children, c) + g.rebuild() +} + +// removeChild drops c from the group and re-lays-out. +func (g *filterGroup) removeChild(c filterChild) { + for i, child := range g.children { + if child == c { + g.children = append(g.children[:i], g.children[i+1:]...) + break + } + } + g.rebuild() +} + +// rebuild repopulates the group's inner box with its children followed by the +// add-buttons footer, then refreshes the surrounding controls column. +func (g *filterGroup) rebuild() { + g.inner.Objects = g.inner.Objects[:0] + for _, c := range g.children { + g.inner.Add(c.object()) + } + g.inner.Add(g.footer) + g.inner.Refresh() + g.mb.refreshControls() +} + +// eachCond visits every leaf condition in the group's subtree, in order. +func (g *filterGroup) eachCond(fn func(*filterCond)) { + for _, c := range g.children { + switch v := c.(type) { + case *filterCond: + fn(v) + case *filterGroup: + v.eachCond(fn) + } + } +} + +// --- condition node --- + +// newFilterCond builds a leaf-condition widget seeded from model (nil for a +// fresh, empty condition). +func (mb *MatrixBuilder) newFilterCond(parent *filterGroup, model *FilterNode) *filterCond { + c := &filterCond{parent: parent} + + c.seriesSel = widget.NewSelectEntry(mb.order) + c.seriesSel.PlaceHolder = "Series" + + c.value = widget.NewEntry() + // The value field doubles as a list when "in" is selected, so its hint tracks + // the operator. + c.opSel = widget.NewSelect(condOperators, func(op string) { + c.value.SetPlaceHolder(valuePlaceholder(op)) + }) + + op := condOperators[0] + if model != nil && model.Operator != "" { + op = model.Operator + } + c.opSel.SetSelected(op) + c.value.SetPlaceHolder(valuePlaceholder(op)) + + if model != nil { + c.seriesSel.SetText(model.Series) + if model.Operator == "in" { + c.value.SetText(formatList(model.Values)) + } else if model.Series != "" { + c.value.SetText(formatFloat(model.Value)) + } + } + + remove := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() { parent.removeChild(c) }) + remove.Importance = widget.LowImportance + + // Series stretches to fill; operator/value/remove are pinned to the right at + // fixed widths so the narrow panel stays readable. The operator box must fit + // the two-char operators plus the dropdown arrow or the Select truncates to "…". + c.container = container.NewBorder(nil, nil, nil, + container.NewHBox( + layout.NewFixedWidth(72, c.opSel), + layout.NewFixedWidth(72, c.value), + remove, + ), + c.seriesSel, + ) + return c +} + +func (c *filterCond) object() fyne.CanvasObject { return c.container } + +// node reads the condition into a FilterNode, returning nil when it is +// incomplete: no series, an empty/invalid value for a comparison, or an empty +// list for "in". Incomplete conditions are dropped rather than matched. +func (c *filterCond) node() *FilterNode { + series := strings.TrimSpace(c.seriesSel.Text) + if series == "" { + return nil + } + op := c.opSel.Selected + if op == "" { + op = condOperators[0] + } + n := &FilterNode{Series: series, Operator: op} + if op == "in" { + n.Values = parseList(c.value.Text) + if len(n.Values) == 0 { + return nil + } + return n + } + v, ok := parseFloatLoose(c.value.Text) + if !ok { + return nil + } + n.Value = v + return n +} + +// valuePlaceholder hints the value field's content for the selected operator. +func valuePlaceholder(op string) string { + if op == "in" { + return "10, 20, …" + } + return "Value" +} + +// formatList renders a membership list as comma-separated numbers. +func formatList(values []float64) string { + parts := make([]string, len(values)) + for i, v := range values { + parts[i] = formatFloat(v) + } + return strings.Join(parts, ", ") +} + +// parseFloatLoose parses a single value, accepting a comma as the decimal point +// (European keyboards). It reports ok=false for blank or unparseable input. +func parseFloatLoose(s string) (float64, bool) { + s = strings.TrimSpace(s) + if s == "" { + return 0, false + } + v, err := strconv.ParseFloat(strings.ReplaceAll(s, ",", "."), 64) + if err != nil { + return 0, false + } + return v, true +} + +// parseList parses a comma-separated membership list, skipping blank or +// unparseable members. Here a comma separates values, so it is not treated as a +// decimal point. +func parseList(s string) []float64 { + var out []float64 + for _, part := range strings.Split(s, ",") { + part = strings.TrimSpace(part) + if part == "" { + continue + } + if v, err := strconv.ParseFloat(part, 64); err == nil { + out = append(out, v) + } + } + return out +} + +// approxEqual reports whether v is approximately equal to threshold, using a +// window that scales with the threshold's magnitude (see ruleEpsilonFrac) with a +// small absolute floor (ruleEpsilonMin) so it stays usable near zero. Shared by +// the per-row "~" rule and the query language's "~" operator. +func approxEqual(v, threshold float64) bool { + eps := math.Abs(threshold) * ruleEpsilonFrac + if eps < ruleEpsilonMin { + eps = ruleEpsilonMin + } + return math.Abs(v-threshold) <= eps +} + +// resolve returns series' value at sample i, reporting ok=false when the series +// is absent or its reading is NaN. It is the bridge the compiled query uses to +// read log data. +func (mb *MatrixBuilder) resolve(series string, i int) (float64, bool) { + data, ok := mb.values[series] + if !ok || i < 0 || i >= len(data) { + return 0, false + } + v := data[i] + if math.IsNaN(v) { + return 0, false + } + return v, true +} + +// currentFilter returns the predicate that decides whether a sample counts as a +// hit. A non-empty query box takes precedence over the visual builder; +// otherwise the builder's tree is compiled to a query (an empty tree accepts +// every sample). A query that fails to compile is returned as an error so Build +// surfaces it instead of silently filtering nothing. +func (mb *MatrixBuilder) currentFilter() (func(i int) bool, error) { + src := strings.TrimSpace(mb.queryEntry.Text) + if src == "" { + // The builder is just a structured way to author a query, so compile it + // through the same path rather than maintaining a second evaluator. + if s, ok := mb.filterTree().toQuery(true); ok { + src = s + } + } + if src == "" { + return func(int) bool { return true }, nil + } + q, err := compileQuery(src, mb.resolve) + if err != nil { + return nil, fmt.Errorf("query: %w", err) + } + return q.Eval, nil +} + // rebuildAxisEntries regenerates the editable entry fields for the current // column/row counts, seeding them from the current axis values. func (mb *MatrixBuilder) rebuildAxisEntries() { @@ -296,6 +894,7 @@ func (mb *MatrixBuilder) rebuildAxisEntries() { mb.yBox.Objects = mb.yBox.Objects[:0] for i := 0; i < mb.rows; i++ { mb.yBox.Add(mb.makeAxisEntry(false, i)) + // mb.yBox.Add(xlayout.NewSpacer()) } mb.yBox.Refresh() } @@ -334,7 +933,7 @@ func (mb *MatrixBuilder) makeAxisEntry(isX bool, idx int) fyne.CanvasObject { mb.yEntries[idx] = e // Y breakpoints run horizontally along the bottom: label above a // fixed-width entry so the strip stays compact. - label := widget.NewLabelWithStyle(prefix+strconv.Itoa(idx), fyne.TextAlignCenter, fyne.TextStyle{}) + label := widget.NewLabelWithStyle(prefix+strconv.Itoa(idx), fyne.TextAlignLeading, fyne.TextStyle{}) return container.NewVBox(label, layout.NewFixedWidth(64, e)) } @@ -375,6 +974,7 @@ func (mb *MatrixBuilder) autoFill(isX bool) { series = mb.xSeries axis = mb.xAxis } + data, ok := mb.values[series] if !ok || len(data) == 0 { return @@ -437,17 +1037,29 @@ func (mb *MatrixBuilder) analyze() error { sort.Float64s(mb.yAxis) mb.syncAxisEntries() + filter, err := mb.currentFilter() + if err != nil { + return err + } + size := mb.cols * mb.rows sum := make([]float64, size) cnt := make([]int, size) used := 0 skipped := 0 + filtered := 0 for i := 0; i < n; i++ { // Skip rows where any of the three series is missing (NaN padding from // merging logs with differing channel sets). if math.IsNaN(xv[i]) || math.IsNaN(yv[i]) || math.IsNaN(zv[i]) { continue } + // Drop samples that fail any user-defined filter rule before they can + // contribute to a cell. + if !filter(i) { + filtered++ + continue + } c := nearestIndex(mb.xAxis, xv[i]) r := nearestIndex(mb.yAxis, yv[i]) // Reject samples sitting too far from their nearest breakpoint on @@ -476,6 +1088,9 @@ func (mb *MatrixBuilder) analyze() error { if skipped > 0 { msg += fmt.Sprintf(" (%d skipped by tolerance)", skipped) } + if filtered > 0 { + msg += fmt.Sprintf(" (%d filtered)", filtered) + } mb.status.SetText(msg) return nil } @@ -503,6 +1118,7 @@ func (mb *MatrixBuilder) rebuildDisplay() { YLabel: mb.ySeries, ZLabel: mb.zSeries, MeshView: true, + MeshRenderer: mb.renderMode, Editable: true, ColorblindMode: colors.ModeNormal, // The matrix is in-memory only; editing cells just mutates zData. @@ -522,7 +1138,7 @@ func (mb *MatrixBuilder) rebuildDisplay() { func (mb *MatrixBuilder) buildLogSection() fyne.CanvasObject { mb.logsLabel = widget.NewLabel("No log files loaded") - mb.logsLabel.Wrapping = fyne.TextWrapWord + mb.logsLabel.Truncation = fyne.TextTruncateEllipsis addBtn := widget.NewButtonWithIcon("Add log files", theme.FolderOpenIcon(), mb.openLogDialog) clearBtn := widget.NewButtonWithIcon("Clear", theme.ContentClearIcon(), mb.clearLogs) @@ -679,6 +1295,13 @@ func (mb *MatrixBuilder) refreshSeriesOptions() { mb.xSel.SetOptions(mb.order) mb.ySel.SetOptions(mb.order) mb.zSel.SetOptions(mb.order) + mb.rootGroup.eachCond(func(c *filterCond) { + c.seriesSel.SetOptions(mb.order) + }) + // Loaded series changed, so the query's unknown-series check may now differ. + if mb.queryEntry != nil { + mb.validateQuery() + } } func (mb *MatrixBuilder) refreshLogList() { @@ -687,7 +1310,25 @@ func (mb *MatrixBuilder) refreshLogList() { return } mb.logsLabel.SetText(fmt.Sprintf("%d file(s), %d records:\n%s", - len(mb.loadedFiles), mb.nrecords, strings.Join(mb.loadedFiles, "\n"))) + len(mb.loadedFiles), mb.nrecords, buildFilenamesList(mb.loadedFiles))) +} + +func buildFilenamesList(files []string) string { + if len(files) == 0 { + return "" + } + var b strings.Builder + for i, f := range files { + if i > 0 { + b.WriteString("\n") + } + base := filepath.Base(f) + //if len(base) > 25 { + // base = base[:25] + "…" + //} + b.WriteString(base) + } + return b.String() } func nanSlice(n int) []float64 { @@ -714,11 +1355,11 @@ func (mb *MatrixBuilder) buildPresetSection() fyne.CanvasObject { refreshBtn := widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), mb.refreshPresets) mb.nameEntry = widget.NewEntry() - mb.nameEntry.SetPlaceHolder("preset name") + mb.nameEntry.SetPlaceHolder("Preset name") saveBtn := widget.NewButtonWithIcon("Save", theme.DocumentSaveIcon(), func() { name := strings.TrimSpace(mb.nameEntry.Text) if name == "" { - mb.status.SetText("enter a preset name to save") + mb.status.SetText("Enter a preset name to save") return } saved, err := mb.savePreset(name) @@ -780,6 +1421,8 @@ func (mb *MatrixBuilder) savePreset(name string) (string, error) { YAxis: mb.yAxis, XTolerance: mb.xTolerance, YTolerance: mb.yTolerance, + Filter: mb.filterTree(), + Query: strings.TrimSpace(mb.queryEntry.Text), } b, err := json.MarshalIndent(&p, "", " ") if err != nil { @@ -839,6 +1482,16 @@ func (mb *MatrixBuilder) applyPreset(p *Preset) { mb.ySel.SetText(p.YSeries) mb.zSel.SetText(p.ZSeries) + // Replace the filter tree with the preset's, falling back to the legacy flat + // rules for presets saved before the tree existed. Restoring the query box + // fires OnChanged, which re-runs validateQuery. + root := p.Filter + if root == nil && len(p.Rules) > 0 { + root = rulesToNode(p.Rules) + } + mb.setFilterTree(root) + mb.queryEntry.SetText(p.Query) + mb.colsLabel.SetText(strconv.Itoa(mb.cols)) mb.rowsLabel.SetText(strconv.Itoa(mb.rows)) mb.rebuildAxisEntries() diff --git a/pkg/widgets/matrixbuilder/query.go b/pkg/widgets/matrixbuilder/query.go new file mode 100644 index 00000000..ebf2f310 --- /dev/null +++ b/pkg/widgets/matrixbuilder/query.go @@ -0,0 +1,420 @@ +package matrixbuilder + +// This file implements the matrix builder's filter query language. Rather than +// hand-rolling a lexer and parser we lean on Go's own expression parser +// (go/parser) and walk the resulting syntax tree (go/ast). The query grammar is +// deliberately a subset of Go expressions, so once we normalise a few +// human-friendly tokens into their Go equivalents the standard library does the +// hard work of tokenising, applying operator precedence and handling () +// grouping for us. We then "compile" the AST into a tree of small closures that +// can be evaluated cheaply once per log sample. +// +// Normalisation maps the bits that aren't valid Go onto bits that are: +// +// if (rpm > 3000 and load > 50) or boost ~ 1.2 +// (rpm > 3000 && load > 50) || __approx(boost, 1.2) +// +// - a leading "if" is stripped (it reads nicely but carries no meaning) +// - the keywords "and"/"or"/"not" become "&&"/"||"/"!" +// - the approximately-equal operator "a ~ b" becomes a call "__approx(a, b)" +// - the membership test "a in [x, y, z]" becomes a call "__in(a, x, y, z)" +// +// Series names are written verbatim. A bare name (m_Request) parses as an +// *ast.Ident; a dotted name (ActualIn.n_Engine) parses as an *ast.SelectorExpr, +// which we flatten back into the original dotted string. + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "regexp" + "strconv" + "strings" +) + +// boolNode evaluates a condition for the sample at index i. +type boolNode func(i int) bool + +// numNode evaluates a value (a series reading, a literal or arithmetic of them) +// for the sample at index i. ok is false when a referenced series is missing or +// NaN at that sample, which makes every comparison using it fail — matching the +// "drop incomplete samples" behaviour of the older per-row rules. +type numNode func(i int) (float64, bool) + +// queryFilter is a compiled query. Eval reports whether a sample passes; Series +// lists every series the query references, so the UI can warn about names that +// aren't present in the loaded logs. +type queryFilter struct { + eval boolNode + series []string +} + +func (q *queryFilter) Eval(i int) bool { return q.eval(i) } +func (q *queryFilter) Series() []string { return q.series } + +var ( + // A leading "if " (any case) is cosmetic and dropped. \b keeps "iffy" intact. + leadingIfRe = regexp.MustCompile(`(?i)^if\b\s*`) + // "a ~ b" -> "__approx(a, b)". Both operands are simple atoms: a series name + // (optionally dotted) or a number (optionally negative/decimal). This runs + // before the keyword swaps so the operands are still in their raw form. + approxRe = regexp.MustCompile(`([A-Za-z_][\w.]*|-?\d[\d.]*)\s*~\s*(-?\d[\d.]*|[A-Za-z_][\w.]*)`) + // "a in [x, y, z]" -> "__in(a, x, y, z)". The left operand is a simple atom + // (a series name or number); the bracketed list is captured verbatim and + // dropped in as the remaining call args, so go/parser tokenises the members. + // Runs before the keyword swaps so the operand is still in its raw form. + inRe = regexp.MustCompile(`(?i)([A-Za-z_][\w.]*|-?\d[\d.]*)\s+in\s*\[([^\]]*)\]`) + // Keyword operators -> Go operators. \b stops us from rewriting these letters + // when they appear inside a series name (e.g. "Sensor" keeps its "or"). + andRe = regexp.MustCompile(`(?i)\band\b`) + orRe = regexp.MustCompile(`(?i)\bor\b`) + notRe = regexp.MustCompile(`(?i)\bnot\b`) + // go/parser prefixes messages with a "line:col: " position into our + // normalised string, which is meaningless to the user; strip it. + parserPosRe = regexp.MustCompile(`^\d+:\d+:\s*`) +) + +// normalizeQuery rewrites the human-friendly query into a valid Go expression +// string that go/parser can handle. +func normalizeQuery(src string) string { + s := strings.TrimSpace(src) + if s == "" { + return "" + } + s = leadingIfRe.ReplaceAllString(s, "") + s = approxRe.ReplaceAllString(s, "__approx($1, $2)") + s = inRe.ReplaceAllString(s, "__in($1, $2)") + s = andRe.ReplaceAllString(s, "&&") + s = orRe.ReplaceAllString(s, "||") + s = notRe.ReplaceAllString(s, "!") + return strings.TrimSpace(s) +} + +// compileQuery parses and compiles src into a queryFilter. resolve supplies a +// series' value at a sample (returning ok=false for a missing/NaN reading). +// Unknown series are not an error here — they simply never pass — so a query +// can be validated before any logs are loaded. +func compileQuery(src string, resolve func(series string, i int) (float64, bool)) (*queryFilter, error) { + norm := normalizeQuery(src) + if norm == "" { + return nil, fmt.Errorf("empty query") + } + expr, err := parser.ParseExpr(norm) + if err != nil { + return nil, fmt.Errorf("syntax error: %s", cleanParseError(err)) + } + c := &queryCompiler{resolve: resolve, seen: make(map[string]bool)} + node, err := c.compileBool(expr) + if err != nil { + return nil, err + } + return &queryFilter{eval: node, series: c.series}, nil +} + +// queryCompiler walks the AST, building closures and collecting referenced +// series names along the way. +type queryCompiler struct { + resolve func(series string, i int) (float64, bool) + seen map[string]bool + series []string +} + +// compileBool compiles an expression that must yield a boolean: a comparison, an +// approx() call, a && / || combination, a ! negation, or any of those grouped in +// parentheses. +func (c *queryCompiler) compileBool(e ast.Expr) (boolNode, error) { + switch v := e.(type) { + case *ast.ParenExpr: + return c.compileBool(v.X) + case *ast.UnaryExpr: + if v.Op != token.NOT { + return nil, fmt.Errorf("%q can't start a condition", v.Op) + } + inner, err := c.compileBool(v.X) + if err != nil { + return nil, err + } + return func(i int) bool { return !inner(i) }, nil + case *ast.CallExpr: + if id, ok := v.Fun.(*ast.Ident); ok && id.Name == "__in" { + return c.compileIn(v) + } + return c.compileApprox(v) + case *ast.BinaryExpr: + switch v.Op { + case token.LAND: + return c.combine(v, func(a, b bool) bool { return a && b }) + case token.LOR: + return c.combine(v, func(a, b bool) bool { return a || b }) + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return c.compileCompare(v) + default: + return nil, fmt.Errorf("operator %q can't join conditions; use and/or", v.Op) + } + } + return nil, fmt.Errorf("expected a condition (e.g. rpm > 3000), got %s", exprString(e)) +} + +// combine compiles both sides of a && / || node and merges them with op. +func (c *queryCompiler) combine(v *ast.BinaryExpr, op func(a, b bool) bool) (boolNode, error) { + l, err := c.compileBool(v.X) + if err != nil { + return nil, err + } + r, err := c.compileBool(v.Y) + if err != nil { + return nil, err + } + return func(i int) bool { return op(l(i), r(i)) }, nil +} + +// compileCompare compiles a comparison "value op value". If either side is +// missing/NaN at a sample the comparison is false. +func (c *queryCompiler) compileCompare(v *ast.BinaryExpr) (boolNode, error) { + l, err := c.compileNum(v.X) + if err != nil { + return nil, err + } + r, err := c.compileNum(v.Y) + if err != nil { + return nil, err + } + op := v.Op + return func(i int) bool { + a, ok := l(i) + if !ok { + return false + } + b, ok := r(i) + if !ok { + return false + } + switch op { + case token.EQL: + return a == b + case token.NEQ: + return a != b + case token.LSS: + return a < b + case token.LEQ: + return a <= b + case token.GTR: + return a > b + case token.GEQ: + return a >= b + } + return false + }, nil +} + +// compileApprox compiles the synthesised __approx(a, b) call that backs the "~" +// operator, reusing the same epsilon window as the per-row "~" rule. +func (c *queryCompiler) compileApprox(call *ast.CallExpr) (boolNode, error) { + id, ok := call.Fun.(*ast.Ident) + if !ok || id.Name != "__approx" { + return nil, fmt.Errorf("unknown function %s(...)", exprString(call.Fun)) + } + if len(call.Args) != 2 { + return nil, fmt.Errorf("~ needs a value on each side") + } + l, err := c.compileNum(call.Args[0]) + if err != nil { + return nil, err + } + r, err := c.compileNum(call.Args[1]) + if err != nil { + return nil, err + } + return func(i int) bool { + a, ok := l(i) + if !ok { + return false + } + b, ok := r(i) + if !ok { + return false + } + return approxEqual(a, b) + }, nil +} + +// compileIn compiles the synthesised __in(value, a, b, ...) call that backs the +// "in [...]" membership test. It passes when value equals any list member. A +// missing/NaN value (or list member) is skipped, matching the "drop incomplete +// samples" behaviour of every other comparison. +func (c *queryCompiler) compileIn(call *ast.CallExpr) (boolNode, error) { + if len(call.Args) < 2 { + return nil, fmt.Errorf("in needs a value and a non-empty list, e.g. rpm in [800, 900]") + } + val, err := c.compileNum(call.Args[0]) + if err != nil { + return nil, err + } + set := make([]numNode, 0, len(call.Args)-1) + for _, arg := range call.Args[1:] { + member, err := c.compileNum(arg) + if err != nil { + return nil, err + } + set = append(set, member) + } + return func(i int) bool { + a, ok := val(i) + if !ok { + return false + } + for _, member := range set { + if b, ok := member(i); ok && a == b { + return true + } + } + return false + }, nil +} + +// compileNum compiles an expression that must yield a number: a literal, a +// series reference, arithmetic of those, or any of them grouped/negated. +func (c *queryCompiler) compileNum(e ast.Expr) (numNode, error) { + switch v := e.(type) { + case *ast.ParenExpr: + return c.compileNum(v.X) + case *ast.BasicLit: + if v.Kind != token.INT && v.Kind != token.FLOAT { + return nil, fmt.Errorf("expected a number, got %s", v.Value) + } + f, err := strconv.ParseFloat(v.Value, 64) + if err != nil { + return nil, fmt.Errorf("bad number %q", v.Value) + } + return func(int) (float64, bool) { return f, true }, nil + case *ast.Ident: + return c.seriesNode(v.Name), nil + case *ast.SelectorExpr: + name, ok := dottedName(v) + if !ok { + return nil, fmt.Errorf("unsupported series reference %s", exprString(v)) + } + return c.seriesNode(name), nil + case *ast.UnaryExpr: + switch v.Op { + case token.SUB: + inner, err := c.compileNum(v.X) + if err != nil { + return nil, err + } + return func(i int) (float64, bool) { x, ok := inner(i); return -x, ok }, nil + case token.ADD: + return c.compileNum(v.X) + case token.NOT: + // "!" binds tighter than comparison, so "not rpm > 3000" parses as + // "(not rpm) > 3000". Point the user at the parenthesised form. + return nil, fmt.Errorf("not negates a condition — wrap it, e.g. not (rpm > 3000)") + } + return nil, fmt.Errorf("%q can't be applied to a value", v.Op) + case *ast.BinaryExpr: + return c.compileArith(v) + } + return nil, fmt.Errorf("expected a series name or number, got %s", exprString(e)) +} + +// compileArith compiles +, -, * and / between two values. Division by zero +// yields ok=false rather than an infinity. +func (c *queryCompiler) compileArith(v *ast.BinaryExpr) (numNode, error) { + op := v.Op + switch op { + case token.ADD, token.SUB, token.MUL, token.QUO: + default: + return nil, fmt.Errorf("operator %q can't be used in a value", op) + } + l, err := c.compileNum(v.X) + if err != nil { + return nil, err + } + r, err := c.compileNum(v.Y) + if err != nil { + return nil, err + } + return func(i int) (float64, bool) { + a, ok := l(i) + if !ok { + return 0, false + } + b, ok := r(i) + if !ok { + return 0, false + } + switch op { + case token.ADD: + return a + b, true + case token.SUB: + return a - b, true + case token.MUL: + return a * b, true + case token.QUO: + if b == 0 { + return 0, false + } + return a / b, true + } + return 0, false + }, nil +} + +// seriesNode builds a value node that reads the named series at a sample, and +// records the name as referenced (deduplicated, in first-seen order). +func (c *queryCompiler) seriesNode(name string) numNode { + if !c.seen[name] { + c.seen[name] = true + c.series = append(c.series, name) + } + resolve := c.resolve + return func(i int) (float64, bool) { return resolve(name, i) } +} + +// dottedName flattens a selector chain (a.b.c) back into its original dotted +// string. Only plain identifiers may appear in the chain. +func dottedName(e ast.Expr) (string, bool) { + switch v := e.(type) { + case *ast.Ident: + return v.Name, true + case *ast.SelectorExpr: + base, ok := dottedName(v.X) + if !ok { + return "", false + } + return base + "." + v.Sel.Name, true + } + return "", false +} + +// exprString renders a small, friendly fragment of an expression for error +// messages, covering just the node kinds this grammar can produce. +func exprString(e ast.Expr) string { + switch v := e.(type) { + case *ast.Ident: + return v.Name + case *ast.BasicLit: + return v.Value + case *ast.SelectorExpr: + if n, ok := dottedName(v); ok { + return n + } + return "selector" + case *ast.ParenExpr: + return "(" + exprString(v.X) + ")" + case *ast.UnaryExpr: + return v.Op.String() + exprString(v.X) + case *ast.BinaryExpr: + return exprString(v.X) + " " + v.Op.String() + " " + exprString(v.Y) + case *ast.CallExpr: + return exprString(v.Fun) + "(...)" + } + return "expression" +} + +// cleanParseError trims go/parser's "line:col: " position prefix, which refers +// to our normalised string and would only confuse the user. +func cleanParseError(err error) string { + return parserPosRe.ReplaceAllString(err.Error(), "") +} diff --git a/pkg/widgets/matrixbuilder/query_test.go b/pkg/widgets/matrixbuilder/query_test.go new file mode 100644 index 00000000..c61f9d3b --- /dev/null +++ b/pkg/widgets/matrixbuilder/query_test.go @@ -0,0 +1,139 @@ +package matrixbuilder + +import ( + "math" + "testing" +) + +// resolverFrom builds a query resolver over an in-memory series map, mirroring +// MatrixBuilder.resolve (missing series or a NaN reading fail with ok=false). +func resolverFrom(series map[string][]float64) func(string, int) (float64, bool) { + return func(name string, i int) (float64, bool) { + data, ok := series[name] + if !ok || i < 0 || i >= len(data) { + return 0, false + } + v := data[i] + if math.IsNaN(v) { + return 0, false + } + return v, true + } +} + +func TestCompileQueryEval(t *testing.T) { + // One sample per row; index i selects the row under test. + series := map[string][]float64{ + "rpm": {1000, 4000, 4000, 4000, math.NaN()}, + "load": {10, 60, 40, 60, 60}, + "boost": {0.5, 1.2, 1.205, 0.8, 1.2}, + "ActualIn.n_Engine": {1000, 4000, 4000, 4000, 4000}, + } + resolve := resolverFrom(series) + + tests := []struct { + name string + query string + want [5]bool + }{ + {"simple gt", "rpm > 3000", [5]bool{false, true, true, true, false}}, + {"and", "rpm > 3000 and load > 50", [5]bool{false, true, false, true, false}}, + // Row 4 has rpm=NaN (so rpm<2000 fails) but load=60>50, so the OR holds. + {"or", "rpm < 2000 or load > 50", [5]bool{true, true, false, true, true}}, + { + // Grouping changes the result vs. default precedence (&& binds tighter). + "grouping", "(rpm == 4000 and load == 40) or rpm == 1000", + [5]bool{true, false, true, false, false}, + }, + { + "precedence no parens", "rpm == 1000 or rpm == 4000 and load == 40", + [5]bool{true, false, true, false, false}, + }, + {"if prefix stripped", "if rpm > 3000", [5]bool{false, true, true, true, false}}, + {"not parenthesized", "not (rpm > 3000)", [5]bool{true, false, false, false, true}}, + {"approx hit", "boost ~ 1.2", [5]bool{false, true, true, false, true}}, + {"ne", "load != 60", [5]bool{true, false, true, false, false}}, + {"dotted series", "ActualIn.n_Engine >= 4000", [5]bool{false, true, true, true, true}}, + // load = {10,60,40,60,60}; only the 40 and the three 60s are members. + {"in set", "load in [40, 60]", [5]bool{false, true, true, true, true}}, + {"in single", "load in [10]", [5]bool{true, false, false, false, false}}, + // Dotted name on the left, float members, combined with another clause. + {"in dotted and", "ActualIn.n_Engine in [1000, 4000] and load < 50", [5]bool{true, false, true, false, false}}, + // not wrapping an in-test; row 4's rpm=NaN value fails the test, so not-> true. + {"not in", "not (rpm in [1000, 4000])", [5]bool{false, false, false, false, true}}, + {"series vs series", "rpm > load", [5]bool{true, true, true, true, false}}, + // rpm/100 = {10,40,40,40,NaN}; load = {10,60,40,60,60}. Row 2 ties (40>40 + // false) and row 4's NaN rpm makes the value fail. + {"arithmetic rhs", "load > rpm / 100", [5]bool{false, true, false, true, false}}, + {"nan fails", "rpm == 4000", [5]bool{false, true, true, true, false}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + q, err := compileQuery(tt.query, resolve) + if err != nil { + t.Fatalf("compileQuery(%q) error: %v", tt.query, err) + } + for i := 0; i < 5; i++ { + if got := q.Eval(i); got != tt.want[i] { + t.Errorf("Eval(%d) = %v, want %v", i, got, tt.want[i]) + } + } + }) + } +} + +func TestCompileQuerySeries(t *testing.T) { + q, err := compileQuery("rpm > 3000 and ActualIn.n_Engine < 6000 or rpm == 0", resolverFrom(nil)) + if err != nil { + t.Fatalf("compileQuery error: %v", err) + } + got := q.Series() + want := []string{"rpm", "ActualIn.n_Engine"} // first-seen order, deduped + if len(got) != len(want) { + t.Fatalf("Series() = %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Errorf("Series()[%d] = %q, want %q", i, got[i], want[i]) + } + } +} + +func TestCompileQueryErrors(t *testing.T) { + resolve := resolverFrom(nil) + for _, query := range []string{ + "", // empty + "rpm >", // dangling operator + "rpm", // not a condition + "rpm + 10", // value, not a condition + "rpm > 3000 and", // dangling and + "(rpm > 3000", // unbalanced paren + "rpm in []", // empty membership list + } { + if _, err := compileQuery(query, resolve); err == nil { + t.Errorf("compileQuery(%q) expected error, got nil", query) + } + } +} + +func TestNormalizeQuery(t *testing.T) { + tests := map[string]string{ + "if rpm > 3000": "rpm > 3000", + "a and b or c": "a && b || c", + "not (a > 1)": "! (a > 1)", + "boost ~ 1.2": "__approx(boost, 1.2)", + "In.p ~ 1.0 and rpm > 100": "__approx(In.p, 1.0) && rpm > 100", + "load in [40, 60]": "__in(load, 40, 60)", + "rpm in [800] or load > 5": "__in(rpm, 800) || load > 5", + // "in" inside a name must survive (no leading whitespace before "in"). + "MainInput > 1": "MainInput > 1", + // "and"/"or" inside names must survive. + "Sensor > 1 and Brand < 2": "Sensor > 1 && Brand < 2", + } + for in, want := range tests { + if got := normalizeQuery(in); got != want { + t.Errorf("normalizeQuery(%q) = %q, want %q", in, got, want) + } + } +} diff --git a/pkg/widgets/meshgrid/meshgrid_draw.go b/pkg/widgets/meshgrid/meshgrid_draw.go index 49abf247..df6a660b 100644 --- a/pkg/widgets/meshgrid/meshgrid_draw.go +++ b/pkg/widgets/meshgrid/meshgrid_draw.go @@ -156,13 +156,30 @@ func (m *Meshgrid) drawMeshgridLines() *image.RGBA { } // cursorScreenPosition projects the tracking-marker cell position set by -// SetCursor onto the screen. The camera transform is linear, so bilinearly -// interpolating the transformed vertex coordinates lands on the same point -// as transforming the interpolated one. +// SetCursor onto the screen so the marker rides the surface the shader draws. func (m *Meshgrid) cursorScreenPosition() (float32, float32) { - // MapViewer indices are cell-centered while mesh vertices sit on cell - // corners; +0.5 lands the marker mid-cell on the corner grid. SetCursor - // clamps the indices, so sx/sy stay within [0.5, cols-0.5]/[0.5, rows-0.5]. + if m.dataVertexMode() { + // The shader's vertices are the cell values themselves, so the marker + // rides the triangulated data surface: project the cell-centered data + // point at the (fractional) cursor, with the same Ox/Oy convention the + // axis uses. SetCursor clamps the indices to the data grid. + cw, ch := float64(m.cellWidth), float64(m.cellHeight) + ox := (m.cursorX + 0.5) * cw + oy := (float64(m.rows) + 0.5 - m.cursorY) * ch + zr := m.zrange + if zr == 0 { + zr = 1 + } + oz := (m.sampleValue(m.cursorX, m.cursorY) - m.zmin) / zr * m.depth + sx, sy, _ := m.projectOriginal(ox, oy, oz) + return sx, sy + } + + // Corner-averaged fallback: MapViewer indices are cell-centered while mesh + // vertices sit on cell corners; +0.5 lands the marker mid-cell on the corner + // grid. The camera transform is linear, so bilinearly interpolating the + // transformed corners lands on the same point as transforming the + // interpolated one. sx := m.cursorX + 0.5 sy := m.cursorY + 0.5 @@ -184,6 +201,32 @@ func (m *Meshgrid) cursorScreenPosition() (float32, float32) { return float32(float64(m.size.Width)*0.5 + vx), float32(float64(m.size.Height)*0.5 + vy) } +// sampleValue bilinearly interpolates the cell values at the fractional cell +// index (fx = column, fy = row), clamped to the data grid. +func (m *Meshgrid) sampleValue(fx, fy float64) float64 { + cx0 := min(max(int(math.Floor(fx)), 0), m.cols-1) + cy0 := min(max(int(math.Floor(fy)), 0), m.rows-1) + cx1 := min(cx0+1, m.cols-1) + cy1 := min(cy0+1, m.rows-1) + tx := fx - float64(cx0) + if tx < 0 { + tx = 0 + } else if tx > 1 { + tx = 1 + } + ty := fy - float64(cy0) + if ty < 0 { + ty = 0 + } else if ty > 1 { + ty = 1 + } + v00 := m.values[cy0*m.cols+cx0] + v01 := m.values[cy0*m.cols+cx1] + v10 := m.values[cy1*m.cols+cx0] + v11 := m.values[cy1*m.cols+cx1] + return (1-ty)*((1-tx)*v00+tx*v01) + ty*((1-tx)*v10+tx*v11) +} + // getColorWithDepth combines color interpolation and depth enhancement in one step func (m *Meshgrid) getColorWithDepth(value, depthFactor float64) color.RGBA { // Get base color from value diff --git a/pkg/widgets/meshgrid/meshgrid_shader.go b/pkg/widgets/meshgrid/meshgrid_shader.go index 23db6bd9..071ee970 100644 --- a/pkg/widgets/meshgrid/meshgrid_shader.go +++ b/pkg/widgets/meshgrid/meshgrid_shader.go @@ -11,20 +11,31 @@ import ( "github.com/roffe/txlogger/pkg/colors" ) -// GPU renderer: the whole mesh is drawn by a single canvas.Shader. The corner +// GPU renderer: the whole mesh is drawn by a single canvas.Shader. The mesh // values live in a small data texture and the camera in a handful of float // uniforms; the fragment shader reconstructs each pixel's orthographic view // ray, walks the grid with a 2D DDA and intersects the two triangles of each // visited cell. Rotating, zooming and panning therefore cost the CPU nothing // but a uniform update - no projection, sorting or rasterization per frame - -// and a data edit re-uploads only the (cols+1)x(rows+1) texture. +// and a data edit re-uploads only the value texture. +// +// The vertices are the raw cell values (dataVertexMode): the texture is cols x +// rows, the grid has (cols-1)x(rows-1) cells and each cell triangulates the +// four data points around it, so the surface passes exactly through every value +// the way T7Suite's mesh does - a low cell only drops the two triangles meeting +// at that corner, never the wider neighbourhood that averaging values onto a +// shared corner grid would. Degenerate 1xN / Nx1 maps fall back to a corner +// grid (cols+1 x rows+1, value averaged per corner); the GLSL is identical, only +// grid_cols/grid_rows, the texture and the centre uniforms differ. // // The grid-space conventions shared between the Go side and the GLSL below: // - one grid unit = one cell = cellWidth (32) logical px before scaling -// - corner (vertex row i, col j) sits at (j, rows-i); +Y is the low-index -// data rows, exactly like the Oy = (vRows-i)*cellHeight CPU layout -// - corner height = z_off + z_gain * value16, reproducing -// Oz = (V - zmin)/zrange * depth in grid units +// - in dataVertexMode the vertex for data cell (r, c) sits at grid (c, +// rows-1-r); +Y is the low-index data rows. center_gx = (cols-1)/2 places +// it half a cell in from the corner grid, aligning the mesh with the axis +// ticks (drawn at cell centres) and with projectOriginal +// - vertex height = z_off + z_gain * value16, reproducing +// (V - zmin)/zrange * depth in grid units // - view = R * ((grid - center) * scale_px) - cam; the viewer sits at +Z // looking along -Z, so the nearest surface has the largest view Z @@ -49,7 +60,7 @@ const meshShaderBody = ` uniform vec2 frame_size; uniform vec4 rect_coords; -uniform sampler2D mesh_tex; // (cols+1)x(rows+1) corner values, 16 bit in RG +uniform sampler2D mesh_tex; // cols x rows cell values (or corner grid), 16 bit in RG uniform sampler2D colormap_tex; // 256x1 value -> base color lookup // camera rotation R, row major; view = R * model @@ -305,7 +316,7 @@ void main() { float h_tl = corner_height(cx, cy + 1.0); float h_tr = corner_height(cx + 1.0, cy + 1.0); - // cell corners; the fill splits along A-C like the CPU rasterizer + // cell corners; the solid fill chooses its diagonal per cell (below) // while the wireframe diagonal runs B-D like the CPU line mesh vec3 A = vec3(cx, cy + 1.0, h_tl); vec3 B = vec3(cx + 1.0, cy + 1.0, h_tr); @@ -322,29 +333,50 @@ void main() { break; } } else { - float t1; - vec3 bc1; - float t2; - vec3 bc2; - bool hit1 = ray_tri(ro, rd, A, B, C, t1, bc1); - bool hit2 = ray_tri(ro, rd, A, C, D, t2, bc2); + // Two triangles per cell, but the diagonal is chosen per cell so the + // fold runs between the two closest corners (the smaller of the two + // diagonal height gaps). A lone outlier - a high peak or a low dip - + // then falls on a single triangle: the other triangle keeps its three + // similar corners as a near-flat plateau and only the outlier's + // triangle slopes. That is the "plateau triangle + sloping triangle" + // look of T7Suite, instead of the whole quad sagging toward the + // outlier. Per-triangle normals let the plateau read flat while the + // slope catches the light. + bool fold_ac = abs(h_tl - h_br) <= abs(h_tr - h_bl); float best_t = BIG; float hit_h = 0.0; - if (hit1) { - best_t = t1; - hit_h = bc1.x * A.z + bc1.y * B.z + bc1.z * C.z; - } - if (hit2 && t2 < best_t) { - best_t = t2; - hit_h = bc2.x * A.z + bc2.y * C.z + bc2.z * D.z; + vec3 hit_n = vec3(0.0, 0.0, -1.0); + float ts; + vec3 bcs; + if (fold_ac) { + if (ray_tri(ro, rd, A, B, C, ts, bcs) && ts < best_t) { + best_t = ts; + hit_h = bcs.x * A.z + bcs.y * B.z + bcs.z * C.z; + hit_n = cross(B - A, C - A); + } + if (ray_tri(ro, rd, A, C, D, ts, bcs) && ts < best_t) { + best_t = ts; + hit_h = bcs.x * A.z + bcs.y * C.z + bcs.z * D.z; + hit_n = cross(C - A, D - A); + } + } else { + if (ray_tri(ro, rd, A, B, D, ts, bcs) && ts < best_t) { + best_t = ts; + hit_h = bcs.x * A.z + bcs.y * B.z + bcs.z * D.z; + hit_n = cross(B - A, D - A); + } + if (ray_tri(ro, rd, B, C, D, ts, bcs) && ts < best_t) { + best_t = ts; + hit_h = bcs.x * B.z + bcs.y * C.z + bcs.z * D.z; + hit_n = cross(C - B, D - B); + } } if (best_t < BIG) { float view_z = umax - best_t; vec3 rgb = height_color(hit_h, view_z); - vec3 n = cross(C - A, D - B); float ao = cell_ao(cx, cy); - rgb = shade_surface(rgb, n, light, view_dir, ao); + rgb = shade_surface(rgb, hit_n, light, view_dir, ao); if (mode == 0) { vec3 pa = project_grid(rot, A, pix_scale); @@ -358,6 +390,8 @@ void main() { edge_check(p_dev, pb, pc, B.z, C.z, best_d, line_h, line_z); edge_check(p_dev, pc, pd, C.z, D.z, best_d, line_h, line_z); edge_check(p_dev, pd, pa, D.z, A.z, best_d, line_h, line_z); + // only the cell borders are drawn; the per-cell fold diagonal + // is left in the cell colour so the two triangles blend float lm = line_mask(best_d, half_w); if (lm > 0.0) { rgb = mix(rgb, height_color(line_h, line_z) * 0.45, lm); @@ -405,52 +439,76 @@ func (m *Meshgrid) initShader() { m.updateShaderUniforms() } -// updateShaderData re-encodes the corner values into the mesh data texture -// and the height-mapping uniforms. A fresh image is allocated on purpose: -// the painter re-uploads a texture only when the map entry points at a new -// image. +// dataVertexMode reports whether the shader treats raw cell values as the mesh +// vertices (the T7Suite-style triangulated surface) rather than averaging them +// onto a corner grid. It needs at least 2x2 cells to span a quad between data +// points; a 1xN / Nx1 map falls back to the corner-averaged path, which already +// renders those degenerate "ribbons" sensibly. +func (m *Meshgrid) dataVertexMode() bool { + return m.cols >= 2 && m.rows >= 2 +} + +// updateShaderData re-encodes the mesh values into the data texture and the +// height-mapping uniforms. In dataVertexMode the texture holds the raw cell +// values (one texel per data cell) so the shader's per-cell triangulation +// passes exactly through each value: a low cell only pulls down the two +// triangles touching that corner, never the wider neighbourhood that corner +// averaging would. Otherwise it falls back to the (cols+1)x(rows+1) averaged +// corner grid. A fresh image is allocated on purpose: the painter re-uploads a +// texture only when the map entry points at a new image. func (m *Meshgrid) updateShaderData() { if m.shader == nil { return } - vRows, vCols := m.rows+1, m.cols+1 // Values are normalized against the actual data extent so the 16-bit // quantization keeps full resolution even when zmin/zmax (set by // LoadFloat64s) span a wider range than the data. dataMin, dataMax := math.Inf(1), math.Inf(-1) - for i := range m.vertices { - row := m.vertices[i] - for j := range row { - v := row[j].V - if v < dataMin { - dataMin = v - } - if v > dataMax { - dataMax = v - } + for _, v := range m.values { + if v < dataMin { + dataMin = v + } + if v > dataMax { + dataMax = v } } dataRange := dataMax - dataMin + encode := func(v float64) color.RGBA { + norm := 0.0 + if dataRange > 0 { + norm = (v - dataMin) / dataRange + } + q := uint16(norm*65535 + 0.5) + return color.RGBA{R: uint8(q >> 8), G: uint8(q), A: 0xff} + } - img := image.NewRGBA(image.Rect(0, 0, vCols, vRows)) - for i := 0; i < vRows; i++ { - row := m.vertices[i] - for j := 0; j < vCols; j++ { - norm := 0.0 - if dataRange > 0 { - norm = (row[j].V - dataMin) / dataRange + var img *image.RGBA + if m.dataVertexMode() { + // One texel per data cell; cell (r, c) is the vertex at grid (c, + // rows-1-r), so data row 0 stays at the far (high-Y) edge. + img = image.NewRGBA(image.Rect(0, 0, m.cols, m.rows)) + for r := 0; r < m.rows; r++ { + for c := 0; c < m.cols; c++ { + img.SetRGBA(c, m.rows-1-r, encode(m.values[r*m.cols+c])) + } + } + } else { + // Corner-averaged grid: vertex row i sits at grid Y rows-i, which is + // also its texture row. + vRows, vCols := m.rows+1, m.cols+1 + img = image.NewRGBA(image.Rect(0, 0, vCols, vRows)) + for i := 0; i < vRows; i++ { + row := m.vertices[i] + for j := 0; j < vCols; j++ { + img.SetRGBA(j, m.rows-i, encode(row[j].V)) } - q := uint16(norm*65535 + 0.5) - // vertex row i sits at grid Y rows-i (data row 0 is the far - // edge), which is also its texture row - img.SetRGBA(j, m.rows-i, color.RGBA{R: uint8(q >> 8), G: uint8(q), A: 0xff}) } } m.shader.Textures["mesh_tex"] = img - // Heights in grid units: z_off + z_gain*norm reproduces the CPU - // Oz = (V - zmin)/zrange * depth, in units of one cell. + // Heights in grid units: z_off + z_gain*norm reproduces + // (V - zmin)/zrange * depth, in units of one cell. zr := m.zrange if zr == 0 { zr = 1 @@ -515,16 +573,32 @@ func (m *Meshgrid) updateShaderUniforms() { r := m.cameraRotation cw := float64(m.cellWidth) + // Grid extent and centre depend on the surface model. In dataVertexMode the + // vertices are the cols x rows data points, so there are (cols-1)x(rows-1) + // cells and the data point for column c sits half a cell in from the corner + // grid (centre_gx = (cols-1)/2), which keeps the mesh aligned with the axis + // ticks (drawn at cell centres) and with projectOriginal. The corner-grid + // fallback keeps the old cols x rows cells and centre derived from the + // averaged vertices. + gridCols, gridRows := float32(m.cols), float32(m.rows) + centerGx := float32(m.centerX / cw) + centerGy := float32(m.centerY/cw - 1) + if m.dataVertexMode() { + gridCols, gridRows = float32(m.cols-1), float32(m.rows-1) + centerGx = float32(float64(m.cols-1) / 2) + centerGy = float32(float64(m.rows-1) / 2) + } + u := m.shader.Uniforms u["r0"], u["r1"], u["r2"] = float32(r[0][0]), float32(r[0][1]), float32(r[0][2]) u["r3"], u["r4"], u["r5"] = float32(r[1][0]), float32(r[1][1]), float32(r[1][2]) u["r6"], u["r7"], u["r8"] = float32(r[2][0]), float32(r[2][1]), float32(r[2][2]) - u["grid_cols"] = float32(m.cols) - u["grid_rows"] = float32(m.rows) + u["grid_cols"] = gridCols + u["grid_rows"] = gridRows u["scale_px"] = float32(cw * m.scale) u["height_units"] = float32(m.depth / cw) - u["center_gx"] = float32(m.centerX / cw) - u["center_gy"] = float32(m.centerY/cw - 1) + u["center_gx"] = centerGx + u["center_gy"] = centerGy u["center_gz"] = float32(m.centerZ / cw) u["cam_x"] = float32(m.cameraPosition[0]) u["cam_y"] = float32(m.cameraPosition[1]) diff --git a/pkg/widgets/meshgrid/meshgrid_shader_test.go b/pkg/widgets/meshgrid/meshgrid_shader_test.go index 493472fd..96a242ff 100644 --- a/pkg/widgets/meshgrid/meshgrid_shader_test.go +++ b/pkg/widgets/meshgrid/meshgrid_shader_test.go @@ -11,32 +11,35 @@ import ( "github.com/roffe/txlogger/pkg/colors" ) -// The mesh data texture plus z_off/z_gain must reproduce every corner height -// (Oz in grid units) and the colormap index the CPU renderer would use. +// In dataVertexMode the data texture plus z_off/z_gain must reproduce every +// cell's flat-top height (V - zmin)/zrange * depth in grid units. Data cell +// (r, c) is stored at texel (c, rows-1-r) so grid Y runs with the data rows. func TestShaderDataEncoding(t *testing.T) { m := testGrid(t) + if !m.dataVertexMode() { + t.Fatalf("testGrid is %dx%d, expected dataVertexMode", m.cols, m.rows) + } tex, ok := m.shader.Textures["mesh_tex"].(*image.RGBA) if !ok { t.Fatal("mesh_tex missing or not RGBA") } - if tex.Bounds().Dx() != m.cols+1 || tex.Bounds().Dy() != m.rows+1 { - t.Fatalf("mesh_tex is %v, want %dx%d", tex.Bounds(), m.cols+1, m.rows+1) + if tex.Bounds().Dx() != m.cols || tex.Bounds().Dy() != m.rows { + t.Fatalf("mesh_tex is %v, want %dx%d", tex.Bounds(), m.cols, m.rows) } zOff := float64(m.shader.Uniforms["z_off"]) zGain := float64(m.shader.Uniforms["z_gain"]) - cw := float64(m.cellWidth) + hmax := m.depth / float64(m.cellWidth) - for i := 0; i <= m.rows; i++ { - for j := 0; j <= m.cols; j++ { - // vertex row i lives at texture row rows-i (grid Y up) - c := tex.RGBAAt(j, m.rows-i) - norm := (float64(c.R)*256 + float64(c.G)) / 65535 + for r := 0; r < m.rows; r++ { + for c := 0; c < m.cols; c++ { + px := tex.RGBAAt(c, m.rows-1-r) + norm := (float64(px.R)*256 + float64(px.G)) / 65535 gotH := zOff + zGain*norm - wantH := m.vertices[i][j].Oz / cw - // 16-bit quantization of a ~12.5 unit range + wantH := (m.values[r*m.cols+c] - m.zmin) / m.zrange * hmax + // 16-bit quantization of the encoded value range if math.Abs(gotH-wantH) > zGain/65535+1e-6 { - t.Fatalf("corner (%d,%d): height %v, want %v", i, j, gotH, wantH) + t.Fatalf("cell (%d,%d): height %v, want %v", r, c, gotH, wantH) } } } @@ -86,7 +89,10 @@ func TestShaderProjectionMatchesVertices(t *testing.T) { for i := 0; i <= m.rows; i += 4 { for j := 0; j <= m.cols; j += 4 { v := m.vertices[i][j] - g := [3]float64{float64(j), float64(m.rows - i), v.Oz / cw} + // dataVertexMode shifts the grid half a cell: the averaged corner + // (i, j) maps to shader grid (j-0.5, rows-i-0.5), which projects to + // the same screen point as the CPU corner transform. + g := [3]float64{float64(j) - 0.5, float64(m.rows-i) - 0.5, v.Oz / cw} var view [3]float64 for a := 0; a < 3; a++ { diff --git a/pkg/widgets/meshgrid/meshgrid_surface.go b/pkg/widgets/meshgrid/meshgrid_surface.go index ddbb5406..afbea12f 100644 --- a/pkg/widgets/meshgrid/meshgrid_surface.go +++ b/pkg/widgets/meshgrid/meshgrid_surface.go @@ -32,10 +32,14 @@ type quadRef struct { const surfaceEdgeFade = 0.45 // drawSurface fills each grid cell with two Gouraud-shaded triangles, -// back-to-front. A fixed directional light flat-shades each quad so the -// surface relief stays visible even where the value color barely changes. -// When edges is true the cell outline is drawn right after its fill, which -// keeps lines on hidden faces correctly occluded by nearer quads. +// back-to-front. Like the shader backend, the split diagonal is chosen per cell +// to fold between the two closest corners (by value, so it stays put as the +// camera rotates): a lone peak or dip then lands on a single triangle and the +// other stays a flat plateau, instead of the whole quad sagging toward it. Each +// triangle is flat-shaded from its own normal so the plateau reads flat while +// the slope catches the light. When edges is true the cell outline is drawn +// right after its fill, which keeps lines on hidden faces correctly occluded by +// nearer quads. func (m *Meshgrid) drawSurface(img *image.RGBA, projX, projY []int, vertCol []color.RGBA, edges bool) { // One quad per data cell; the corner-vertex grid is (rows+1) x (cols+1). quads := m.scratchQuads[:0] @@ -67,19 +71,34 @@ func (m *Meshgrid) drawSurface(img *image.RGBA, projX, projY []int, vertCol []co vCols := m.cols + 1 for _, q := range quads { - ai := q.i*vCols + q.j // top-left - bi := ai + 1 // top-right - di := ai + vCols // bottom-left - ci := di + 1 // bottom-right - - shade := m.quadShade(q.i, q.j, lx, ly, lz) - ca := fadeColor(vertCol[ai], shade) - cb := fadeColor(vertCol[bi], shade) - cc := fadeColor(vertCol[ci], shade) - cd := fadeColor(vertCol[di], shade) - - fillTriangle(img, projX[ai], projY[ai], projX[bi], projY[bi], projX[ci], projY[ci], ca, cb, cc) - fillTriangle(img, projX[ai], projY[ai], projX[ci], projY[ci], projX[di], projY[di], ca, cc, cd) + i, j := q.i, q.j + ai := i*vCols + j // top-left + bi := ai + 1 // top-right + di := ai + vCols // bottom-left + ci := di + 1 // bottom-right + + a := &m.vertices[i][j] + b := &m.vertices[i][j+1] + c := &m.vertices[i+1][j+1] + d := &m.vertices[i+1][j] + + // Fold along whichever diagonal has the smaller corner-value gap, so an + // outlier is isolated in one sloping triangle (see the doc comment). + if math.Abs(a.V-c.V) <= math.Abs(b.V-d.V) { + s1 := triShade(a, b, c, lx, ly, lz) + s2 := triShade(a, c, d, lx, ly, lz) + fillTriangle(img, projX[ai], projY[ai], projX[bi], projY[bi], projX[ci], projY[ci], + fadeColor(vertCol[ai], s1), fadeColor(vertCol[bi], s1), fadeColor(vertCol[ci], s1)) + fillTriangle(img, projX[ai], projY[ai], projX[ci], projY[ci], projX[di], projY[di], + fadeColor(vertCol[ai], s2), fadeColor(vertCol[ci], s2), fadeColor(vertCol[di], s2)) + } else { + s1 := triShade(a, b, d, lx, ly, lz) + s2 := triShade(b, c, d, lx, ly, lz) + fillTriangle(img, projX[ai], projY[ai], projX[bi], projY[bi], projX[di], projY[di], + fadeColor(vertCol[ai], s1), fadeColor(vertCol[bi], s1), fadeColor(vertCol[di], s1)) + fillTriangle(img, projX[bi], projY[bi], projX[ci], projY[ci], projX[di], projY[di], + fadeColor(vertCol[bi], s2), fadeColor(vertCol[ci], s2), fadeColor(vertCol[di], s2)) + } if edges { ea := fadeColor(vertCol[ai], surfaceEdgeFade) @@ -122,6 +141,30 @@ func (m *Meshgrid) quadShade(i, j int, lx, ly, lz float64) float64 { return 0.6 + 0.4*dot } +// triShade computes a flat Lambert term for one triangle from its view-space +// normal (cross of two edges). The absolute dot product is used since the +// surface is single-sided and may be viewed from below. This is the per-cell +// quadShade applied per triangle so each facet of the chosen split shades on +// its own slope. +func triShade(a, b, c *Vertex, lx, ly, lz float64) float64 { + ux, uy, uz := b.X-a.X, b.Y-a.Y, b.Z-a.Z + vx, vy, vz := c.X-a.X, c.Y-a.Y, c.Z-a.Z + + nx := uy*vz - uz*vy + ny := uz*vx - ux*vz + nz := ux*vy - uy*vx + + nl := math.Sqrt(nx*nx + ny*ny + nz*nz) + if nl == 0 { + return 1 + } + dot := (nx*lx + ny*ly + nz*lz) / nl + if dot < 0 { + dot = -dot + } + return 0.6 + 0.4*dot +} + // fillTriangle rasterizes a triangle with per-vertex (Gouraud) color // interpolation using incremental integer edge functions, clipped to the // image bounds via the bounding box. diff --git a/pkg/windows/mainWindow_toolbar.go b/pkg/windows/mainWindow_toolbar.go index 24cefa6c..47494c34 100644 --- a/pkg/windows/mainWindow_toolbar.go +++ b/pkg/windows/mainWindow_toolbar.go @@ -17,7 +17,7 @@ func (mw *MainWindow) openMatrixBuilder() { mw.wm.Raise(w) return } - inner := multiwindow.NewInnerWindow("Matrix builder", matrixbuilder.New()) + inner := multiwindow.NewInnerWindow("Matrix builder", matrixbuilder.New(mw.settings.GetMeshRenderer())) inner.Icon = theme.GridIcon() mw.wm.Add(inner) inner.Resize(fyne.NewSize(1000, 720)) From 709f7b85765f21c7bd36dd64de2fde48099e6a71 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 17 Jun 2026 20:57:10 +0200 Subject: [PATCH 051/102] experiment --- pkg/widgets/boosttuner/boosttuner.go | 444 +++++++++++++++++++++ pkg/widgets/boosttuner/integration_test.go | 53 +++ pkg/widgets/boosttuner/pid.go | 179 +++++++++ pkg/widgets/boosttuner/pidtune.go | 299 ++++++++++++++ pkg/widgets/boosttuner/pidtune_test.go | 79 ++++ pkg/widgets/boosttuner/regmap.go | 375 +++++++++++++++++ pkg/widgets/boosttuner/regmap_test.go | 83 ++++ pkg/widgets/boosttuner/sim.go | 278 +++++++++++++ pkg/widgets/boosttuner/sim_test.go | 74 ++++ pkg/windows/mainWindow_toolbar.go | 51 +++ 10 files changed, 1915 insertions(+) create mode 100644 pkg/widgets/boosttuner/boosttuner.go create mode 100644 pkg/widgets/boosttuner/integration_test.go create mode 100644 pkg/widgets/boosttuner/pid.go create mode 100644 pkg/widgets/boosttuner/pidtune.go create mode 100644 pkg/widgets/boosttuner/pidtune_test.go create mode 100644 pkg/widgets/boosttuner/regmap.go create mode 100644 pkg/widgets/boosttuner/regmap_test.go create mode 100644 pkg/widgets/boosttuner/sim.go create mode 100644 pkg/widgets/boosttuner/sim_test.go diff --git a/pkg/widgets/boosttuner/boosttuner.go b/pkg/widgets/boosttuner/boosttuner.go new file mode 100644 index 00000000..dde8a718 --- /dev/null +++ b/pkg/widgets/boosttuner/boosttuner.go @@ -0,0 +1,444 @@ +// Package boosttuner provides a widget that helps auto-tune the Trionic 7 boost +// (APC) controller from logged data and writes the result back into the loaded +// binary. +// +// The T7 boost controller is a feedforward + PID loop (see Boost.c in the EU03 +// source): +// +// PWMCalc = RegConValue // feedforward: BoostCal.RegMap[SetValue, rpm] +// + Adaption // learned offset (BoostAdap.Adaption) +// + PFac + IFac + DFac // PID on LoadDiff = SetValue - m_AirInlet +// + env compensation // temp / altitude / E85 / noise reduction +// +// PWMCalc is the wastegate solenoid duty cycle in 0.1% units, clamped 2..98%. +// +// RegMap is the feedforward map and can be genuinely learned: at samples where +// the loop is settled and on target, the duty the feedforward *should* have +// supplied equals RegConValue plus everything the loop was adding to correct it +// (PFac + IFac + DFac + Adaption). Folding that sum back into RegMap leaves the +// loop with less to correct. The PID maps cannot be cleanly learned this way, so +// they are handled separately (heuristic suggestions + a replay simulator). +// +// Units note: the controller internals (RegConValue, P/I/D, Adaption, PWMCalc) +// are logged in raw 0.1% units (correction factor 1, e.g. 450 == 45.0%), while +// the BoostCal.RegMap symbol stores % (correction factor 0.1, e.g. 45.0). We +// learn in raw units and divide by dutyRawPerPct to land in the % the map uses. +package boosttuner + +import ( + "fmt" + "io" + "log" + "math" + "path/filepath" + "sort" + "strconv" + "strings" + + "fyne.io/fyne/v2" + "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/colors" + "github.com/roffe/txlogger/pkg/logfile" + "github.com/roffe/txlogger/pkg/widgets" + "github.com/roffe/txlogger/pkg/widgets/meshgrid" + "github.com/roffe/txlogger/pkg/widgets/progressmodal" +) + +// dutyRawPerPct converts the controller's raw 0.1% duty units into the % units +// the BoostCal.RegMap symbol stores (450 raw -> 45.0%). +const dutyRawPerPct = 10.0 + +var _ fyne.Widget = (*BoostTuner)(nil) + +// Config wires the tuner to the loaded binary. Symbols is read for the current +// maps and their axes; Save persists an edited map back into the binary (and to +// disk). Both are supplied by the main window, which owns mw.fw and the filename. +type Config struct { + // Symbols is the currently loaded binary (mw.fw). Used read-only here. + Symbols symbol.SymbolCollection + // Save writes data (in engineering units) into the named symbol and persists + // the binary to disk, taking a one-time backup on first write. nil disables + // the "Apply to binary" buttons. + Save func(symbolName string, data []float64) error + + MeshRenderer meshgrid.RenderBackend + Colorblind colors.ColorBlindMode +} + +// channel is a logical signal the tuner needs, with the candidate series names to +// look for in a log (first present wins) and a human description for the checklist. +type channel struct { + key string + candidates []string + desc string +} + +// requiredChannels lists the log signals the RegMap learner and simulator rely +// on. Boost.SetValue is the exact load value the ECU feeds into the RegMap X +// lookup; m_Request is the same quantity under its airmass-master name and is a +// fallback. m_AirInletBoost is the airmass the loop regulates against. +var requiredChannels = []channel{ + {"rpm", []string{"ActualIn.n_Engine"}, "Engine speed (RegMap Y axis)"}, + {"setValue", []string{"Boost.SetValue", "m_Request", "AirMassMast.m_Request"}, "Load set value (RegMap X axis)"}, + {"regCon", []string{"BoostProt.RegConValue"}, "Feedforward duty from RegMap"}, + {"pFac", []string{"BoostProt.PFac"}, "P part"}, + {"iFac", []string{"BoostProt.IFac"}, "I part"}, + {"dFac", []string{"BoostProt.DFac"}, "D part"}, + {"adaption", []string{"BoostAdap.Adaption"}, "Adaption offset"}, + {"loadDiff", []string{"BoostProt.LoadDiff"}, "Load error (SetValue - airmass)"}, + {"pwmCalc", []string{"BoostProt.PWMCalc"}, "Total calculated duty"}, + {"pwm2pct", []string{"BoostProt.ST_PWM2Perc"}, "Open-loop/2% flag"}, + {"airInlet", []string{"MAF.m_AirInletBoost", "MAF.m_AirInlet"}, "Actual airmass"}, +} + +type BoostTuner struct { + widget.BaseWidget + cfg Config + + // values holds every series merged across all loaded log files, row-aligned + // with NaN padding (same scheme as the matrix builder). + values map[string][]float64 + order []string + loadedFiles []string + nrecords int + + // resolved maps each logical channel key to the actual series name found in + // the loaded logs (empty when missing). + resolved map[string]string + + // RegMap state (see regmap.go). + rmAxisX, rmAxisY []float64 // breakpoints read from the binary + rmCols, rmRows int + rmCurrent, rmLearned, rmDelta []float64 // engineering units (%) + rmCounts []int + rmBuilt bool + + // RegMap tuning parameters. + onTarget float64 // accept samples with |LoadDiff| <= this (mg/c) + rpmStab float64 // reject when |rpm step| exceeds this (rpm) + loadStab float64 // reject when |SetValue step| exceeds this (mg/c) + minSamples int // cells with fewer hits keep their current value + blend float64 // fraction (0..1) of the learned change to apply + + // PID editors keyed by "P"/"I"/"D" (see pid.go). + pidEditors map[string]*pidEditor + + // widgets + logsLabel *widget.Label + channelList *fyne.Container + rmStatus *widget.Label + rmView *widget.Select + rmDisplay *fyne.Container + + tabs *container.AppTabs + content fyne.CanvasObject +} + +// New builds an empty tuner bound to the loaded binary in cfg. +func New(cfg Config) *BoostTuner { + bt := &BoostTuner{ + cfg: cfg, + values: make(map[string][]float64), + resolved: make(map[string]string), + onTarget: 30, + rpmStab: 150, + loadStab: 50, + minSamples: 5, + blend: 1.0, + } + bt.ExtendBaseWidget(bt) + bt.buildUI() + return bt +} + +func (bt *BoostTuner) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(bt.content) +} + +func (bt *BoostTuner) buildUI() { + bt.tabs = container.NewAppTabs( + container.NewTabItemWithIcon("Logs", theme.FolderOpenIcon(), bt.buildLogsTab()), + container.NewTabItemWithIcon("RegMap", theme.GridIcon(), bt.buildRegMapTab()), + container.NewTabItemWithIcon("PID maps", theme.GridIcon(), bt.buildPIDTab()), + container.NewTabItemWithIcon("Simulator", theme.MediaPlayIcon(), bt.buildSimTab()), + ) + bt.content = bt.tabs +} + +// --- Logs tab --- + +func (bt *BoostTuner) buildLogsTab() fyne.CanvasObject { + bt.logsLabel = widget.NewLabel("No log files loaded") + bt.logsLabel.Wrapping = fyne.TextWrapWord + + addBtn := widget.NewButtonWithIcon("Add log files", theme.FolderOpenIcon(), bt.openLogDialog) + clearBtn := widget.NewButtonWithIcon("Clear", theme.ContentClearIcon(), bt.clearLogs) + + bt.channelList = container.NewVBox() + bt.refreshChannelList() + + intro := widget.NewLabel( + "Load logs from boost pulls (ideally full-throttle runs across the rev range), " + + "then use the RegMap tab to learn the feedforward map. The channels below " + + "must be present in the logs.") + intro.Wrapping = fyne.TextWrapWord + + return container.NewBorder( + container.NewVBox( + intro, + container.NewGridWithColumns(2, addBtn, clearBtn), + bt.logsLabel, + widget.NewSeparator(), + widget.NewLabelWithStyle("Required channels", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + ), + nil, nil, nil, + container.NewVScroll(bt.channelList), + ) +} + +// refreshChannelList redraws the per-channel present/missing checklist against the +// currently loaded logs. +func (bt *BoostTuner) refreshChannelList() { + bt.channelList.Objects = bt.channelList.Objects[:0] + for _, ch := range requiredChannels { + name := bt.resolved[ch.key] + var icon fyne.Resource + var detail string + if name != "" { + icon = theme.ConfirmIcon() + detail = name + } else { + icon = theme.CancelIcon() + detail = "missing: " + strings.Join(ch.candidates, " / ") + } + row := container.NewBorder(nil, nil, + widget.NewIcon(icon), + widget.NewLabel(detail), + widget.NewLabel(ch.desc), + ) + bt.channelList.Add(row) + } + bt.channelList.Refresh() +} + +// resolveChannels picks, for each logical channel, the first candidate series +// present in the loaded logs. +func (bt *BoostTuner) resolveChannels() { + bt.resolved = make(map[string]string) + for _, ch := range requiredChannels { + for _, cand := range ch.candidates { + if _, ok := bt.values[cand]; ok { + bt.resolved[ch.key] = cand + break + } + } + } +} + +// series returns the merged log data for a resolved channel key. +func (bt *BoostTuner) series(key string) ([]float64, bool) { + name := bt.resolved[key] + if name == "" { + return nil, false + } + v, ok := bt.values[name] + return v, ok +} + +// missingChannels lists the descriptions of any required channels not found. +func (bt *BoostTuner) missingChannels() []string { + var out []string + for _, ch := range requiredChannels { + if bt.resolved[ch.key] == "" { + out = append(out, ch.desc) + } + } + return out +} + +// --- log loading (mirrors the matrix builder's pipeline) --- + +func (bt *BoostTuner) openLogDialog() { + widgets.SelectFiles(func(readers []fyne.URIReadCloser) { + c := fyne.CurrentApp().Driver().CanvasForObject(bt) + if c == nil { + if wins := fyne.CurrentApp().Driver().AllWindows(); len(wins) > 0 { + c = wins[0].Canvas() + } + } + var pm *progressmodal.ProgressModal + if c != nil { + pm = progressmodal.New(c, fmt.Sprintf("Parsing %d log file(s)...", len(readers))) + pm.Show() + } + + go func() { + type parsed struct { + name string + local map[string][]float64 + n int + } + var ok []parsed + var failed int + for _, r := range readers { + name := r.URI().Name() + local, n, err := parseLog(name, r) + r.Close() + if err != nil { + log.Println("boosttuner:", err) + failed++ + continue + } + ok = append(ok, parsed{name, local, n}) + } + + fyne.Do(func() { + if pm != nil { + pm.Hide() + } + for _, p := range ok { + bt.mergeLog(p.name, p.local, p.n) + } + bt.rebuildOrder() + bt.resolveChannels() + bt.refreshChannelList() + bt.refreshLogList() + if failed > 0 { + bt.logStatus(fmt.Sprintf("Loaded %d file(s), %d failed", len(ok), failed)) + } + }) + }() + }, "logfile", "t5l", "t7l", "t8l", "csv", "bpl") +} + +// parseLog reads a single log into a row-aligned series map, padding gaps with +// NaN. It touches no shared state, so it is safe off the UI goroutine. +func parseLog(name string, r io.Reader) (map[string][]float64, int, error) { + lf, err := logfile.Open(name, r) + if err != nil { + return nil, 0, err + } + defer lf.Close() + + local := make(map[string][]float64) + n := 0 + for { + rec := lf.Next() + if rec.EOF { + break + } + for k, v := range rec.Values { + if k == "Pgm_status" { + continue + } + arr := local[k] + for len(arr) < n { // back-fill records before this key first appeared + arr = append(arr, math.NaN()) + } + local[k] = append(arr, v) + } + n++ + for k, arr := range local { // forward-fill keys missing from this record + for len(arr) < n { + arr = append(arr, math.NaN()) + } + local[k] = arr + } + } + if n == 0 { + return nil, 0, fmt.Errorf("%s contains no records", name) + } + return local, n, nil +} + +// mergeLog appends a parsed log to the merged series set, keeping every series +// row-aligned. Must run on the UI goroutine. +func (bt *BoostTuner) mergeLog(name string, local map[string][]float64, n int) { + base := bt.nrecords + for k, arr := range local { + cur, ok := bt.values[k] + if !ok { + cur = nanSlice(base) + } + bt.values[k] = append(cur, arr...) + } + for k, cur := range bt.values { + if _, ok := local[k]; !ok { + bt.values[k] = append(cur, nanSlice(n)...) + } + } + bt.nrecords = base + n + bt.loadedFiles = append(bt.loadedFiles, name) +} + +func (bt *BoostTuner) clearLogs() { + bt.values = make(map[string][]float64) + bt.order = nil + bt.loadedFiles = nil + bt.nrecords = 0 + bt.resolveChannels() + bt.refreshChannelList() + bt.refreshLogList() +} + +func (bt *BoostTuner) rebuildOrder() { + bt.order = make([]string, 0, len(bt.values)) + for k := range bt.values { + bt.order = append(bt.order, k) + } + sort.Slice(bt.order, func(i, j int) bool { + return strings.ToLower(bt.order[i]) < strings.ToLower(bt.order[j]) + }) +} + +func (bt *BoostTuner) refreshLogList() { + if len(bt.loadedFiles) == 0 { + bt.logsLabel.SetText("No log files loaded") + return + } + var b strings.Builder + for i, f := range bt.loadedFiles { + if i > 0 { + b.WriteString("\n") + } + b.WriteString(filepath.Base(f)) + } + bt.logsLabel.SetText(fmt.Sprintf("%d file(s), %d records:\n%s", + len(bt.loadedFiles), bt.nrecords, b.String())) +} + +// logStatus appends a one-off message to the log list label. +func (bt *BoostTuner) logStatus(msg string) { + bt.refreshLogList() + bt.logsLabel.SetText(bt.logsLabel.Text + "\n" + msg) +} + +func nanSlice(n int) []float64 { + s := make([]float64, n) + for i := range s { + s[i] = math.NaN() + } + return s +} + +// --- shared helpers --- + +// nearestIndex returns the index of the axis breakpoint closest to v. +func nearestIndex(axis []float64, v float64) int { + best := 0 + bestDist := math.Abs(axis[0] - v) + for i := 1; i < len(axis); i++ { + if d := math.Abs(axis[i] - v); d < bestDist { + bestDist = d + best = i + } + } + return best +} + +func formatFloat(v float64) string { + return strconv.FormatFloat(v, 'f', -1, 64) +} diff --git a/pkg/widgets/boosttuner/integration_test.go b/pkg/widgets/boosttuner/integration_test.go new file mode 100644 index 00000000..7118e1a6 --- /dev/null +++ b/pkg/widgets/boosttuner/integration_test.go @@ -0,0 +1,53 @@ +package boosttuner + +import ( + "math" + "os" + "testing" + + symbol "github.com/roffe/ecusymbol" +) + +const testBinary = "/home/roffe/temp/bosse.bin" + +// TestRegMapBilerp_RealBinary checks that, against a real T7 binary, the RegMap is +// read row-major [rpm][load], the %->raw conversion is right, and bilerp returns +// the stored cell values at the axis breakpoints. Skips when the binary is absent. +func TestRegMapBilerp_RealBinary(t *testing.T) { + data, err := os.ReadFile(testBinary) + if err != nil { + t.Skipf("test binary not available: %v", err) + } + _, syms, err := symbol.Load(testBinary, data, func(string) {}) + if err != nil { + t.Fatalf("load: %v", err) + } + + x := syms.GetByName(symSetLoadXSP).Float64s() + y := syms.GetByName(symNEngSP).Float64s() + regPct := syms.GetByName(symRegMap).Float64s() + if len(x)*len(y) != len(regPct) { + t.Fatalf("RegMap %d != %d x %d", len(regPct), len(x), len(y)) + } + raw := make([]float64, len(regPct)) + for i, v := range regPct { + raw[i] = v * dutyRawPerPct + } + + // First cell is [rpm[0]][load[0]]; last is [rpm[last]][load[last]]. + first := regPct[0] * dutyRawPerPct + last := regPct[len(regPct)-1] * dutyRawPerPct + if got := bilerp(x, y, raw, x[0], y[0]); math.Abs(got-first) > 1e-6 { + t.Errorf("bilerp at first breakpoint = %v, want %v", got, first) + } + if got := bilerp(x, y, raw, x[len(x)-1], y[len(y)-1]); math.Abs(got-last) > 1e-6 { + t.Errorf("bilerp at last breakpoint = %v, want %v", got, last) + } + + // An interior breakpoint must equal its exact stored cell (row-major index). + r, c := len(y)/2, len(x)/2 + want := regPct[r*len(x)+c] * dutyRawPerPct + if got := bilerp(x, y, raw, x[c], y[r]); math.Abs(got-want) > 1e-6 { + t.Errorf("bilerp at interior breakpoint = %v, want %v", got, want) + } +} diff --git a/pkg/widgets/boosttuner/pid.go b/pkg/widgets/boosttuner/pid.go new file mode 100644 index 00000000..67302e1b --- /dev/null +++ b/pkg/widgets/boosttuner/pid.go @@ -0,0 +1,179 @@ +package boosttuner + +import ( + "fmt" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/widgets/mapviewer" +) + +// PID maps and their shared axes. All three are row-major [rpm][loadDiff]: +// rows follow PIDYSP (rpm), columns follow PIDXSP (load error mg/c). +const ( + symPMap = "BoostCal.PMap" + symIMap = "BoostCal.IMap" + symDMap = "BoostCal.DMap" + symPIDXSP = "BoostCal.PIDXSP" // X axis: load error (mg/c) + symPIDYSP = "BoostCal.PIDYSP" // Y axis: engine speed (rpm) +) + +// pidEditor is one editable PID gain map. data is the same slice the mapviewer +// edits in place, so hand-edits flow back without a copy; Apply writes data into +// the binary. +type pidEditor struct { + bt *BoostTuner + name string // "P" / "I" / "D" + symbolName string + axisX, axisY []float64 + cols, rows int + data []float64 + mv *mapviewer.MapViewer + status *widget.Label + suggestBox *fyne.Container // populated by the heuristics in pidtune.go +} + +func (bt *BoostTuner) buildPIDTab() fyne.CanvasObject { + x, errX := bt.readSymbol(symPIDXSP) + y, errY := bt.readSymbol(symPIDYSP) + if errX != nil || errY != nil { + return container.NewCenter(widget.NewLabel("BoostCal PID axes not found in this binary.")) + } + + bt.pidEditors = map[string]*pidEditor{} + tabs := container.NewAppTabs() + for _, m := range []struct{ name, sym string }{ + {"P", symPMap}, {"I", symIMap}, {"D", symDMap}, + } { + ed := bt.newPIDEditor(m.name, m.sym, x, y) + bt.pidEditors[m.name] = ed + tabs.Append(container.NewTabItem(m.name+" map", ed.object())) + } + + intro := widget.NewLabel( + "Edit the PID gain maps directly, or use the per-rpm-band suggestions " + + "(from logged boost transients) to scale a map. Validate changes in the " + + "Simulator tab before flashing.") + intro.Wrapping = fyne.TextWrapWord + + suggestStatus := widget.NewLabel("") + computeBtn := widget.NewButtonWithIcon("Compute suggestions from logs", theme.SearchIcon(), func() { + suggestStatus.SetText(bt.computePIDSuggestions()) + }) + + header := container.NewVBox( + intro, + container.NewBorder(nil, nil, nil, suggestStatus, computeBtn), + ) + return container.NewBorder(header, nil, nil, nil, tabs) +} + +func (bt *BoostTuner) newPIDEditor(name, symName string, x, y []float64) *pidEditor { + ed := &pidEditor{ + bt: bt, name: name, symbolName: symName, + axisX: x, axisY: y, cols: len(x), rows: len(y), + } + ed.status = widget.NewLabel("") + ed.suggestBox = container.NewVBox() + ed.reload() + return ed +} + +func (ed *pidEditor) object() fyne.CanvasObject { + display := container.NewStack() + ed.rebuildViewer(display) + + applyBtn := widget.NewButtonWithIcon("Apply to binary", theme.DocumentSaveIcon(), func() { + if ed.bt.cfg.Save == nil { + ed.status.SetText("No binary to write to.") + return + } + if err := ed.bt.cfg.Save(ed.symbolName, ed.data); err != nil { + ed.status.SetText("Save failed: " + err.Error()) + return + } + ed.status.SetText("Wrote " + ed.symbolName) + }) + if ed.bt.cfg.Save == nil { + applyBtn.Disable() + } + reloadBtn := widget.NewButtonWithIcon("Reload from binary", theme.ViewRefreshIcon(), func() { + ed.reload() + ed.rebuildViewer(display) + ed.status.SetText("Reloaded " + ed.symbolName) + }) + + controls := container.NewVBox( + container.NewGridWithColumns(2, applyBtn, reloadBtn), + ed.status, + widget.NewSeparator(), + widget.NewLabelWithStyle("Suggestions", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + container.NewVScroll(ed.suggestBox), + ) + side := container.NewBorder(nil, nil, nil, nil, controls) + + split := container.NewHSplit(display, side) + split.Offset = 0.72 + return split +} + +// reload reads the map from the binary into a fresh editable slice. +func (ed *pidEditor) reload() { + z, err := ed.bt.readSymbol(ed.symbolName) + if err != nil { + ed.data = make([]float64, ed.cols*ed.rows) + ed.status.SetText(err.Error()) + return + } + ed.data = z +} + +// rebuildViewer swaps a fresh editable map viewer (bound to ed.data) into host. +func (ed *pidEditor) rebuildViewer(host *fyne.Container) { + mv, err := mapviewer.New(&mapviewer.Config{ + Name: ed.symbolName, + XData: ed.axisX, + YData: ed.axisY, + ZData: ed.data, + XPrecision: 0, + YPrecision: 0, + ZPrecision: 0, + XLabel: "Load error (mg/c)", + YLabel: "Engine speed (rpm)", + ZLabel: ed.name + " constant", + MeshView: true, + MeshRenderer: ed.bt.cfg.MeshRenderer, + Editable: true, + ColorblindMode: ed.bt.cfg.Colorblind, + SaveECUFunc: func([]float64) {}, + OnUpdateCell: func(int, []float64) {}, + }) + if err != nil { + host.Objects = []fyne.CanvasObject{container.NewCenter(widget.NewLabel(err.Error()))} + host.Refresh() + return + } + ed.mv = mv + host.Objects = []fyne.CanvasObject{mv} + host.Refresh() +} + +// scaleRows multiplies whole rpm rows (indexed by PIDYSP) by per-row factors and +// refreshes the viewer. Used by the heuristic suggestions in pidtune.go. +func (ed *pidEditor) scaleRows(factors map[int]float64) { + for row, f := range factors { + if row < 0 || row >= ed.rows { + continue + } + for c := 0; c < ed.cols; c++ { + ed.data[row*ed.cols+c] *= f + } + } + if ed.mv != nil { + _ = ed.mv.SetZData(ed.data) + ed.mv.Refresh() + } + ed.status.SetText(fmt.Sprintf("Scaled %d row(s); review then Apply.", len(factors))) +} diff --git a/pkg/widgets/boosttuner/pidtune.go b/pkg/widgets/boosttuner/pidtune.go new file mode 100644 index 00000000..0828864b --- /dev/null +++ b/pkg/widgets/boosttuner/pidtune.go @@ -0,0 +1,299 @@ +package boosttuner + +import ( + "fmt" + "math" + "sort" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" +) + +// PID heuristics. The PID gain maps cannot be cleanly auto-learned from arbitrary +// logs, so instead we measure the *quality* of logged boost transients and +// suggest bounded per-rpm-band scalings of the maps. These are starting points to +// review and validate in the Simulator, never an automatic flash. +// +// Error convention: loadDiff = SetValue - airmass, so a positive error means we +// are under the request (still spooling) and a negative error means airmass has +// exceeded the request (overshoot). + +// transientCfg tunes the boost-onset detector. +type transientCfg struct { + startErr float64 // onset: error above this (mg/c) with the loop engaged + settleBand float64 // |error| within this counts as "on target" (mg/c) + minLen int // ignore events shorter than this many samples +} + +func defaultTransientCfg() transientCfg { + return transientCfg{startErr: 100, settleBand: 20, minLen: 5} +} + +// transient summarises one boost-onset event. +type transient struct { + rpm float64 // mean rpm over the event + overshoot float64 // max airmass-over-request after onset (mg/c, >=0) + crossings int // sign changes of the error (oscillation) + riseSamples int // samples from onset until first settle (or event length) + settled bool + ssError float64 // mean error in the settled tail (mg/c) +} + +// pidSuggestInputs holds the row-aligned channels the detector consumes. +type pidSuggestInputs struct { + n int + rpm, loadDiff, pwm2pct []float64 +} + +// detectTransients walks the log and extracts boost-onset events: a stretch where +// the loop is engaged and the error starts large-positive, tracked until the loop +// disengages or the log ends. +func detectTransients(in pidSuggestInputs, cfg transientCfg) []transient { + var out []transient + active := false + var onset, settleAt int + var sumRPM, maxNeg, prevSign float64 + var crossings, length int + + finalize := func(end int) { + if !active { + return + } + active = false + if length < cfg.minLen { + return + } + t := transient{ + rpm: sumRPM / float64(length), + overshoot: maxNeg, + crossings: crossings, + settled: settleAt >= 0, + } + if settleAt >= 0 { + t.riseSamples = settleAt - onset + // Steady-state error: mean over the settled tail. + var s float64 + var c int + for i := settleAt; i < end; i++ { + if !math.IsNaN(in.loadDiff[i]) { + s += in.loadDiff[i] + c++ + } + } + if c > 0 { + t.ssError = s / float64(c) + } + } else { + t.riseSamples = length + } + out = append(out, t) + } + + for i := 0; i < in.n; i++ { + e, r, p := in.loadDiff[i], in.rpm[i], in.pwm2pct[i] + if math.IsNaN(e) || math.IsNaN(r) || math.IsNaN(p) { + finalize(i) + continue + } + engaged := p == 0 + if !active { + if engaged && e > cfg.startErr { + active = true + onset, settleAt = i, -1 + sumRPM, maxNeg, crossings, length = 0, 0, 0, 0 + prevSign = 1 // onset error is positive + } else { + continue + } + } + if !engaged { + finalize(i) + continue + } + sumRPM += r + length++ + if sign := signOf(e); sign != 0 && sign != prevSign { + crossings++ + prevSign = sign + } + if e < 0 && -e > maxNeg { + maxNeg = -e + } + if settleAt < 0 && math.Abs(e) <= cfg.settleBand { + settleAt = i + } + } + finalize(in.n) + return out +} + +// suggestCfg tunes how metrics map to scaling factors. +type suggestCfg struct { + overshootHi float64 // mg/c above which we trim P & D + riseHi int // rise samples above which we add P + ssHi float64 // |steady error| above which we add I + trim float64 // factor applied when trimming (e.g. 0.85) + boost float64 // factor applied when adding (e.g. 1.15) +} + +func defaultSuggestCfg() suggestCfg { + return suggestCfg{overshootHi: 50, riseHi: 25, ssHi: 20, trim: 0.85, boost: 1.15} +} + +// bandSuggestion is a per-rpm-band scaling recommendation for the PID maps. +type bandSuggestion struct { + row int // PIDYSP index + rpm float64 // band breakpoint + factorP float64 + factorI float64 + factorD float64 + reason string + events int +} + +// factorFor returns the suggested factor for the named map ("P"/"I"/"D"). +func (b bandSuggestion) factorFor(name string) float64 { + switch name { + case "P": + return b.factorP + case "I": + return b.factorI + case "D": + return b.factorD + } + return 1 +} + +// suggestPID aggregates transients into per-rpm-band scaling factors. +func suggestPID(ts []transient, pidYSP []float64, cfg suggestCfg) []bandSuggestion { + type acc struct { + overshoot, rise, ss float64 + crossings, n int + } + bands := make(map[int]*acc) + for _, t := range ts { + row := nearestIndex(pidYSP, t.rpm) + a := bands[row] + if a == nil { + a = &acc{} + bands[row] = a + } + a.overshoot += t.overshoot + a.rise += float64(t.riseSamples) + a.ss += t.ssError + a.crossings += t.crossings + a.n++ + } + + var out []bandSuggestion + for row, a := range bands { + n := float64(a.n) + avgOver := a.overshoot / n + avgRise := a.rise / n + avgSS := a.ss / n + avgCross := float64(a.crossings) / n + + b := bandSuggestion{row: row, rpm: pidYSP[row], factorP: 1, factorI: 1, factorD: 1, events: a.n} + switch { + case avgOver > cfg.overshootHi || avgCross >= 2: + b.factorP = cfg.trim + b.factorD = cfg.trim + b.reason = fmt.Sprintf("overshoot %.0f mg/c, %.1f crossings", avgOver, avgCross) + case avgRise > float64(cfg.riseHi): + b.factorP = cfg.boost + b.reason = fmt.Sprintf("slow rise (%.0f samples)", avgRise) + } + if math.Abs(avgSS) > cfg.ssHi { + b.factorI = cfg.boost + if b.reason != "" { + b.reason += "; " + } + b.reason += fmt.Sprintf("steady error %.0f mg/c", avgSS) + } + if b.factorP != 1 || b.factorI != 1 || b.factorD != 1 { + out = append(out, b) + } + } + sort.Slice(out, func(i, j int) bool { return out[i].rpm < out[j].rpm }) + return out +} + +func signOf(v float64) float64 { + switch { + case v > 0: + return 1 + case v < 0: + return -1 + default: + return 0 + } +} + +// --- UI wiring --- + +// computePIDSuggestions runs the transient analysis over the loaded logs and +// fills each editor's suggestion panel. Returns a user-facing status string. +func (bt *BoostTuner) computePIDSuggestions() string { + if len(bt.values) == 0 { + return "Load logs first (Logs tab)." + } + rpm, ok1 := bt.series("rpm") + loadDiff, ok2 := bt.series("loadDiff") + pwm2pct, ok3 := bt.series("pwm2pct") + if !ok1 || !ok2 || !ok3 { + return "Need rpm, LoadDiff and the 2% flag channels." + } + ts := detectTransients(pidSuggestInputs{ + n: bt.nrecords, rpm: rpm, loadDiff: loadDiff, pwm2pct: pwm2pct, + }, defaultTransientCfg()) + if len(ts) == 0 { + bt.renderSuggestions(nil) + return "No boost transients detected." + } + var ysp []float64 + if ed := bt.pidEditors["P"]; ed != nil { + ysp = ed.axisY + } + if len(ysp) == 0 { + return "PID maps/axes not found in this binary." + } + sugg := suggestPID(ts, ysp, defaultSuggestCfg()) + bt.renderSuggestions(sugg) + return fmt.Sprintf("%d transients, %d band suggestion(s).", len(ts), len(sugg)) +} + +// renderSuggestions populates every editor's suggestion box with the rows +// relevant to its map. +func (bt *BoostTuner) renderSuggestions(sugg []bandSuggestion) { + for name, ed := range bt.pidEditors { + ed.suggestBox.Objects = ed.suggestBox.Objects[:0] + var rows []bandSuggestion + for _, b := range sugg { + if b.factorFor(name) != 1 { + rows = append(rows, b) + } + } + if len(rows) == 0 { + ed.suggestBox.Add(widget.NewLabel("No suggestions for this map.")) + ed.suggestBox.Refresh() + continue + } + factors := map[int]float64{} + for _, b := range rows { + b := b + factors[b.row] = b.factorFor(name) + lbl := widget.NewLabel(fmt.Sprintf("%.0f rpm: ×%.2f (%s)", b.rpm, b.factorFor(name), b.reason)) + lbl.Wrapping = fyne.TextWrapWord + apply := widget.NewButton("Apply", func() { + ed.scaleRows(map[int]float64{b.row: b.factorFor(name)}) + }) + apply.Importance = widget.LowImportance + ed.suggestBox.Add(container.NewBorder(nil, nil, nil, apply, lbl)) + } + allBtn := widget.NewButton("Apply all", func() { ed.scaleRows(factors) }) + allBtn.Importance = widget.LowImportance + ed.suggestBox.Add(allBtn) + ed.suggestBox.Refresh() + } +} diff --git a/pkg/widgets/boosttuner/pidtune_test.go b/pkg/widgets/boosttuner/pidtune_test.go new file mode 100644 index 00000000..9bbfb1f5 --- /dev/null +++ b/pkg/widgets/boosttuner/pidtune_test.go @@ -0,0 +1,79 @@ +package boosttuner + +import "testing" + +// buildTrace turns a sequence of (rpm, loadDiff, pwm2pct) samples into inputs. +func buildTrace(samples [][3]float64) pidSuggestInputs { + in := pidSuggestInputs{n: len(samples)} + for _, s := range samples { + in.rpm = append(in.rpm, s[0]) + in.loadDiff = append(in.loadDiff, s[1]) + in.pwm2pct = append(in.pwm2pct, s[2]) + } + return in +} + +// TestDetectTransients_OvershootEvent feeds a spool-up that overshoots and +// oscillates, and checks the detector measures it. +func TestDetectTransients_OvershootEvent(t *testing.T) { + // error: large positive -> crosses 0 -> negative (overshoot) -> back up. + trace := [][3]float64{ + {3000, 150, 0}, // onset (err>startErr, engaged) + {3000, 90, 0}, + {3000, 30, 0}, + {3000, -40, 0}, // crossing 1, overshoot 40 + {3000, -60, 0}, // overshoot 60 + {3000, 10, 0}, // crossing 2, settled (|err|<=20) + {3000, 5, 0}, + {3000, 4, 0}, + } + ts := detectTransients(buildTrace(trace), defaultTransientCfg()) + if len(ts) != 1 { + t.Fatalf("got %d transients, want 1", len(ts)) + } + got := ts[0] + if got.overshoot < 59 || got.overshoot > 61 { + t.Errorf("overshoot = %v, want ~60", got.overshoot) + } + if got.crossings < 2 { + t.Errorf("crossings = %d, want >=2", got.crossings) + } + if !got.settled { + t.Errorf("expected event to settle") + } +} + +// TestSuggestPID_TrimsOnOvershoot checks an overshooting band yields a P/D trim. +func TestSuggestPID_TrimsOnOvershoot(t *testing.T) { + ysp := []float64{1000, 2000, 3000, 4000, 5000, 6000} + ts := []transient{ + {rpm: 3000, overshoot: 80, crossings: 3, riseSamples: 6, settled: true, ssError: 2}, + {rpm: 3100, overshoot: 70, crossings: 2, riseSamples: 5, settled: true, ssError: 1}, + } + sugg := suggestPID(ts, ysp, defaultSuggestCfg()) + if len(sugg) != 1 { + t.Fatalf("got %d suggestions, want 1", len(sugg)) + } + b := sugg[0] + if b.factorP >= 1 || b.factorD >= 1 { + t.Errorf("expected P/D trim (<1), got P=%.2f D=%.2f", b.factorP, b.factorD) + } + if b.factorI != 1 { + t.Errorf("expected no I change for small steady error, got %.2f", b.factorI) + } +} + +// TestSuggestPID_AddsIOnSteadyError checks a persistent steady error raises I. +func TestSuggestPID_AddsIOnSteadyError(t *testing.T) { + ysp := []float64{1000, 2000, 3000, 4000} + ts := []transient{ + {rpm: 2000, overshoot: 5, crossings: 0, riseSamples: 8, settled: true, ssError: 40}, + } + sugg := suggestPID(ts, ysp, defaultSuggestCfg()) + if len(sugg) != 1 { + t.Fatalf("got %d suggestions, want 1", len(sugg)) + } + if sugg[0].factorI <= 1 { + t.Errorf("expected I boost (>1), got %.2f", sugg[0].factorI) + } +} diff --git a/pkg/widgets/boosttuner/regmap.go b/pkg/widgets/boosttuner/regmap.go new file mode 100644 index 00000000..e17afbaf --- /dev/null +++ b/pkg/widgets/boosttuner/regmap.go @@ -0,0 +1,375 @@ +package boosttuner + +import ( + "fmt" + "math" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/layout" + "github.com/roffe/txlogger/pkg/widgets/mapviewer" +) + +// RegMap symbol and its axes in the binary. RegMap is row-major [rpm][load]: +// rows follow n_EngSP, columns follow SetLoadXSP. +const ( + symRegMap = "BoostCal.RegMap" + symSetLoadXSP = "BoostCal.SetLoadXSP" // X axis: load set value (mg/c) + symNEngSP = "BoostCal.n_EngSP" // Y axis: engine speed (rpm) +) + +// regMapView lists the selectable views for the learned map. +const ( + viewCurrent = "Current" + viewLearned = "Learned (what gets written)" + viewDelta = "Delta" + viewCoverage = "Coverage (samples)" +) + +func (bt *BoostTuner) buildRegMapTab() fyne.CanvasObject { + bt.rmStatus = widget.NewLabel("Load logs, then click Analyze.") + bt.rmStatus.Wrapping = fyne.TextWrapWord + + analyzeBtn := widget.NewButtonWithIcon("Analyze", theme.SearchIcon(), func() { + if err := bt.analyzeRegMap(); err != nil { + bt.rmStatus.SetText(err.Error()) + return + } + bt.refreshRegMapDisplay() + }) + analyzeBtn.Importance = widget.HighImportance + + bt.rmView = widget.NewSelect( + []string{viewCurrent, viewLearned, viewDelta, viewCoverage}, + func(string) { bt.refreshRegMapDisplay() }, + ) + bt.rmView.SetSelected(viewLearned) + + applyBtn := widget.NewButtonWithIcon("Apply to binary", theme.DocumentSaveIcon(), bt.applyRegMap) + if bt.cfg.Save == nil { + applyBtn.Disable() + } + + controls := container.NewVBox( + analyzeBtn, + labeledRow("View", bt.rmView), + widget.NewSeparator(), + widget.NewLabelWithStyle("Sample filter", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + bt.slider("On-target |error| (mg/c)", 5, 200, 5, bt.onTarget, func(v float64) { bt.onTarget = v }), + bt.slider("Max rpm step (rpm)", 20, 500, 10, bt.rpmStab, func(v float64) { bt.rpmStab = v }), + bt.slider("Max load step (mg/c)", 10, 300, 10, bt.loadStab, func(v float64) { bt.loadStab = v }), + widget.NewSeparator(), + widget.NewLabelWithStyle("Apply", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + bt.slider("Min samples / cell", 1, 50, 1, float64(bt.minSamples), func(v float64) { bt.minSamples = int(v); bt.refreshRegMapDisplay() }), + bt.slider("Blend %", 0, 100, 5, bt.blend*100, func(v float64) { bt.blend = v / 100; bt.refreshRegMapDisplay() }), + applyBtn, + widget.NewSeparator(), + bt.rmStatus, + ) + + bt.rmDisplay = container.NewStack(container.NewCenter( + widget.NewLabel("Learn BoostCal.RegMap from the loaded logs."), + )) + + controlScroll := container.NewVScroll(controls) + controlScroll.SetMinSize(fyne.NewSize(250, 0)) + split := container.NewHSplit(controlScroll, bt.rmDisplay) + split.Offset = 0.28 + return split +} + +// slider builds a labelled slider with a live value readout that calls onChange. +func (bt *BoostTuner) slider(label string, min, max, step, init float64, onChange func(float64)) fyne.CanvasObject { + val := widget.NewLabel(formatFloat(init)) + s := widget.NewSlider(min, max) + s.Step = step + s.SetValue(init) + s.OnChanged = func(v float64) { + val.SetText(formatFloat(v)) + onChange(v) + } + return container.NewVBox( + widget.NewLabel(label), + container.NewBorder(nil, nil, nil, layout.NewFixedWidth(48, val), s), + ) +} + +// analyzeRegMap reads the current RegMap and its axes from the binary, then learns +// a new map from the logs by folding the loop's corrections back into the +// feedforward. See the package doc for the control-law rationale. +func (bt *BoostTuner) analyzeRegMap() error { + if len(bt.values) == 0 { + return fmt.Errorf("load a log file first (Logs tab)") + } + if missing := bt.missingChannels(); len(missing) > 0 { + return fmt.Errorf("missing channels: %v", missing) + } + if err := bt.loadRegMapFromBinary(); err != nil { + return err + } + + in := regMapInputs{ + n: bt.nrecords, + rpm: mustSeries(bt.series("rpm")), + setv: mustSeries(bt.series("setValue")), + regCon: mustSeries(bt.series("regCon")), + pFac: mustSeries(bt.series("pFac")), + iFac: mustSeries(bt.series("iFac")), + dFac: mustSeries(bt.series("dFac")), + adap: mustSeries(bt.series("adaption")), + loadDiff: mustSeries(bt.series("loadDiff")), + pwmCalc: mustSeries(bt.series("pwmCalc")), + pwm2pct: mustSeries(bt.series("pwm2pct")), + } + p := regMapParams{ + axisX: bt.rmAxisX, axisY: bt.rmAxisY, + cols: bt.rmCols, rows: bt.rmRows, + current: bt.rmCurrent, + onTarget: bt.onTarget, rpmStab: bt.rpmStab, loadStab: bt.loadStab, + } + + learned, cnt, used, filtered := learnRegMap(in, p) + bt.rmLearned, bt.rmCounts = learned, cnt + filled := 0 + for _, c := range cnt { + if c > 0 { + filled++ + } + } + bt.rmBuilt = true + bt.rmStatus.SetText(fmt.Sprintf("Used %d samples (%d filtered). %d/%d cells have data.", + used, filtered, filled, bt.rmCols*bt.rmRows)) + return nil +} + +// regMapInputs holds the row-aligned log channels the learner consumes. All +// slices have length n (NaN where a sample is missing). +type regMapInputs struct { + n int + rpm, setv, regCon, pFac, iFac, dFac, adap, loadDiff, pwmCalc, pwm2pct []float64 +} + +// regMapParams holds the map geometry (from the binary) and the sample filter. +type regMapParams struct { + axisX, axisY []float64 + cols, rows int + current []float64 + onTarget, rpmStab, loadStab float64 +} + +// learnRegMap folds the loop's corrections back into the feedforward map. For +// each accepted sample it adds RegConValue+PFac+IFac+DFac+Adaption (converted to +// %) to the cell its (load, rpm) lands on, then averages per cell. Empty cells +// return the current value so they contribute a zero delta. Pure: no UI/state. +func learnRegMap(in regMapInputs, p regMapParams) (learned []float64, counts []int, used, filtered int) { + size := p.cols * p.rows + sum := make([]float64, size) + counts = make([]int, size) + + prevRPM, prevLoad := math.NaN(), math.NaN() + for i := 0; i < in.n; i++ { + r, s := in.rpm[i], in.setv[i] + if anyNaN(r, s, in.regCon[i], in.pFac[i], in.iFac[i], in.dFac[i], in.adap[i], in.loadDiff[i], in.pwmCalc[i], in.pwm2pct[i]) { + prevRPM, prevLoad = r, s + continue + } + ok := acceptSample(p, r, s, in.loadDiff[i], in.pwmCalc[i], in.pwm2pct[i], prevRPM, prevLoad) + prevRPM, prevLoad = r, s + if !ok { + filtered++ + continue + } + target := (in.regCon[i] + in.pFac[i] + in.iFac[i] + in.dFac[i] + in.adap[i]) / dutyRawPerPct + c := nearestIndex(p.axisX, s) + row := nearestIndex(p.axisY, r) + idx := row*p.cols + c + sum[idx] += target + counts[idx]++ + used++ + } + + learned = make([]float64, size) + for i := range sum { + if counts[i] > 0 { + learned[i] = sum[i] / float64(counts[i]) + } else { + learned[i] = p.current[i] + } + } + return learned, counts, used, filtered +} + +// acceptSample reports whether a sample is trustworthy for learning the +// feedforward: on target, steady (not mid-transient), in closed loop, and not at +// the duty rails. +func acceptSample(p regMapParams, rpm, load, loadDiff, pwmCalc, pwm2pct, prevRPM, prevLoad float64) bool { + if math.Abs(loadDiff) > p.onTarget { + return false + } + if pwm2pct != 0 { // open-loop / forced 2% + return false + } + if pwmCalc <= 20 || pwmCalc >= 980 { // clamped at a rail + return false + } + if !math.IsNaN(prevRPM) && math.Abs(rpm-prevRPM) > p.rpmStab { + return false + } + if !math.IsNaN(prevLoad) && math.Abs(load-prevLoad) > p.loadStab { + return false + } + return true +} + +// mustSeries unwraps a resolved channel; analyzeRegMap guarantees presence via +// missingChannels before calling, so the bool is discarded here. +func mustSeries(s []float64, _ bool) []float64 { return s } + +// loadRegMapFromBinary reads BoostCal.RegMap and its axis breakpoints from the +// loaded binary into the tuner's state. +func (bt *BoostTuner) loadRegMapFromBinary() error { + if bt.cfg.Symbols == nil { + return fmt.Errorf("no binary loaded") + } + x, err := bt.readSymbol(symSetLoadXSP) + if err != nil { + return err + } + y, err := bt.readSymbol(symNEngSP) + if err != nil { + return err + } + z, err := bt.readSymbol(symRegMap) + if err != nil { + return err + } + if len(x)*len(y) != len(z) { + return fmt.Errorf("RegMap size %d != %d x %d axes", len(z), len(x), len(y)) + } + bt.rmAxisX, bt.rmAxisY = x, y + bt.rmCols, bt.rmRows = len(x), len(y) + bt.rmCurrent = z + return nil +} + +// readSymbol returns a symbol's values in engineering units. +func (bt *BoostTuner) readSymbol(name string) ([]float64, error) { + if bt.cfg.Symbols == nil { + return nil, fmt.Errorf("no binary loaded") + } + s := bt.cfg.Symbols.GetByName(name) + if s == nil { + return nil, fmt.Errorf("symbol %s not found in binary", name) + } + return s.Float64s(), nil +} + +// writeTarget returns the map that "Apply" would write: filled cells blended +// toward the learned value, everything else left at its current value. +func (bt *BoostTuner) writeTarget() []float64 { + out := make([]float64, len(bt.rmCurrent)) + copy(out, bt.rmCurrent) + for i := range out { + if bt.rmCounts[i] >= bt.minSamples { + out[i] = bt.rmCurrent[i] + bt.blend*(bt.rmLearned[i]-bt.rmCurrent[i]) + } + } + return out +} + +func (bt *BoostTuner) applyRegMap() { + if !bt.rmBuilt { + bt.rmStatus.SetText("Analyze first.") + return + } + if bt.cfg.Save == nil { + bt.rmStatus.SetText("No binary to write to.") + return + } + data := bt.writeTarget() + changed := 0 + for i := range data { + if data[i] != bt.rmCurrent[i] { + changed++ + } + } + if err := bt.cfg.Save(symRegMap, data); err != nil { + bt.rmStatus.SetText("Save failed: " + err.Error()) + return + } + bt.rmCurrent = data // the binary now holds these values + bt.rmStatus.SetText(fmt.Sprintf("Wrote %s: %d cells changed.", symRegMap, changed)) + bt.refreshRegMapDisplay() +} + +// refreshRegMapDisplay rebuilds the map viewer for the selected view. +func (bt *BoostTuner) refreshRegMapDisplay() { + if !bt.rmBuilt { + return + } + var z []float64 + var label string + zPrec := 1 + switch bt.rmView.Selected { + case viewCurrent: + z, label = bt.rmCurrent, "Current duty %" + case viewDelta: + target := bt.writeTarget() + z = make([]float64, len(target)) + for i := range z { + z[i] = target[i] - bt.rmCurrent[i] + } + label = "Delta duty %" + case viewCoverage: + z = make([]float64, len(bt.rmCounts)) + for i := range z { + z[i] = float64(bt.rmCounts[i]) + } + label, zPrec = "Samples", 0 + default: // viewLearned + z, label = bt.writeTarget(), "Learned duty %" + } + + mv, err := mapviewer.New(&mapviewer.Config{ + Name: symRegMap, + XData: bt.rmAxisX, + YData: bt.rmAxisY, + ZData: z, + XPrecision: 0, + YPrecision: 0, + ZPrecision: zPrec, + XLabel: "Load set value (mg/c)", + YLabel: "Engine speed (rpm)", + ZLabel: label, + MeshView: true, + MeshRenderer: bt.cfg.MeshRenderer, + Editable: false, + ColorblindMode: bt.cfg.Colorblind, + SaveECUFunc: func([]float64) {}, + OnUpdateCell: func(int, []float64) {}, + }) + if err != nil { + bt.rmDisplay.Objects = []fyne.CanvasObject{container.NewCenter(widget.NewLabel(err.Error()))} + bt.rmDisplay.Refresh() + return + } + bt.rmDisplay.Objects = []fyne.CanvasObject{mv} + bt.rmDisplay.Refresh() +} + +// --- small helpers --- + +func labeledRow(label string, obj fyne.CanvasObject) fyne.CanvasObject { + return container.NewBorder(nil, nil, widget.NewLabel(label), nil, obj) +} + +func anyNaN(vals ...float64) bool { + for _, v := range vals { + if math.IsNaN(v) { + return true + } + } + return false +} diff --git a/pkg/widgets/boosttuner/regmap_test.go b/pkg/widgets/boosttuner/regmap_test.go new file mode 100644 index 00000000..c0065edf --- /dev/null +++ b/pkg/widgets/boosttuner/regmap_test.go @@ -0,0 +1,83 @@ +package boosttuner + +import ( + "math" + "testing" +) + +// TestLearnRegMap_FoldsCorrectionsToPercent checks the core learning behaviour: +// the learned cell value is (RegConValue + P + I + D + Adaption) averaged and +// converted from raw 0.1% units to %, and that the sample filter rejects +// off-target, clamped, open-loop and transient samples. +func TestLearnRegMap_FoldsCorrectionsToPercent(t *testing.T) { + axisX := []float64{800, 900, 1000} // load + axisY := []float64{1000, 2000} // rpm + cols, rows := len(axisX), len(axisY) + current := make([]float64, cols*rows) // all zero + + // Helper to append one sample to every channel. + var in regMapInputs + add := func(rpm, load, regCon, p, i, d, adap, loadDiff, pwm, twopct float64) { + in.rpm = append(in.rpm, rpm) + in.setv = append(in.setv, load) + in.regCon = append(in.regCon, regCon) + in.pFac = append(in.pFac, p) + in.iFac = append(in.iFac, i) + in.dFac = append(in.dFac, d) + in.adap = append(in.adap, adap) + in.loadDiff = append(in.loadDiff, loadDiff) + in.pwmCalc = append(in.pwmCalc, pwm) + in.pwm2pct = append(in.pwm2pct, twopct) + } + + // Two good samples at cell (load=900 -> col1, rpm=2000 -> row1). + // raw sum = 400 + 30 + 20 + 0 + 50 = 500 -> 50.0% ; pwmCalc 500 (in band). + add(2000, 900, 400, 30, 20, 0, 50, 5, 500, 0) + add(2000, 905, 400, 30, 20, 0, 50, -5, 500, 0) // steady (small steps) + + // Rejected: off target (loadDiff beyond onTarget). + add(2000, 900, 400, 30, 20, 0, 50, 500, 500, 0) + // Rejected: open-loop flag set. + add(2000, 900, 400, 30, 20, 0, 50, 5, 500, 1) + // Rejected: clamped at the upper rail. + add(2000, 900, 400, 30, 20, 0, 50, 5, 980, 0) + in.n = len(in.rpm) + + p := regMapParams{ + axisX: axisX, axisY: axisY, cols: cols, rows: rows, current: current, + onTarget: 30, rpmStab: 150, loadStab: 50, + } + learned, counts, used, filtered := learnRegMap(in, p) + + if used != 2 { + t.Fatalf("used = %d, want 2", used) + } + if filtered != 3 { + t.Fatalf("filtered = %d, want 3", filtered) + } + cell := 1*cols + 1 // row1 (rpm 2000), col1 (load 900) + if counts[cell] != 2 { + t.Fatalf("counts[cell] = %d, want 2", counts[cell]) + } + if math.Abs(learned[cell]-50.0) > 1e-9 { + t.Fatalf("learned[cell] = %v, want 50.0", learned[cell]) + } + // Untouched cells fall back to current (0 here). + if learned[0] != current[0] { + t.Fatalf("empty cell learned = %v, want current %v", learned[0], current[0]) + } +} + +// TestAcceptSample_Steadiness verifies the transient gate uses the previous +// sample's rpm/load steps. +func TestAcceptSample_Steadiness(t *testing.T) { + p := regMapParams{onTarget: 30, rpmStab: 150, loadStab: 50} + // Big rpm jump from the previous sample -> rejected. + if acceptSample(p, 3000, 900, 0, 500, 0, 2000, 900) { + t.Fatal("expected rejection on large rpm step") + } + // First sample (prev = NaN) with everything in band -> accepted. + if !acceptSample(p, 3000, 900, 0, 500, 0, math.NaN(), math.NaN()) { + t.Fatal("expected acceptance for first in-band sample") + } +} diff --git a/pkg/widgets/boosttuner/sim.go b/pkg/widgets/boosttuner/sim.go new file mode 100644 index 00000000..cc00ab91 --- /dev/null +++ b/pkg/widgets/boosttuner/sim.go @@ -0,0 +1,278 @@ +package boosttuner + +import ( + "math" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/widgets/plotter" +) + +// The replay simulator re-runs the ECU boost control law (from Boost.c) over a +// logged session using the *current* maps, so the effect of map edits on the +// controller's duty output can be inspected before flashing. +// +// IMPORTANT limitation: it drives the law with the *logged* error (LoadDiff), +// because predicting the resulting airmass would need a turbo/engine plant model +// we do not have. So it shows how the control effort (PWM and the P/I/D split) +// would differ for the same measured error — useful for spotting instability or +// I-windup — but it does NOT predict the resulting boost. + +// bracket returns the two indices of an ascending table that surround v and the +// interpolation fraction between them, clamping to the ends (so values outside +// the table hold the edge value, matching the ECU's TAB/MAT routines). +func bracket(tab []float64, v float64) (i0, i1 int, frac float64) { + n := len(tab) + if n <= 1 { + return 0, 0, 0 + } + if v <= tab[0] { + return 0, 0, 0 + } + if v >= tab[n-1] { + return n - 1, n - 1, 0 + } + for i := 0; i < n-1; i++ { + if v >= tab[i] && v <= tab[i+1] { + span := tab[i+1] - tab[i] + if span == 0 { + return i, i, 0 + } + return i, i + 1, (v - tab[i]) / span + } + } + return n - 1, n - 1, 0 +} + +// bilerp bilinearly interpolates a row-major map z (rows follow ytab, cols follow +// xtab) at (x, y), clamping outside the axis ranges. This is the Go stand-in for +// the ECU's MATs16 routines (faithful, not bit-exact). +func bilerp(xtab, ytab, z []float64, x, y float64) float64 { + cols := len(xtab) + x0, x1, xf := bracket(xtab, x) + y0, y1, yf := bracket(ytab, y) + z00 := z[y0*cols+x0] + z10 := z[y0*cols+x1] + z01 := z[y1*cols+x0] + z11 := z[y1*cols+x1] + top := z00 + (z10-z00)*xf + bot := z01 + (z11-z01)*xf + return top + (bot-top)*yf +} + +// simInputs holds the row-aligned log channels the replay consumes (all length n, +// raw units: duty terms in 0.1%, errors in mg/c). +type simInputs struct { + n int + rpm, setv, loadDiff, regCon, pFac, iFac, dFac, adap, pwmCalc, pwm2pct []float64 +} + +// simMaps holds the maps and constants the law uses. regMapRaw is in raw 0.1% +// units (the % map symbol scaled by dutyRawPerPct). +type simMaps struct { + setLoadXSP, nEngSP, regMapRaw []float64 + pidXSP, pidYSP []float64 + pMap, iMap, dMap []float64 + iFacMax, filterFactor float64 +} + +// simOutput is the replay result, all in raw 0.1% duty units. +type simOutput struct { + predicted, regCon, pFac, iFac, dFac []float64 +} + +// simulate replays the control law. It recomputes RegConValue and the P/I/D terms +// from the maps while carrying the logged environment residual (E85/altitude/temp/ +// noise + any rounding) unchanged, so only map-driven differences show. +func simulate(in simInputs, m simMaps) simOutput { + out := simOutput{ + predicted: make([]float64, in.n), + regCon: make([]float64, in.n), + pFac: make([]float64, in.n), + iFac: make([]float64, in.n), + dFac: make([]float64, in.n), + } + var iBuff, iFacAcc, dFacState, loadDiffOld float64 + haveOld := false + + for i := 0; i < in.n; i++ { + if anyNaN(in.rpm[i], in.setv[i], in.loadDiff[i], in.regCon[i], in.pFac[i], in.iFac[i], in.dFac[i], in.adap[i], in.pwmCalc[i], in.pwm2pct[i]) { + out.predicted[i], out.regCon[i] = math.NaN(), math.NaN() + out.pFac[i], out.iFac[i], out.dFac[i] = math.NaN(), math.NaN(), math.NaN() + haveOld = false + continue + } + // Residual = everything in the logged PWMCalc the maps don't drive. + env := in.pwmCalc[i] - (in.regCon[i] + in.pFac[i] + in.iFac[i] + in.dFac[i] + in.adap[i]) + regConSim := bilerp(m.setLoadXSP, m.nEngSP, m.regMapRaw, in.setv[i], in.rpm[i]) + out.regCon[i] = regConSim + + if in.pwm2pct[i] != 0 { // open loop: PID ramped off + iBuff, iFacAcc, dFacState = 0, 0, 0 + haveOld = false + out.predicted[i] = regConSim + in.adap[i] + env + continue + } + + ld := in.loadDiff[i] + pConst := bilerp(m.pidXSP, m.pidYSP, m.pMap, ld, in.rpm[i]) + iConst := bilerp(m.pidXSP, m.pidYSP, m.iMap, ld, in.rpm[i]) + dConst := bilerp(m.pidXSP, m.pidYSP, m.dMap, ld, in.rpm[i]) + + pFac := ld * pConst / 100 + iBuff += iConst * ld + if iBuff > 1000 { + iFacAcc += iBuff / 1000 + iBuff = 0 + if iFacAcc > m.iFacMax { + iFacAcc = m.iFacMax + } + } else if iBuff < -1000 { + iFacAcc += iBuff / 1000 + iBuff = 0 + if iFacAcc < -m.iFacMax { + iFacAcc = -m.iFacMax + } + } + if !haveOld { + loadDiffOld = ld + haveOld = true + } + dFacState = ((ld-loadDiffOld)*dConst + dFacState*m.filterFactor) / (20 + m.filterFactor) + loadDiffOld = ld + + out.pFac[i], out.iFac[i], out.dFac[i] = pFac, iFacAcc, dFacState + out.predicted[i] = regConSim + pFac + iFacAcc + dFacState + in.adap[i] + env + } + return out +} + +// --- UI --- + +func (bt *BoostTuner) buildSimTab() fyne.CanvasObject { + status := widget.NewLabel("") + status.Wrapping = fyne.TextWrapWord + + intro := widget.NewLabel( + "Replays the ECU boost control law over the loaded logs using the current " + + "maps (RegMap from the binary; P/I/D from the editors). It predicts the " + + "controller's duty output for the logged error — it does NOT predict the " + + "resulting boost (no engine/turbo model). Use it to check edits don't make " + + "the duty oscillate or wind up.") + intro.Wrapping = fyne.TextWrapWord + + display := container.NewStack(container.NewCenter( + widget.NewLabel("Load logs, then click Simulate."), + )) + + simBtn := widget.NewButtonWithIcon("Simulate", theme.MediaPlayIcon(), func() { + values, err := bt.runSimulation() + if err != nil { + status.SetText(err.Error()) + return + } + order := []string{"PWMCalc logged", "PWMCalc predicted", "P (sim)", "I (sim)", "D (sim)"} + p := plotter.NewPlotter(values, plotter.WithOrder(order)) + display.Objects = []fyne.CanvasObject{p} + display.Refresh() + status.SetText("Simulated. Toggle series in the legend.") + }) + + header := container.NewVBox(intro, container.NewBorder(nil, nil, nil, status, simBtn)) + return container.NewBorder(header, nil, nil, nil, display) +} + +// runSimulation gathers inputs and current maps, runs the replay and returns the +// named series for the plotter. +func (bt *BoostTuner) runSimulation() (map[string][]float64, error) { + if len(bt.values) == 0 { + return nil, errString("load logs first (Logs tab)") + } + if missing := bt.missingChannels(); len(missing) > 0 { + return nil, errString("missing channels for simulation") + } + in := simInputs{ + n: bt.nrecords, + rpm: mustSeries(bt.series("rpm")), + setv: mustSeries(bt.series("setValue")), + loadDiff: mustSeries(bt.series("loadDiff")), + regCon: mustSeries(bt.series("regCon")), + pFac: mustSeries(bt.series("pFac")), + iFac: mustSeries(bt.series("iFac")), + dFac: mustSeries(bt.series("dFac")), + adap: mustSeries(bt.series("adaption")), + pwmCalc: mustSeries(bt.series("pwmCalc")), + pwm2pct: mustSeries(bt.series("pwm2pct")), + } + + m, err := bt.loadSimMaps() + if err != nil { + return nil, err + } + out := simulate(in, m) + + return map[string][]float64{ + "PWMCalc logged": in.pwmCalc, + "PWMCalc predicted": out.predicted, + "P (sim)": out.pFac, + "I (sim)": out.iFac, + "D (sim)": out.dFac, + }, nil +} + +// loadSimMaps reads the maps and constants for the replay: RegMap and its axes +// from the binary, P/I/D from the editors (so edits/suggestions are reflected), +// IFacMax and FilterFactor from the binary. +func (bt *BoostTuner) loadSimMaps() (simMaps, error) { + var m simMaps + var err error + if m.setLoadXSP, err = bt.readSymbol(symSetLoadXSP); err != nil { + return m, err + } + if m.nEngSP, err = bt.readSymbol(symNEngSP); err != nil { + return m, err + } + regPct, err := bt.readSymbol(symRegMap) + if err != nil { + return m, err + } + m.regMapRaw = make([]float64, len(regPct)) + for i, v := range regPct { + m.regMapRaw[i] = v * dutyRawPerPct // % -> raw 0.1% + } + if m.pidXSP, err = bt.readSymbol(symPIDXSP); err != nil { + return m, err + } + if m.pidYSP, err = bt.readSymbol(symPIDYSP); err != nil { + return m, err + } + m.pMap = bt.pidMapData("P", symPMap) + m.iMap = bt.pidMapData("I", symIMap) + m.dMap = bt.pidMapData("D", symDMap) + + if v, err := bt.readSymbol("BoostCal.IFacMax"); err == nil && len(v) > 0 { + m.iFacMax = v[0] + } else { + m.iFacMax = 350 + } + if v, err := bt.readSymbol("BoostCal.FilterFactor"); err == nil && len(v) > 0 { + m.filterFactor = v[0] + } + return m, nil +} + +// pidMapData returns the editor's live (possibly edited) map data, falling back +// to the binary if the editor is absent. +func (bt *BoostTuner) pidMapData(name, symName string) []float64 { + if ed := bt.pidEditors[name]; ed != nil && len(ed.data) > 0 { + return ed.data + } + v, _ := bt.readSymbol(symName) + return v +} + +type errString string + +func (e errString) Error() string { return string(e) } diff --git a/pkg/widgets/boosttuner/sim_test.go b/pkg/widgets/boosttuner/sim_test.go new file mode 100644 index 00000000..91339dfd --- /dev/null +++ b/pkg/widgets/boosttuner/sim_test.go @@ -0,0 +1,74 @@ +package boosttuner + +import ( + "math" + "testing" +) + +func TestBilerp_CornersAndCenter(t *testing.T) { + xtab := []float64{0, 1} + ytab := []float64{0, 1} + z := []float64{0, 10, 20, 30} // [y][x]: (0,0)=0 (1,0)=10 (0,1)=20 (1,1)=30 + + cases := []struct { + x, y, want float64 + }{ + {0, 0, 0}, {1, 0, 10}, {0, 1, 20}, {1, 1, 30}, + {0.5, 0, 5}, {0, 0.5, 10}, {0.5, 0.5, 15}, + {-5, -5, 0}, // clamp low + {99, 99, 30}, // clamp high + } + for _, c := range cases { + if got := bilerp(xtab, ytab, z, c.x, c.y); math.Abs(got-c.want) > 1e-9 { + t.Errorf("bilerp(%v,%v) = %v, want %v", c.x, c.y, got, c.want) + } + } +} + +// TestSimulate_OpenLoopIdentity: with the loop disengaged and unchanged RegMap, +// the predicted PWM equals the logged PWMCalc (the residual carries everything). +func TestSimulate_OpenLoopIdentity(t *testing.T) { + m := simMaps{ + setLoadXSP: []float64{1000}, nEngSP: []float64{3000}, + regMapRaw: []float64{450}, // bilerp -> 450, matching logged regCon + pidXSP: []float64{0}, pidYSP: []float64{3000}, + pMap: []float64{0}, iMap: []float64{0}, dMap: []float64{0}, + iFacMax: 350, + } + in := simInputs{ + n: 1, rpm: []float64{3000}, setv: []float64{1000}, loadDiff: []float64{0}, + regCon: []float64{450}, pFac: []float64{0}, iFac: []float64{0}, dFac: []float64{0}, + adap: []float64{30}, pwmCalc: []float64{520}, pwm2pct: []float64{1}, // open loop + } + out := simulate(in, m) + if math.Abs(out.predicted[0]-520) > 1e-9 { + t.Fatalf("predicted = %v, want 520 (logged)", out.predicted[0]) + } +} + +// TestSimulate_ClosedLoopRecomputesP checks the P term is recomputed from the map +// and the environment residual is preserved. +func TestSimulate_ClosedLoopRecomputesP(t *testing.T) { + m := simMaps{ + setLoadXSP: []float64{1000}, nEngSP: []float64{3000}, + regMapRaw: []float64{450}, + pidXSP: []float64{0}, pidYSP: []float64{3000}, + pMap: []float64{100}, iMap: []float64{0}, dMap: []float64{0}, // P const 100 + iFacMax: 350, + } + // Logged decomposition: 450+5+20+0+30 = 505 == pwmCalc, so env residual = 0. + in := simInputs{ + n: 1, rpm: []float64{3000}, setv: []float64{1000}, loadDiff: []float64{10}, + regCon: []float64{450}, pFac: []float64{5}, iFac: []float64{20}, dFac: []float64{0}, + adap: []float64{30}, pwmCalc: []float64{505}, pwm2pct: []float64{0}, + } + out := simulate(in, m) + // P_sim = loadDiff*Pconst/100 = 10*100/100 = 10; I=0; D=0. + // predicted = 450 + 10 + 0 + 0 + 30 + env(0) = 490. + if math.Abs(out.pFac[0]-10) > 1e-9 { + t.Errorf("P_sim = %v, want 10", out.pFac[0]) + } + if math.Abs(out.predicted[0]-490) > 1e-9 { + t.Errorf("predicted = %v, want 490", out.predicted[0]) + } +} diff --git a/pkg/windows/mainWindow_toolbar.go b/pkg/windows/mainWindow_toolbar.go index 47494c34..b8980c5a 100644 --- a/pkg/windows/mainWindow_toolbar.go +++ b/pkg/windows/mainWindow_toolbar.go @@ -1,15 +1,65 @@ package windows import ( + "fmt" + "os" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/widgets/boosttuner" "github.com/roffe/txlogger/pkg/widgets/canflasher" "github.com/roffe/txlogger/pkg/widgets/matrixbuilder" "github.com/roffe/txlogger/pkg/widgets/multiwindow" ) +// openBoostTuner opens (or raises) the T7 boost auto-tuner. It reads the current +// BoostCal maps from the loaded binary and writes tuned maps back through a save +// closure that takes a one-time .bak of the file before the first write. +func (mw *MainWindow) openBoostTuner() { + if mw.fw == nil { + mw.Error(fmt.Errorf("no binary loaded")) + return + } + if w := mw.wm.HasWindow("Boost Auto-Tuner"); w != nil { + mw.wm.Raise(w) + return + } + save := func(symbolName string, data []float64) error { + sym := mw.fw.GetByName(symbolName) + if sym == nil { + return fmt.Errorf("symbol %s not found", symbolName) + } + if err := sym.SetData(sym.EncodeFloat64s(data)); err != nil { + return err + } + if mw.filename != "" { + if bak := mw.filename + ".bak"; !fileExists(bak) { + if orig, err := os.ReadFile(mw.filename); err == nil { + _ = os.WriteFile(bak, orig, 0o644) + } + } + } + return mw.fw.Save(mw.filename) + } + bt := boosttuner.New(boosttuner.Config{ + Symbols: mw.fw, + Save: save, + MeshRenderer: mw.settings.GetMeshRenderer(), + Colorblind: mw.settings.GetColorBlindMode(), + }) + inner := multiwindow.NewInnerWindow("Boost Auto-Tuner", bt) + inner.Icon = theme.GridIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(1100, 760)) +} + +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + // openMatrixBuilder opens (or raises) the matrix builder window. The builder // loads its own log files, so it is independent of any open log player. func (mw *MainWindow) openMatrixBuilder() { @@ -60,6 +110,7 @@ func (mw *MainWindow) newToolbar() *fyne.Container { }), ) + toolbar.Add(widget.NewButtonWithIcon("Boost", theme.MediaFastForwardIcon(), mw.openBoostTuner)) /* toolbar.Add(widget.NewButtonWithIcon("", theme.DocumentIcon(), func() { if w := mw.wm.HasWindow("txweb"); w != nil { From 9c0590fbf027efa5b1d4a89e3c9d50e677a4875c Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 17 Jun 2026 20:59:00 +0200 Subject: [PATCH 052/102] add initial AS2 support --- pkg/windows/mainWindow.go | 34 ++++++++---- pkg/windows/mainWindow_internal.go | 4 ++ pkg/windows/mainWindow_menu.go | 88 +++++++++++++++++++++++++++--- 3 files changed, 107 insertions(+), 19 deletions(-) diff --git a/pkg/windows/mainWindow.go b/pkg/windows/mainWindow.go index 90e0cb32..bd37032b 100644 --- a/pkg/windows/mainWindow.go +++ b/pkg/windows/mainWindow.go @@ -20,6 +20,7 @@ import ( "fyne.io/fyne/v2/widget" xwidget "fyne.io/x/fyne/widget" symbol "github.com/roffe/ecusymbol" + "github.com/roffe/ecusymbol/as2" "github.com/roffe/gocan" "github.com/roffe/gocan/proto" "github.com/roffe/txlogger/pkg/colors" @@ -61,16 +62,19 @@ func (s *SecretText) MouseUp(e *desktop.MouseEvent) { type MainWindow struct { fyne.Window - app fyne.App - menu *MainMenu - outputData binding.StringList - selects *mainWindowSelects - buttons *mainWindowButtons - counters *mainWindowCounters - loggingRunning bool - filename string - symbolList *symbollist.Widget - fw symbol.SymbolCollection + app fyne.App + menu *MainMenu + outputData binding.StringList + selects *mainWindowSelects + buttons *mainWindowButtons + counters *mainWindowCounters + loggingRunning bool + filename string + symbolList *symbollist.Widget + + as2 *as2.File + fw symbol.SymbolCollection + dlc datalogger.IClient gwclient proto.GocanClient buttonsDisabled bool @@ -398,6 +402,16 @@ func (mw *MainWindow) LoadLogfileCombined(filename string, reader io.ReadCloser, mw.Log("loaded log file " + filename + " in combined logplayer") } +func (mw *MainWindow) LoadAS2File(filename string) error { + f, err := as2.Load(filename) + if err != nil { + return fmt.Errorf("failed to load AS2 file: %w", err) + } + mw.as2 = f + mw.Log("Loaded AS2 file " + filename) + return nil +} + func (mw *MainWindow) LoadLogfile(filename string, r io.Reader, pos fyne.Position) { // Just filename, used for Window title fp := filepath.Base(filename) diff --git a/pkg/windows/mainWindow_internal.go b/pkg/windows/mainWindow_internal.go index 894addf4..b6cb9010 100644 --- a/pkg/windows/mainWindow_internal.go +++ b/pkg/windows/mainWindow_internal.go @@ -71,6 +71,10 @@ func (mw *MainWindow) onDropped(p fyne.Position, uris []fyne.URI) { for _, u := range uris { filename := u.Path() switch strings.ToLower(path.Ext(filename)) { + case ".as2": + if err := mw.LoadAS2File(filename); err != nil { + mw.Error(err) + } case ".bin": if err := mw.LoadSymbolsFromFile(filename); err != nil { mw.Error(err) diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index f4c91367..b84ce5c4 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -210,6 +210,17 @@ func (mw *MainWindow) setupMenu() { mw.Error(err) } }), + fyne.NewMenuItemWithIcon("Open AS2 file", theme.DocumentIcon(), func() { + cb := func(r fyne.URIReadCloser) { + defer r.Close() + filename := r.URI().Path() + mw.Log("Opening AS2 file " + filename) + if err := mw.LoadAS2File(filename); err != nil { + mw.Error(err) + } + } + widgets.SelectFile(cb, "AS2 file", "as2") + }), ) leading := []*fyne.Menu{ @@ -280,7 +291,35 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) return } - axis := symbol.GetInfo(typ, mapName) + var axis symbol.Axis + if mw.as2 != nil { + axis.Z = mapName + axes := mw.as2.Axes(mapName) + if len(axes) == 0 { + mw.Error(fmt.Errorf("map %q not found in as2 file", mapName)) + return + } + if len(axes) == 1 { + axis.Y = axes[0].SupportPoints + axis.YFrom = axes[0].Signal + } else { + for i, a := range axes { + log.Printf("Axis %d: %+v", i, a) + if i == 0 { + axis.X = a.SupportPoints + axis.XFrom = a.Signal + continue + } + if i == 1 { + axis.Y = a.SupportPoints + axis.YFrom = a.Signal + continue + } + } + } + } else { + axis = symbol.GetInfo(typ, mapName) + } windowName := axis.Z if title != "" { @@ -308,6 +347,12 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) symY := mw.fw.GetByName(axis.Y) symZ := mw.fw.GetByName(axis.Z) + if mw.as2 != nil { + if symZ != nil { + symZ.Correctionfactor = mw.as2.GetCorrectionfactor(mapName) + } + } + if symZ == nil { mw.Error(fmt.Errorf("failed to find symbol %s", axis.Z)) return @@ -473,16 +518,40 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) } var xPrecision, yPrecision, zPrecision int - if symX != nil { - xPrecision = symbol.GetPrecision(symX.Correctionfactor) - } - if symY != nil { - yPrecision = symbol.GetPrecision(symY.Correctionfactor) + if mw.as2 != nil { + if symX != nil { + xPrecision = mw.as2.Precision(axis.X) + log.Printf("Precision for %s: %d", axis.X, xPrecision) + //if xPrecision == 0 { + // log.Printf("Warning: precision for %s is 0, defaulting to 2", axis.X) + // xPrecision = 2 + //} + } + if symY != nil { + yPrecision = mw.as2.Precision(axis.Y) + log.Printf("Precision for %s: %d", axis.Y, yPrecision) + //if yPrecision == 0 { + // log.Printf("Warning: precision for %s is 0, defaulting to 2", axis.Y) + // yPrecision = 2 + //} + } + zPrecision = mw.as2.Precision(axis.Z) + log.Printf("Precision for %s: %d", axis.Z, zPrecision) + //if zPrecision == 0 { + // log.Printf("Warning: precision for %s is 0, defaulting to 2", axis.Z) + // zPrecision = 2 + //} + } else { + if symX != nil { + xPrecision = symbol.GetPrecision(symX.Correctionfactor) + } + if symY != nil { + yPrecision = symbol.GetPrecision(symY.Correctionfactor) + } + zPrecision = symbol.GetPrecision(symZ.Correctionfactor) } - zPrecision = symbol.GetPrecision(symZ.Correctionfactor) - cfg := &mapviewer.Config{ Name: symZ.Name, @@ -556,7 +625,8 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) }, } - mv, err := mapviewer.New(cfg) + var err error + mv, err = mapviewer.New(cfg) if err != nil { mw.Error(err) return From 87055afe1adf752e320b6c3c73c59d5bef78e3ce Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 17 Jun 2026 23:19:08 +0200 Subject: [PATCH 053/102] add coverage view --- pkg/widgets/matrixbuilder/matrixbuilder.go | 61 +++++++++++++++++++--- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/pkg/widgets/matrixbuilder/matrixbuilder.go b/pkg/widgets/matrixbuilder/matrixbuilder.go index 07c79982..92827c72 100644 --- a/pkg/widgets/matrixbuilder/matrixbuilder.go +++ b/pkg/widgets/matrixbuilder/matrixbuilder.go @@ -50,6 +50,13 @@ const ( defaultTolerance = 100 ) +// Display views for the built matrix. viewMatrix shows the learned Z values; +// viewCoverage shows how many samples landed in each cell. +const ( + viewMatrix = "Matrix" + viewCoverage = "Coverage (hits)" +) + var _ fyne.Widget = (*MatrixBuilder)(nil) type MatrixBuilder struct { @@ -71,6 +78,10 @@ type MatrixBuilder struct { yAxis []float64 zData []float64 + // counts holds the number of samples that landed in each cell during the + // last analyze, parallel to zData. It feeds the Coverage view. + counts []int + // xTolerance/yTolerance gate how close (as a percentage of the cell's // half-spacing) a sample must be to its nearest breakpoint to count as a // Z-hit on that axis. A sample is mapped only if it passes on both axes. @@ -91,6 +102,7 @@ type MatrixBuilder struct { xTolLabel, yTolLabel *widget.Label presetSelect *widget.Select nameEntry *widget.Entry + viewSelect *widget.Select display *fyne.Container // Filter tree: a nested group/condition builder. rootGroup is the live editor @@ -359,6 +371,19 @@ func (mb *MatrixBuilder) buildUI() { }) buildBtn.Importance = widget.HighImportance + // View toggle: switch the display between the learned matrix and a coverage + // heatmap of how many samples landed in each cell. + mb.viewSelect = widget.NewSelect([]string{viewMatrix, viewCoverage}, func(string) { + mb.rebuildDisplay() + }) + // Set the field directly rather than SetSelected: the latter fires OnChanged, + // which calls rebuildDisplay before mb.display exists (panic during buildUI). + mb.viewSelect.Selected = viewMatrix + bottomBar := container.NewBorder(nil, nil, nil, + container.NewHBox(widget.NewLabel("View"), mb.viewSelect), + buildBtn, + ) + mb.xBox = container.NewVBox() mb.yBox = container.NewHBox() mb.rebuildAxisEntries() @@ -407,7 +432,7 @@ func (mb *MatrixBuilder) buildUI() { mainSplit := container.NewHSplit( container.NewBorder( yPanel, - buildBtn, + bottomBar, container.NewVBox( widget.NewLabelWithStyle("X axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), mb.xBox, @@ -1076,6 +1101,7 @@ func (mb *MatrixBuilder) analyze() error { } mb.zData = make([]float64, size) + mb.counts = cnt filled := 0 for i := range sum { if cnt[i] > 0 { @@ -1105,21 +1131,34 @@ func (mb *MatrixBuilder) rebuildDisplay() { return } + // Default to the learned matrix; the Coverage view swaps in the per-cell hit + // counts as a read-only heatmap. + zData := mb.zData + zLabel := mb.zSeries + zPrec := precisionFor(mb.zData) + editable := true + if mb.viewSelect != nil && mb.viewSelect.Selected == viewCoverage { + zData = countsToFloat(mb.counts) + zLabel = "Hits" + zPrec = 0 + editable = false + } + noop := func([]float64) {} mv, err := mapviewer.New(&mapviewer.Config{ - Name: mb.zSeries, + Name: zLabel, XData: mb.xAxis, YData: mb.yAxis, - ZData: mb.zData, + ZData: zData, XPrecision: precisionFor(mb.xAxis), YPrecision: precisionFor(mb.yAxis), - ZPrecision: precisionFor(mb.zData), + ZPrecision: zPrec, XLabel: mb.xSeries, YLabel: mb.ySeries, - ZLabel: mb.zSeries, + ZLabel: zLabel, MeshView: true, MeshRenderer: mb.renderMode, - Editable: true, + Editable: editable, ColorblindMode: colors.ModeNormal, // The matrix is in-memory only; editing cells just mutates zData. SaveECUFunc: noop, @@ -1591,6 +1630,16 @@ func nearestIndex(axis []float64, v float64) int { return best } +// countsToFloat converts per-cell hit counts to float64 Z data for the +// mapviewer's Coverage view. +func countsToFloat(counts []int) []float64 { + out := make([]float64, len(counts)) + for i, c := range counts { + out[i] = float64(c) + } + return out +} + func minMax(data []float64) (float64, float64) { lo, hi := data[0], data[0] for _, v := range data[1:] { From 988cc34290ab235b94c7d615d384643663bfd02c Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 09:54:47 +0200 Subject: [PATCH 054/102] gocan stuff --- go.mod | 2 +- go.sum | 4 +- pkg/datalogger/t5logger.go | 6 +- pkg/datalogger/t7logger.go | 7 +- pkg/datalogger/t8logger.go | 6 +- pkg/datalogger/txbridgelogger.go | 7 +- pkg/widgets/canflasher/candump.go | 30 +- pkg/widgets/canflasher/canflash.go | 19 +- pkg/widgets/canflasher/caninfo.go | 1 - pkg/widgets/canflasher/canrecovery.go | 19 +- pkg/widgets/dtcreader/dtcreader.go | 34 +- pkg/widgets/editparameters/editparameters.go | 387 +++++++++---------- 12 files changed, 234 insertions(+), 288 deletions(-) diff --git a/go.mod b/go.mod index 77979766..064176d3 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 - github.com/roffe/ecusymbol v1.1.9 + github.com/roffe/ecusymbol v1.2.0 github.com/roffe/gocan v1.4.0 go.bug.st/serial v1.6.4 golang.org/x/image v0.40.0 diff --git a/go.sum b/go.sum index 4299a795..cb93fdce 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/roffe/ecusymbol v1.1.9 h1:muLJ5ihTK2LTWHrbfB/Dlk3pDAh5d0/UHKX2PE60fcE= -github.com/roffe/ecusymbol v1.1.9/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= +github.com/roffe/ecusymbol v1.2.0 h1:mI5M0HG17gCgbPTbMpIR8nPJGBu4HNZp0133HcPOzYw= +github.com/roffe/ecusymbol v1.2.0/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= github.com/roffe/gocan v1.4.0 h1:OSs//lr4vy/ozyMPUbgZaNFVZWMeXzOsXhCujpA4WRs= github.com/roffe/gocan v1.4.0/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= diff --git a/pkg/datalogger/t5logger.go b/pkg/datalogger/t5logger.go index f8bf8712..4a251882 100644 --- a/pkg/datalogger/t5logger.go +++ b/pkg/datalogger/t5logger.go @@ -34,12 +34,16 @@ func (c *T5Client) Start() error { } } - cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventHandler(eventHandler)) + cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventFunc(eventHandler)) if err != nil { return err } defer cl.Close() + // Drive everything below off the client's context so a fatal adapter error + // (or Close) cancels the polling loop and aborts in-flight requests directly. + ctx = cl.Context() + t := time.NewTicker(time.Second / time.Duration(c.Rate)) defer t.Stop() t5 := t5can.NewClient(cl) diff --git a/pkg/datalogger/t7logger.go b/pkg/datalogger/t7logger.go index d238c6ca..21b93f0d 100644 --- a/pkg/datalogger/t7logger.go +++ b/pkg/datalogger/t7logger.go @@ -85,12 +85,17 @@ func (c *T7Client) Start() error { } } - cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventHandler(eventHandler)) + cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventFunc(eventHandler)) if err != nil { return fmt.Errorf("failed to create t7 client: %w", err) } defer cl.Close() + // Drive everything below off the client's context so a fatal adapter error + // (or Close) cancels the polling loop and aborts in-flight requests directly, + // instead of relying on cl.Wait returning and the deferred cancel bouncing back. + ctx = cl.Context() + checkBroadcast := true if strings.Contains(c.Device.Name(), "OBDLink") || strings.Contains(c.Device.Name(), "STN") || strings.Contains(c.Device.Name(), "ELM") { checkBroadcast = false diff --git a/pkg/datalogger/t8logger.go b/pkg/datalogger/t8logger.go index 8b08fd8c..4b6ce00f 100644 --- a/pkg/datalogger/t8logger.go +++ b/pkg/datalogger/t8logger.go @@ -52,12 +52,16 @@ func (c *T8Client) Start() error { } } - cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventHandler(eventHandler)) + cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventFunc(eventHandler)) if err != nil { return err } defer cl.Close() + // Drive everything below off the client's context so a fatal adapter error + // (or Close) cancels the polling loop and aborts in-flight requests directly. + ctx = cl.Context() + if err := c.setupWBL(ctx, cl); err != nil { return err } diff --git a/pkg/datalogger/txbridgelogger.go b/pkg/datalogger/txbridgelogger.go index 8a31bda0..b3eca75f 100644 --- a/pkg/datalogger/txbridgelogger.go +++ b/pkg/datalogger/txbridgelogger.go @@ -35,12 +35,17 @@ func (c *TxBridge) Start() error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventHandler(eventHandler)) + cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventFunc(eventHandler)) if err != nil { return err } defer cl.Close() + // Drive everything below (incl. the per-ECU loops, which derive their ctx + // from this one) off the client's context so a fatal adapter error or Close + // cancels logging and aborts in-flight requests directly. + ctx = cl.Context() + if err := c.setupWBL(ctx, cl); err != nil { return err } diff --git a/pkg/widgets/canflasher/candump.go b/pkg/widgets/canflasher/candump.go index e0cdc92e..6032a5eb 100644 --- a/pkg/widgets/canflasher/candump.go +++ b/pkg/widgets/canflasher/candump.go @@ -3,7 +3,6 @@ package canflasher import ( "context" "fmt" - "log" "os" "time" @@ -32,33 +31,18 @@ func (t *CanFlasherWidget) ecuDump(filename string) { filename = addSuffix(filename, ".bin") t.progressBar.SetValue(0) - done := make(chan struct{}) - go func() { - for { - select { - case err := <-dev.Err(): - if err == nil { - return - } - log.Println("Error:", err) - case <-done: - return - } - } - }() - - go func() { - defer close(done) ctx, cancel := context.WithTimeout(context.Background(), 1200*time.Second) defer cancel() - //defer dev.Close() + // defer dev.Close() fyne.Do(t.Disable) defer fyne.Do(t.Enable) - c, err := gocan.NewWithOpts(ctx, dev) + c, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventFunc(func(e gocan.Event) { + t.log(e.String()) + })) if err != nil { t.logValues.Append(err.Error()) return @@ -86,7 +70,7 @@ func (t *CanFlasherWidget) ecuDump(filename string) { return } - if err := os.WriteFile(filename, bin, 0644); err == nil { + if err := os.WriteFile(filename, bin, 0o644); err == nil { t.log("Saved as " + filename) } else { t.log(err.Error()) @@ -97,8 +81,6 @@ func (t *CanFlasherWidget) ecuDump(filename string) { time.Sleep(200 * time.Millisecond) - if err := tr.ResetECU(ctx); err != nil { - t.log(err.Error()) - } + _ = tr.ResetECU(ctx) }() } diff --git a/pkg/widgets/canflasher/canflash.go b/pkg/widgets/canflasher/canflash.go index 13368f17..f070d7f5 100644 --- a/pkg/widgets/canflasher/canflash.go +++ b/pkg/widgets/canflasher/canflash.go @@ -3,7 +3,6 @@ package canflasher import ( "context" "fmt" - "log" "os" "time" @@ -37,21 +36,7 @@ func (t *CanFlasherWidget) ecuFlash(filename string) { t.progressBar.SetValue(0) - done := make(chan struct{}) - - go func() { - for { - select { - case err := <-dev.Err(): - log.Println("Error:", err) - case <-done: - return - } - } - }() - go func() { - defer close(done) ctx, cancel := context.WithTimeout(context.Background(), 1800*time.Second) defer cancel() @@ -60,7 +45,9 @@ func (t *CanFlasherWidget) ecuFlash(filename string) { fyne.Do(t.Disable) defer fyne.Do(t.Enable) - c, err := gocan.NewWithOpts(ctx, dev) + c, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventFunc(func(e gocan.Event) { + t.log(e.String()) + })) if err != nil { t.logValues.Append(err.Error()) return diff --git a/pkg/widgets/canflasher/caninfo.go b/pkg/widgets/canflasher/caninfo.go index b5dcbdb7..05562e3a 100644 --- a/pkg/widgets/canflasher/caninfo.go +++ b/pkg/widgets/canflasher/caninfo.go @@ -21,7 +21,6 @@ func (t *CanFlasherWidget) ecuInfo() { } go func() { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() diff --git a/pkg/widgets/canflasher/canrecovery.go b/pkg/widgets/canflasher/canrecovery.go index 6b0f4a0d..1c9d23ec 100644 --- a/pkg/widgets/canflasher/canrecovery.go +++ b/pkg/widgets/canflasher/canrecovery.go @@ -3,7 +3,6 @@ package canflasher import ( "context" "fmt" - "log" "os" "time" @@ -28,21 +27,7 @@ func (t *CanFlasherWidget) ecuRecover(filename string) { t.progressBar.SetValue(0) - done := make(chan struct{}) - - go func() { - for { - select { - case err := <-dev.Err(): - log.Println("Error:", err) - case <-done: - return - } - } - }() - go func() { - defer close(done) ctx, cancel := context.WithTimeout(context.Background(), 1800*time.Second) defer cancel() @@ -51,7 +36,9 @@ func (t *CanFlasherWidget) ecuRecover(filename string) { fyne.Do(t.Disable) defer fyne.Do(t.Enable) - c, err := gocan.NewWithOpts(ctx, dev) + c, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventFunc(func(e gocan.Event) { + t.log(e.String()) + })) if err != nil { t.logValues.Append(err.Error()) return diff --git a/pkg/widgets/dtcreader/dtcreader.go b/pkg/widgets/dtcreader/dtcreader.go index a1a182c3..c04e8f5a 100644 --- a/pkg/widgets/dtcreader/dtcreader.go +++ b/pkg/widgets/dtcreader/dtcreader.go @@ -148,26 +148,19 @@ func (d *DTCReader) ReadDTCS() error { return } - eventHandler := func(e gocan.Event) { - d.log(e.String()) - } - d.log("Connecting to device " + dev.Name()) - cl, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventHandler(eventHandler)) + // Events (incl. the final fatal) stream to the log; a fatal adapter + // failure also aborts any in-flight call below with that error. + cl, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventFunc(func(e gocan.Event) { + d.log(e.String()) + })) if err != nil { d.err(err) return } defer cl.Close() - go func() { - if err := cl.Wait(ctx); err != nil { - d.err(err) - return - } - }() - readDTCSFunc(ctx, cl) }() return nil @@ -198,26 +191,19 @@ func (d *DTCReader) ClearDTCS() error { d.err(err) return } - eventHandler := func(e gocan.Event) { - d.log(e.String()) - } - d.log("Connecting to device " + dev.Name()) - cl, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventHandler(eventHandler)) + // Events (incl. the final fatal) stream to the log; a fatal adapter + // failure also aborts any in-flight call below with that error. + cl, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventFunc(func(e gocan.Event) { + d.log(e.String()) + })) if err != nil { d.err(err) return } defer cl.Close() - go func() { - if err := cl.Wait(ctx); err != nil { - d.err(err) - return - } - }() - clearDTCSFunc(ctx, cl) }() return nil diff --git a/pkg/widgets/editparameters/editparameters.go b/pkg/widgets/editparameters/editparameters.go index 47c3e1d5..bfaad065 100644 --- a/pkg/widgets/editparameters/editparameters.go +++ b/pkg/widgets/editparameters/editparameters.go @@ -193,81 +193,74 @@ func (t *EditParameters) readParameters() { t.err(err) return } - eventHandler := func(e gocan.Event) { + cl, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventFunc(func(e gocan.Event) { log.Printf("EVENT: %v", e) - } - - cl, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventHandler(eventHandler)) + })) if err != nil { t.err(err) return } + defer cl.Close() + gm := gmlan.New(cl, 0x7e0, 0x5e8, 0x7e8) t8c := &T8{gm: gm} - go func() { - defer cl.Close() - - defer func() { - _ = gm.ReturnToNormalMode(ctx) - time.Sleep(75 * time.Millisecond) - }() - - data, err := gm.ReadDataByIdentifier(ctx, 0x01) - if err != nil { - t.err(err) - return - } + defer func() { + _ = gm.ReturnToNormalMode(ctx) + time.Sleep(75 * time.Millisecond) + }() - status, err := t8.DecodePI01(data) - if err != nil { - t.err(err) - return - } + // A fatal adapter failure aborts any of these calls with that error. + data, err := gm.ReadDataByIdentifier(ctx, 0x01) + if err != nil { + t.err(err) + return + } - t.SetDiagnosticType(status.DiagnosticType.String()) - t.SetTankType(status.TankType.String()) - t.SetConvertible(status.Convertible) - t.SetSAI(status.SAI) - t.SetHighOutput(status.HighOutput) - t.SetBioPower(status.BioPower) - t.SetClutchStart(status.ClutchStart) + status, err := t8.DecodePI01(data) + if err != nil { + t.err(err) + return + } - oilQuality, err := t8c.GetOilQuality(ctx) - if err != nil { - t.err(err) - return - } - t.SetOilQuality(strconv.FormatFloat(oilQuality, 'f', 2, 64)) + t.SetDiagnosticType(status.DiagnosticType.String()) + t.SetTankType(status.TankType.String()) + t.SetConvertible(status.Convertible) + t.SetSAI(status.SAI) + t.SetHighOutput(status.HighOutput) + t.SetBioPower(status.BioPower) + t.SetClutchStart(status.ClutchStart) - vin, err := t8c.GetVehicleVIN(ctx) - if err != nil { - t.err(err) - return - } - t.SetVIN(vin) + oilQuality, err := t8c.GetOilQuality(ctx) + if err != nil { + t.err(err) + return + } + t.SetOilQuality(strconv.FormatFloat(oilQuality, 'f', 2, 64)) - topSpeed, err := t8c.GetTopSpeed(ctx) - if err != nil { - t.err(err) - return - } - t.SetTopSpeed(strconv.Itoa(int(topSpeed))) + vin, err := t8c.GetVehicleVIN(ctx) + if err != nil { + t.err(err) + return + } + t.SetVIN(vin) - time.Sleep(5 * time.Millisecond) + topSpeed, err := t8c.GetTopSpeed(ctx) + if err != nil { + t.err(err) + return + } + t.SetTopSpeed(strconv.Itoa(int(topSpeed))) - e85percentage, err := t8c.GetE85Percent(ctx) - if err != nil { - t.err(err) - return - } - t.SetE85Percent(strconv.FormatFloat(e85percentage, 'f', 0, 64)) - }() + time.Sleep(5 * time.Millisecond) - if err := cl.Wait(ctx); err != nil { + e85percentage, err := t8c.GetE85Percent(ctx) + if err != nil { t.err(err) return } + t.SetE85Percent(strconv.FormatFloat(e85percentage, 'f', 0, 64)) + t.hasBeenRead = true } @@ -287,168 +280,162 @@ func (t *EditParameters) writeParameters() { t.err(err) return } - eventHandler := func(e gocan.Event) { + cl, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventFunc(func(e gocan.Event) { log.Printf("EVENT: %v", e) - } - - cl, err := gocan.NewWithOpts(ctx, dev, gocan.WithEventHandler(eventHandler)) + })) if err != nil { t.err(err) return } + defer cl.Close() + gm := gmlan.New(cl, 0x7e0, 0x5e8, 0x7e8) t8c := &T8{gm: gm} - go func() { - defer cl.Close() - - //if err := gm.InitiateDiagnosticOperation(ctx, gmlan.LEV_EDDDC); err != nil { - // log.Println(err) - // return - //} - - defer func() { - _ = gm.ReturnToNormalMode(ctx) - time.Sleep(75 * time.Millisecond) - }() - - if err := gm.RequestSecurityAccess(ctx, 0xFD, 1, t8sec.CalculateAccessKey); err != nil { - t.err(err) - return - } - - vin, err := t.GetVIN() - if err != nil { - t.err(fmt.Errorf("Error getting VIN: %w", err)) - return - } - if err := t8c.SetVehicleVIN(ctx, vin); err != nil { - t.err(fmt.Errorf("Error setting VIN: %w", err)) - } - - e85content, err := t.GetE85Percent() - if err != nil { - t.err(fmt.Errorf("Error getting E85 content: %w", err)) - return - } - e85percent, err := strconv.ParseFloat(e85content, 64) - if err != nil { - t.err(fmt.Errorf("Error parsing E85 content: %w", err)) - return - } - if err := t8c.SetE85Percent(ctx, e85percent); err != nil { - t.err(fmt.Errorf("Error setting E85 percent: %w", err)) - return - } - - topSpeed, err := t.GetTopSpeed() - if err != nil { - t.err(fmt.Errorf("Error getting Top Speed: %w", err)) - return - } - topSpeedVal, err := strconv.Atoi(topSpeed) - if err != nil { - t.err(fmt.Errorf("Error parsing Top Speed: %w", err)) - return - } - if err := t8c.SetTopSpeed(ctx, uint16(topSpeedVal)); err != nil { - t.err(fmt.Errorf("Error setting Top Speed: %w", err)) - return - } + //if err := gm.InitiateDiagnosticOperation(ctx, gmlan.LEV_EDDDC); err != nil { + // log.Println(err) + // return + //} - oilQuality, err := t.GetOilQuality() - if err != nil { - t.err(fmt.Errorf("Error getting oil quality: %w", err)) - return - } - oilQualityVal, err := strconv.ParseFloat(oilQuality, 64) - if err != nil { - t.err(fmt.Errorf("Error parsing oil quality: %w", err)) - return - } - if err := t8c.SetOilQuality(ctx, oilQualityVal); err != nil { - t.err(fmt.Errorf("Error setting oil quality: %w", err)) - return - } + defer func() { + _ = gm.ReturnToNormalMode(ctx) + time.Sleep(75 * time.Millisecond) + }() - data, err := gm.ReadDataByIdentifier(ctx, 0x01) - if err != nil { - t.err(fmt.Errorf("Error reading PI01: %w", err)) - return - } + // A fatal adapter failure aborts any of these calls with that error. + if err := gm.RequestSecurityAccess(ctx, 0xFD, 1, t8sec.CalculateAccessKey); err != nil { + t.err(err) + return + } - pi01, err := t.GetPI01Data() - if err != nil { - t.err(fmt.Errorf("Error getting PI 0x01 data: %w", err)) - return - } + vin, err := t.GetVIN() + if err != nil { + t.err(fmt.Errorf("Error getting VIN: %w", err)) + return + } + if err := t8c.SetVehicleVIN(ctx, vin); err != nil { + t.err(fmt.Errorf("Error setting VIN: %w", err)) + } - // -------C - data[0] = setBit(data[0], 0, pi01.BioPower) - - // -----C-- - data[0] = setBit(data[0], 2, pi01.Convertible) - - // ---01--- US - // ---10--- EU - // ---11--- AWD - switch pi01.TankType { - case t8.TankTypeUS: - data[0] = setBit(data[0], 3, true) - data[0] = setBit(data[0], 4, false) - case t8.TankTypeEU: - data[0] = setBit(data[0], 3, false) - data[0] = setBit(data[0], 4, true) - case t8.TankTypeAWD: - data[0] = setBit(data[0], 3, true) - data[0] = setBit(data[0], 4, true) - } + e85content, err := t.GetE85Percent() + if err != nil { + t.err(fmt.Errorf("Error getting E85 content: %w", err)) + return + } + e85percent, err := strconv.ParseFloat(e85content, 64) + if err != nil { + t.err(fmt.Errorf("Error parsing E85 content: %w", err)) + return + } + if err := t8c.SetE85Percent(ctx, e85percent); err != nil { + t.err(fmt.Errorf("Error setting E85 percent: %w", err)) + return + } - // -01----- OBD2 - // -10----- EOBD - // -11----- LOBD - switch pi01.DiagnosticType { - case t8.DiagnosticTypeOBD2: - data[0] = setBit(data[0], 5, true) - data[0] = setBit(data[0], 6, false) - case t8.DiagnosticTypeEOBD: - data[0] = setBit(data[0], 5, false) - data[0] = setBit(data[0], 6, true) - case t8.DiagnosticTypeLOBD: - data[0] = setBit(data[0], 5, true) - data[0] = setBit(data[0], 6, true) - case t8.DiagnosticTypeNone: - data[0] = setBit(data[0], 5, false) - data[0] = setBit(data[0], 6, false) - } + topSpeed, err := t.GetTopSpeed() + if err != nil { + t.err(fmt.Errorf("Error getting Top Speed: %w", err)) + return + } + topSpeedVal, err := strconv.Atoi(topSpeed) + if err != nil { + t.err(fmt.Errorf("Error parsing Top Speed: %w", err)) + return + } + if err := t8c.SetTopSpeed(ctx, uint16(topSpeedVal)); err != nil { + t.err(fmt.Errorf("Error setting Top Speed: %w", err)) + return + } - // on = -----10- - // off= -----01- - data[1] = setBit(data[1], 1, !pi01.ClutchStart) - data[1] = setBit(data[1], 2, pi01.ClutchStart) + oilQuality, err := t.GetOilQuality() + if err != nil { + t.err(fmt.Errorf("Error getting oil quality: %w", err)) + return + } + oilQualityVal, err := strconv.ParseFloat(oilQuality, 64) + if err != nil { + t.err(fmt.Errorf("Error parsing oil quality: %w", err)) + return + } + if err := t8c.SetOilQuality(ctx, oilQualityVal); err != nil { + t.err(fmt.Errorf("Error setting oil quality: %w", err)) + return + } - // on = ---10--- - // off= ---01--- - data[1] = setBit(data[1], 3, !pi01.SAI) - data[1] = setBit(data[1], 4, pi01.SAI) + data, err := gm.ReadDataByIdentifier(ctx, 0x01) + if err != nil { + t.err(fmt.Errorf("Error reading PI01: %w", err)) + return + } - // high= -01----- - // low = -10----- - data[1] = setBit(data[1], 5, pi01.HighOutput) - data[1] = setBit(data[1], 6, !pi01.HighOutput) + pi01, err := t.GetPI01Data() + if err != nil { + t.err(fmt.Errorf("Error getting PI 0x01 data: %w", err)) + return + } - if err := gm.WriteDataByIdentifier(ctx, 0x01, data); err != nil { - t.err(fmt.Errorf("Error writing PI 0x01: %w", err)) - return - } + // -------C + data[0] = setBit(data[0], 0, pi01.BioPower) + + // -----C-- + data[0] = setBit(data[0], 2, pi01.Convertible) + + // ---01--- US + // ---10--- EU + // ---11--- AWD + switch pi01.TankType { + case t8.TankTypeUS: + data[0] = setBit(data[0], 3, true) + data[0] = setBit(data[0], 4, false) + case t8.TankTypeEU: + data[0] = setBit(data[0], 3, false) + data[0] = setBit(data[0], 4, true) + case t8.TankTypeAWD: + data[0] = setBit(data[0], 3, true) + data[0] = setBit(data[0], 4, true) + } + + // -01----- OBD2 + // -10----- EOBD + // -11----- LOBD + switch pi01.DiagnosticType { + case t8.DiagnosticTypeOBD2: + data[0] = setBit(data[0], 5, true) + data[0] = setBit(data[0], 6, false) + case t8.DiagnosticTypeEOBD: + data[0] = setBit(data[0], 5, false) + data[0] = setBit(data[0], 6, true) + case t8.DiagnosticTypeLOBD: + data[0] = setBit(data[0], 5, true) + data[0] = setBit(data[0], 6, true) + case t8.DiagnosticTypeNone: + data[0] = setBit(data[0], 5, false) + data[0] = setBit(data[0], 6, false) + } + + // on = -----10- + // off= -----01- + data[1] = setBit(data[1], 1, !pi01.ClutchStart) + data[1] = setBit(data[1], 2, pi01.ClutchStart) + + // on = ---10--- + // off= ---01--- + data[1] = setBit(data[1], 3, !pi01.SAI) + data[1] = setBit(data[1], 4, pi01.SAI) + + // high= -01----- + // low = -10----- + data[1] = setBit(data[1], 5, pi01.HighOutput) + data[1] = setBit(data[1], 6, !pi01.HighOutput) + + if err := gm.WriteDataByIdentifier(ctx, 0x01, data); err != nil { + t.err(fmt.Errorf("Error writing PI 0x01: %w", err)) + return + } - if err := gm.DeviceControl(ctx, 0x16); err != nil { - t.err(fmt.Errorf("Error performing device control 0x16: %w", err)) - return - } - }() - if err := cl.Wait(ctx); err != nil { - t.err(err) + if err := gm.DeviceControl(ctx, 0x16); err != nil { + t.err(fmt.Errorf("Error performing device control 0x16: %w", err)) + return } } From ae0b33857c9f57da8c77eea88faf3d63ea12eba7 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 09:54:58 +0200 Subject: [PATCH 055/102] new symbol browser --- pkg/widgets/symbolbrowser/symbolbrowser.go | 232 +++++++++++++++++++++ pkg/windows/mainWindow_menu.go | 15 ++ 2 files changed, 247 insertions(+) create mode 100644 pkg/widgets/symbolbrowser/symbolbrowser.go diff --git a/pkg/widgets/symbolbrowser/symbolbrowser.go b/pkg/widgets/symbolbrowser/symbolbrowser.go new file mode 100644 index 00000000..33c4d97b --- /dev/null +++ b/pkg/widgets/symbolbrowser/symbolbrowser.go @@ -0,0 +1,232 @@ +package symbolbrowser + +import ( + "fmt" + "strconv" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + xlayout "fyne.io/x/fyne/layout" + symbol "github.com/roffe/ecusymbol" +) + +var _ fyne.Widget = (*Widget)(nil) + +// Column proportions, shared by the header and every row so they line up. +// Name, Address, SRAM offset, Length, Type, Action(Open). +var columnSizes = []float64{0.34, 0.15, 0.15, 0.09, 0.15, 0.12} + +// Widget lists every symbol in the loaded binary together with its address, +// sram offset, length and type, and offers a button to open each one as a map. +type Widget struct { + widget.BaseWidget + + getFW func() symbol.SymbolCollection + getECU func() symbol.ECUType + openMap func(symbol.ECUType, string, string) + err func(error) + + search *widget.Entry + countLbl *widget.Label + list *widget.List + + all []*symbol.Symbol + filtered []*symbol.Symbol +} + +func New(getFW func() symbol.SymbolCollection, getECU func() symbol.ECUType, openMap func(symbol.ECUType, string, string), err func(error)) *Widget { + w := &Widget{ + getFW: getFW, + getECU: getECU, + openMap: openMap, + err: err, + } + w.loadSymbols() + w.ExtendBaseWidget(w) + return w +} + +// loadSymbols pulls the current symbol collection from the main window. +func (w *Widget) loadSymbols() { + fw := w.getFW() + if fw == nil { + w.all = nil + w.filtered = nil + return + } + w.all = fw.Symbols() + w.filtered = w.all +} + +func (w *Widget) applyFilter(q string) { + q = strings.ToLower(strings.TrimSpace(q)) + if q == "" { + w.filtered = w.all + } else { + out := make([]*symbol.Symbol, 0, len(w.all)) + for _, s := range w.all { + if strings.Contains(strings.ToLower(s.Name), q) { + out = append(out, s) + } + } + w.filtered = out + } + if w.countLbl != nil { + w.countLbl.SetText(w.countText()) + } + if w.list != nil { + w.list.Refresh() + w.list.ScrollToTop() + } +} + +func (w *Widget) countText() string { + if len(w.filtered) == len(w.all) { + return fmt.Sprintf("%d symbols", len(w.all)) + } + return fmt.Sprintf("%d / %d symbols", len(w.filtered), len(w.all)) +} + +func (w *Widget) openSymbol(sym *symbol.Symbol) { + if w.openMap == nil || sym == nil { + return + } + w.openMap(w.getECU(), "", sym.Name) +} + +func (w *Widget) header() fyne.CanvasObject { + mk := func(s string) *widget.Label { + return widget.NewLabelWithStyle(s, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + } + return container.New( + xlayout.NewHPortion(columnSizes), + mk("Name"), + mk("Address"), + mk("SRAM offset"), + mk("Length"), + mk("Type"), + mk("Action"), + ) +} + +func (w *Widget) CreateRenderer() fyne.WidgetRenderer { + w.search = widget.NewEntry() + w.search.SetPlaceHolder("Filter by name...") + w.search.OnChanged = w.applyFilter + + w.countLbl = widget.NewLabel(w.countText()) + + refresh := widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { + w.loadSymbols() + w.applyFilter(w.search.Text) + }) + + w.list = widget.NewList( + func() int { return len(w.filtered) }, + func() fyne.CanvasObject { return newSymbolRow(w.openSymbol) }, + func(i widget.ListItemID, o fyne.CanvasObject) { + if i < 0 || i >= len(w.filtered) { + return + } + o.(*symbolRow).set(w.filtered[i]) + }, + ) + + top := container.NewBorder(nil, nil, nil, container.NewHBox(w.countLbl, refresh), w.search) + + return widget.NewSimpleRenderer(container.NewBorder( + container.NewVBox(top, w.header()), + nil, + nil, + nil, + w.list, + )) +} + +// typeString renders the Trionic type bitfield as readable flags. +func typeString(t uint8) string { + var parts []string + if t&symbol.SIGNED != 0 { + parts = append(parts, "signed") + } + if t&symbol.KONST != 0 { + parts = append(parts, "const") + } + if t&symbol.CHAR != 0 { + parts = append(parts, "char") + } + if t&symbol.LONG != 0 { + parts = append(parts, "long") + } + if t&symbol.BITFIELD != 0 { + parts = append(parts, "bitfield") + } + if t&symbol.STRUCT != 0 { + parts = append(parts, "struct") + } + if len(parts) == 0 { + return fmt.Sprintf("0x%02X", t) + } + return strings.Join(parts, "|") +} + +var _ fyne.Widget = (*symbolRow)(nil) + +type symbolRow struct { + widget.BaseWidget + + name *widget.Label + addr *widget.Label + sram *widget.Label + length *widget.Label + typ *widget.Label + open *widget.Button + + sym *symbol.Symbol + onOpen func(*symbol.Symbol) +} + +func newSymbolRow(onOpen func(*symbol.Symbol)) *symbolRow { + r := &symbolRow{ + name: widget.NewLabel(""), + addr: widget.NewLabel(""), + sram: widget.NewLabel(""), + length: widget.NewLabel(""), + typ: widget.NewLabel(""), + onOpen: onOpen, + } + r.name.Truncation = fyne.TextTruncateEllipsis + r.name.Selectable = true + r.typ.Truncation = fyne.TextTruncateEllipsis + r.open = widget.NewButtonWithIcon("Open", theme.GridIcon(), func() { + if r.sym != nil && r.onOpen != nil { + r.onOpen(r.sym) + } + }) + r.ExtendBaseWidget(r) + return r +} + +func (r *symbolRow) set(sym *symbol.Symbol) { + r.sym = sym + r.name.SetText(sym.Name) + r.addr.SetText(fmt.Sprintf("$%06X", sym.Address)) + r.sram.SetText(fmt.Sprintf("$%06X", sym.SramOffset)) + r.length.SetText(strconv.Itoa(int(sym.Length))) + r.typ.SetText(typeString(sym.Type)) +} + +func (r *symbolRow) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(container.New( + xlayout.NewHPortion(columnSizes), + r.name, + r.addr, + r.sram, + r.length, + r.typ, + r.open, + )) +} diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index b84ce5c4..c4a4d21e 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -26,6 +26,7 @@ import ( "github.com/roffe/txlogger/pkg/widgets/mapviewer" "github.com/roffe/txlogger/pkg/widgets/multiwindow" "github.com/roffe/txlogger/pkg/widgets/progressmodal" + "github.com/roffe/txlogger/pkg/widgets/symbolbrowser" "github.com/roffe/txlogger/pkg/widgets/trionic5/pgmmod" "github.com/roffe/txlogger/pkg/widgets/trionic5/pgmstatus" "github.com/roffe/txlogger/pkg/widgets/trionic7/t7esp" @@ -235,6 +236,20 @@ func (mw *MainWindow) setupMenu() { mw.wm.Add(inner) }), openItem, + fyne.NewMenuItemWithIcon("Symbol Browser", theme.ListIcon(), func() { + if w := mw.wm.HasWindow("Symbol Browser"); w != nil { + mw.wm.Raise(w) + return + } + getECU := func() symbol.ECUType { + return symbol.ECUTypeFromString(mw.selects.ecuSelect.Selected) + } + browser := symbolbrowser.New(getFW, getECU, mw.openMap, mw.Error) + inner := multiwindow.NewInnerWindow("Symbol Browser", browser) + inner.Icon = theme.ListIcon() + mw.wm.Add(inner) + inner.Resize(fyne.Size{Width: 760, Height: 520}) + }), fyne.NewMenuItemWithIcon("Settings", theme.SettingsIcon(), func() { mw.openSettings() }), From 4c23dd0a98b7813ba972d7f310be9af5b3837413 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 23:56:24 +0200 Subject: [PATCH 056/102] add liveplotter --- pkg/widgets/liveplotter/liveplotter.go | 580 +++++++++++++++++++ pkg/widgets/liveplotter/liveplotter_mouse.go | 73 +++ pkg/widgets/liveplotter/liveplotter_test.go | 103 ++++ 3 files changed, 756 insertions(+) create mode 100644 pkg/widgets/liveplotter/liveplotter.go create mode 100644 pkg/widgets/liveplotter/liveplotter_mouse.go create mode 100644 pkg/widgets/liveplotter/liveplotter_test.go diff --git a/pkg/widgets/liveplotter/liveplotter.go b/pkg/widgets/liveplotter/liveplotter.go new file mode 100644 index 00000000..602b3e63 --- /dev/null +++ b/pkg/widgets/liveplotter/liveplotter.go @@ -0,0 +1,580 @@ +// Package liveplotter provides a scope-style widget that plots live EBUS values +// over a sliding time window (default 120s). It auto-follows the newest data and +// lets you scrub back into the retained history of the current pull. +// +// Unlike the logplayer's plotter, which renders an immutable log, the live +// plotter appends one sample per ECU frame: it subscribes to each selected +// symbol (storing the latest value) and to ebus.TOPIC_FRAME, which fires once per +// completed frame carrying that frame's real timestamp. On each frame it snapshots +// every symbol's latest value with the shared timestamp, so all series stay time +// aligned. It reuses the plotter package's primitives (TimeSeries + PlotImage +// rasterizer and the TappableText legend) but owns its own data model and layout. +package liveplotter + +import ( + "fmt" + "image" + "image/color" + "sort" + "sync" + "sync/atomic" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/ebus" + "github.com/roffe/txlogger/pkg/widgets/plotter" +) + +var ( + _ fyne.Widget = (*Widget)(nil) + _ fyne.Draggable = (*Widget)(nil) + _ fyne.Scrollable = (*Widget)(nil) +) + +const defaultWindow = 120 * time.Second + +// Config configures a live plotter. +type Config struct { + // Order is the set of symbol names to plot, in legend order. + Order []string + // Window is the sliding time window length. Defaults to 120s when zero. + Window time.Duration +} + +type Widget struct { + widget.BaseWidget + + order []string + ts []*plotter.TimeSeries + + // mu guards the raw sample store and latest values, which are written from + // the logging goroutine (subscriptions) and read on the UI goroutine (refresh). + mu sync.Mutex + latest map[string]float64 + times []int64 // frame timestamps, Unix millis, ascending + values map[string][]float64 // per-series samples, parallel to times + view map[string][]float64 // reusable per-render windowed copy (UI goroutine) + + windowMillis int64 + + // UI-goroutine-only view state. + follow bool + anchorMillis int64 // right edge timestamp when paused + lastNewest int64 + viewCount int + hilightLine int + + // mouse cursor state (UI goroutine only). When mouseInPlot the vertical + // cursor and the legend track cursorX (plot-relative pixels); otherwise the + // cursor sits at the right edge and the legend shows the newest/frozen value. + mouseInPlot bool + cursorX float32 + + // canvas objects + canvasImage *canvas.Image + cursor *canvas.Line + overlayText *canvas.Text + plotObj fyne.CanvasObject + zoom *widget.Slider + legend *fyne.Container + legendTexts []*plotter.TappableText + legendVals []float64 + windowSel *widget.Select + followBtn *widget.Button + split *container.Split + root *fyne.Container + + plotResolution fyne.Size + size fyne.Size + + imgBuffers [2]*image.RGBA + imgIndex int + + refreshPending atomic.Bool + // paused mirrors !follow for the logging goroutine (which can't touch the + // UI-only follow field): while set, onFrame keeps appending but skips + // compaction so the frozen history is never trimmed until playback resumes. + paused atomic.Bool + + cancels []func() + closeOnce sync.Once + closed atomic.Bool +} + +func New(cfg *Config) *Widget { + window := cfg.Window + if window <= 0 { + window = defaultWindow + } + + p := &Widget{ + order: append([]string(nil), cfg.Order...), + latest: make(map[string]float64, len(cfg.Order)), + values: make(map[string][]float64, len(cfg.Order)), + view: make(map[string][]float64, len(cfg.Order)), + windowMillis: window.Milliseconds(), + follow: true, + hilightLine: -1, + legend: container.NewVBox(), + zoom: widget.NewSlider(1, 100), + canvasImage: canvas.NewImageFromImage(image.NewRGBA(image.Rect(0, 0, 400, 200))), + cursor: canvas.NewLine(color.White), + } + p.ExtendBaseWidget(p) + + p.canvasImage.FillMode = canvas.ImageFillContain + p.canvasImage.ScaleMode = canvas.ImageScaleFastest + + p.zoom.Orientation = widget.Vertical + p.zoom.Value = 100 // show the whole window by default + p.zoom.OnChanged = func(float64) { p.scheduleRefresh() } + + p.overlayText = canvas.NewText("", color.White) + p.overlayText.TextSize = 25 + + p.ts = make([]*plotter.TimeSeries, len(p.order)) + p.legendVals = make([]float64, len(p.order)) + for n, name := range p.order { + p.values[name] = make([]float64, 0, 4096) + p.ts[n] = plotter.NewSeries(name) + p.legendTexts = append(p.legendTexts, p.newLegendText(n, name)) + p.legend.Add(p.legendTexts[n]) + } + + p.plotObj = p.canvasImage + p.subscribe() + + return p +} + +func (p *Widget) newLegendText(n int, name string) *plotter.TappableText { + onTapped := func(enabled bool) { + p.ts[n].Enabled = enabled + p.scheduleRefresh() + } + onColorUpdate := func(col color.Color) { + r, g, b, a := col.RGBA() + p.ts[n].Color = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)} + p.scheduleRefresh() + } + onHover := func(hover bool) { + if hover { + p.overlayText.Text = name + p.overlayText.Color = p.ts[n].Color + p.hilightLine = n + } else { + p.overlayText.Text = "" + p.hilightLine = -1 + } + p.scheduleRefresh() + } + return plotter.NewTappableText(name, p.ts[n].Color, onTapped, onColorUpdate, onHover) +} + +// subscribe wires the per-symbol latest-value updates and the frame tick. The +// callbacks run on the logging goroutine and must stay fast, so they only touch +// the guarded store; the redraw is coalesced onto the UI goroutine. +func (p *Widget) subscribe() { + for _, name := range p.order { + name := name + p.cancels = append(p.cancels, ebus.CONTROLLER.SubscribeFunc(name, func(v float64) { + p.mu.Lock() + p.latest[name] = v + p.mu.Unlock() + })) + } + p.cancels = append(p.cancels, ebus.CONTROLLER.SubscribeFunc(ebus.TOPIC_FRAME, p.onFrame)) +} + +func (p *Widget) onFrame(tMillis float64) { + if p.closed.Load() { + return + } + p.ingest(int64(tMillis), p.paused.Load()) + p.scheduleRefresh() +} + +// ingest appends one frame (the latest value of every series at timestamp t) and +// compacts unless paused. Split out from onFrame so the windowing/retention can +// be tested without a running UI. +func (p *Widget) ingest(t int64, paused bool) { + p.mu.Lock() + defer p.mu.Unlock() + // Guard against out-of-order/duplicate timestamps so times stays ascending. + if n := len(p.times); n > 0 && t <= p.times[n-1] { + t = p.times[n-1] + 1 + } + p.times = append(p.times, t) + for _, name := range p.order { + p.values[name] = append(p.values[name], p.latest[name]) + } + if !paused { + p.compactLocked() + } +} + +// viewRange returns the [start,end) sample indices to display for the visible +// span. In follow mode the right edge tracks the newest sample; when paused it +// is frozen at anchor, clamped only to the data we still hold (never pushed +// forward). The returned rightT is the clamped right-edge timestamp. +func viewRange(times []int64, follow bool, anchor, span int64) (start, end int, rightT int64) { + n := len(times) + newest, oldest := times[n-1], times[0] + if follow { + rightT = newest + } else { + rightT = anchor + if rightT > newest { + rightT = newest + } + if rightT < oldest { + rightT = oldest + } + } + leftT := rightT - span + if leftT < oldest { + leftT = oldest + } + start = sort.Search(n, func(i int) bool { return times[i] >= leftT }) + end = sort.Search(n, func(i int) bool { return times[i] > rightT }) + if end <= start { + end = start + 1 + } + return start, end, rightT +} + +// compactLocked drops samples older than the window once the retained span grows +// past 2x the window, so memory stays bounded and the cost is amortized O(1) per +// frame. Caller holds p.mu. +func (p *Widget) compactLocked() { + n := len(p.times) + if n < 2 || p.times[n-1]-p.times[0] <= 2*p.windowMillis { + return + } + cutoff := p.times[n-1] - p.windowMillis + cut := sort.Search(n, func(i int) bool { return p.times[i] >= cutoff }) + if cut <= 0 { + return + } + p.times = append(p.times[:0], p.times[cut:]...) + for _, name := range p.order { + s := p.values[name] + p.values[name] = append(s[:0], s[cut:]...) + } +} + +// scheduleRefresh coalesces redraws onto the UI goroutine: while one is pending, +// further calls only return, so the draw rate is bounded by what the UI can keep +// up with rather than the frame rate (mirrors plotter.Plotter.Seek). +func (p *Widget) scheduleRefresh() { + if p.closed.Load() { + return + } + if !p.refreshPending.CompareAndSwap(false, true) { + return + } + fyne.Do(func() { + p.refreshPending.Store(false) + p.refresh() + }) +} + +// spanMillis is the visible time span derived from the zoom slider, clamped to +// the window. Zoom at max shows the whole window; scrolling in shrinks it. +func (p *Widget) spanMillis() int64 { + span := int64(float64(p.windowMillis) * p.zoom.Value / p.zoom.Max) + if span > p.windowMillis { + span = p.windowMillis + } + if span < 1000 { + span = 1000 + } + return span +} + +// refresh recomputes the visible window, copies it out under the lock, and +// redraws. Runs on the UI goroutine. +func (p *Widget) refresh() { + span := p.spanMillis() + + p.mu.Lock() + n := len(p.times) + if n < 2 { + p.mu.Unlock() + p.viewCount = 0 + p.drawImage() + p.canvasImage.Refresh() + return + } + p.lastNewest = p.times[n-1] + + startIdx, endIdx, rightT := viewRange(p.times, p.follow, p.anchorMillis, span) + if !p.follow { + p.anchorMillis = rightT + } + for _, name := range p.order { + src := p.values[name][startIdx:endIdx] + p.view[name] = append(p.view[name][:0], src...) + } + p.mu.Unlock() + + p.viewCount = endIdx - startIdx + + // Auto-range unknown signals from the visible window so they stay on screen. + for _, ts := range p.ts { + if !ts.Auto || !ts.Enabled { + continue + } + data := p.view[ts.Name] + if len(data) == 0 { + continue + } + mn, mx := data[0], data[0] + for _, v := range data { + if v < mn { + mn = v + } + if v > mx { + mx = v + } + } + if mn == mx { + mn -= 1 + mx += 1 + } else { + pad := (mx - mn) * 0.05 + mn -= pad + mx += pad + } + ts.SetRange(mn, mx) + } + + p.computeLegendVals() + p.drawImage() + p.updateLegendValues() + p.layoutCursor() + p.cursor.Refresh() + p.canvasImage.Refresh() +} + +// computeLegendVals fills legendVals from the visible window: the sample under +// the mouse cursor when hovering, otherwise the right edge (newest when live, +// frozen value when paused). +func (p *Widget) computeLegendVals() { + if p.viewCount <= 0 { + return + } + idx := p.viewCount - 1 + if p.mouseInPlot { + if plotW := p.plotObj.Size().Width; plotW > 0 { + frac := float64(p.cursorX) / float64(plotW) + idx = int(frac*float64(p.viewCount-1) + 0.5) + if idx < 0 { + idx = 0 + } + if idx > p.viewCount-1 { + idx = p.viewCount - 1 + } + } + } + for i, name := range p.order { + if v := p.view[name]; idx < len(v) { + p.legendVals[i] = v[idx] + } + } +} + +func (p *Widget) updateLegendValues() { + for i := range p.order { + newValue := fmt.Sprintf("%.4g", p.legendVals[i]) + obj := p.legendTexts[i] + if obj.Value() == newValue { + continue + } + obj.SetValue(newValue) + } +} + +// drawImage rasterizes the visible window into a reused buffer via the plotter's +// TimeSeries.PlotImage, exactly like the plotter image backend. +func (p *Widget) drawImage() { + w, h := int(p.plotResolution.Width), int(p.plotResolution.Height) + if w <= 0 || h <= 0 { + return + } + p.imgIndex ^= 1 + img := p.imgBuffers[p.imgIndex] + if img == nil || img.Rect.Dx() != w || img.Rect.Dy() != h { + img = image.NewRGBA(image.Rect(0, 0, w, h)) + p.imgBuffers[p.imgIndex] = img + } else { + clear(img.Pix) + } + + if p.viewCount >= 2 { + for n := range p.ts { + if !p.ts[n].Enabled || p.hilightLine == n { + continue + } + p.ts[n].PlotImage(img, p.view, 0, p.viewCount, 1) + } + if p.hilightLine >= 0 && p.ts[p.hilightLine].Enabled { + p.ts[p.hilightLine].PlotImage(img, p.view, 0, p.viewCount, 4) + } + } + + p.canvasImage.Image = img +} + +// layoutCursor positions the vertical cursor: under the mouse while hovering the +// plot, otherwise at the right edge as a "now" line. +func (p *Widget) layoutCursor() { + plotSize := p.plotObj.Size() + zw := p.zoom.Size().Width + var x float32 + if p.mouseInPlot { + x = zw + p.cursorX + } else { + x = zw + plotSize.Width - 1 + } + p.cursor.Position1 = fyne.NewPos(x, 0) + p.cursor.Position2 = fyne.NewPos(x+1, plotSize.Height) +} + +func (p *Widget) setFollow(follow bool) { + p.follow = follow + p.paused.Store(!follow) + // The button is labelled with the action it performs, not the current state: + // while live it offers Pause; while paused it offers Go Live. + if follow { + p.followBtn.SetIcon(theme.MediaPauseIcon()) + p.followBtn.SetText("Pause") + } else { + p.anchorMillis = p.lastNewest + p.followBtn.SetIcon(theme.MediaPlayIcon()) + p.followBtn.SetText("Go Live") + } + p.scheduleRefresh() +} + +func (p *Widget) Close() { + p.closeOnce.Do(func() { + p.closed.Store(true) + for _, cancel := range p.cancels { + cancel() + } + p.cancels = nil + }) +} + +func (p *Widget) CreateRenderer() fyne.WidgetRenderer { + // Initial state is live (following), so the button offers the Pause action. + p.followBtn = widget.NewButtonWithIcon("Pause", theme.MediaPauseIcon(), func() { + p.setFollow(!p.follow) + }) + + p.windowSel = widget.NewSelect([]string{"30s", "60s", "120s", "300s"}, func(s string) { + var d time.Duration + switch s { + case "30s": + d = 30 * time.Second + case "60s": + d = 60 * time.Second + case "120s": + d = 120 * time.Second + case "300s": + d = 300 * time.Second + } + if d > 0 { + p.mu.Lock() + p.windowMillis = d.Milliseconds() + p.mu.Unlock() + p.scheduleRefresh() + } + }) + p.windowSel.Selected = fmt.Sprintf("%ds", p.windowMillis/1000) + + toggleVisibleBtn := widget.NewButton("Toggle visible", func() { + for _, ts := range p.legendTexts { + ts.Tapped(&fyne.PointEvent{}) + } + }) + + leading := container.NewBorder( + nil, nil, p.zoom, nil, + container.New(&livePlotLayout{p: p}, p.plotObj), + ) + p.split = container.NewHSplit( + leading, + container.NewBorder( + nil, toggleVisibleBtn, nil, nil, + container.NewVScroll(p.legend), + ), + ) + p.split.Offset = 0.90 + + controls := container.NewHBox( + p.followBtn, + widget.NewLabel("Window"), + p.windowSel, + ) + + p.root = container.NewBorder(nil, controls, nil, nil, p.split) + + return &liveRenderer{p: p} +} + +type liveRenderer struct { + p *Widget + objects []fyne.CanvasObject +} + +func (r *liveRenderer) MinSize() fyne.Size { return r.p.root.MinSize() } + +func (r *liveRenderer) Layout(size fyne.Size) { + if r.p.size == size { + return + } + r.p.size = size + r.p.root.Resize(size) +} + +func (r *liveRenderer) Refresh() {} + +func (r *liveRenderer) Destroy() { r.p.Close() } + +func (r *liveRenderer) Objects() []fyne.CanvasObject { + if r.objects == nil { + r.objects = []fyne.CanvasObject{r.p.root, r.p.overlayText, r.p.cursor} + } + return r.objects +} + +// livePlotLayout sizes the plot canvas and recomputes the render resolution and +// cursor whenever the plot area changes (mirrors plotter.plotLayout). +type livePlotLayout struct { + p *Widget + oldSize fyne.Size +} + +func (t *livePlotLayout) Layout(_ []fyne.CanvasObject, plotSize fyne.Size) { + if t.oldSize == plotSize { + return + } + t.oldSize = plotSize + + t.p.overlayText.Move(fyne.NewPos(t.p.zoom.Size().Width, 20)) + t.p.plotObj.Resize(plotSize) + t.p.plotResolution = fyne.NewSize(plotSize.Width, plotSize.Height) + t.p.refresh() + t.p.layoutCursor() + t.p.cursor.Refresh() +} + +func (t *livePlotLayout) MinSize([]fyne.CanvasObject) fyne.Size { + return fyne.NewSize(400, 100) +} diff --git a/pkg/widgets/liveplotter/liveplotter_mouse.go b/pkg/widgets/liveplotter/liveplotter_mouse.go new file mode 100644 index 00000000..4b49824f --- /dev/null +++ b/pkg/widgets/liveplotter/liveplotter_mouse.go @@ -0,0 +1,73 @@ +package liveplotter + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/driver/desktop" +) + +var _ desktop.Hoverable = (*Widget)(nil) + +// Scrolled zooms the visible time span: scrolling up zooms in (shorter span), +// down zooms out toward the full window. +func (p *Widget) Scrolled(event *fyne.ScrollEvent) { + if event.Scrolled.DY > 0 { + p.zoom.SetValue(p.zoom.Value - 5) + } else { + p.zoom.SetValue(p.zoom.Value + 5) + } +} + +// Dragged pans the view back in time, pausing the live follow. Dragging right +// reveals older data; the anchored right edge is clamped to the retained window +// in refresh. +func (p *Widget) Dragged(event *fyne.DragEvent) { + plotW := p.plotObj.Size().Width + if plotW <= 0 { + return + } + if p.follow { + p.setFollow(false) + } + deltaMillis := int64(float64(event.Dragged.DX) / float64(plotW) * float64(p.spanMillis())) + p.anchorMillis -= deltaMillis + p.scheduleRefresh() +} + +func (p *Widget) DragEnd() {} + +// MouseIn / MouseMoved track the pointer over the plot so the cursor line and +// legend follow it. MouseOut returns the cursor to the right edge and the legend +// to the newest/frozen value. +func (p *Widget) MouseIn(event *desktop.MouseEvent) { p.onMouse(event.Position) } + +func (p *Widget) MouseMoved(event *desktop.MouseEvent) { p.onMouse(event.Position) } + +func (p *Widget) MouseOut() { + p.mouseInPlot = false + p.refreshCursorAndLegend() +} + +// onMouse maps a pointer position (widget-local) onto the plot and updates the +// cursor + legend. Runs on the UI goroutine. +func (p *Widget) onMouse(pos fyne.Position) { + plotW := p.plotObj.Size().Width + relX := pos.X - p.zoom.Size().Width + if relX < 0 { + relX = 0 + } + if relX > plotW { + relX = plotW + } + p.cursorX = relX + p.mouseInPlot = true + p.refreshCursorAndLegend() +} + +// refreshCursorAndLegend repositions the cursor and updates the legend readout +// without redrawing the trace (the samples are unchanged on a mouse move). +func (p *Widget) refreshCursorAndLegend() { + p.computeLegendVals() + p.updateLegendValues() + p.layoutCursor() + p.cursor.Refresh() +} diff --git a/pkg/widgets/liveplotter/liveplotter_test.go b/pkg/widgets/liveplotter/liveplotter_test.go new file mode 100644 index 00000000..422d61af --- /dev/null +++ b/pkg/widgets/liveplotter/liveplotter_test.go @@ -0,0 +1,103 @@ +package liveplotter + +import "testing" + +// newBare builds a Widget with only the fields the data path needs, so the +// windowing/retention logic can be exercised without a Fyne app. +func newBare(order []string, windowMillis int64) *Widget { + p := &Widget{ + order: order, + windowMillis: windowMillis, + follow: true, + latest: map[string]float64{}, + values: map[string][]float64{}, + } + for _, n := range order { + p.values[n] = nil + } + return p +} + +// span at full zoom equals the window. +func fullSpan(p *Widget) int64 { return p.windowMillis } + +func TestFollowWindowSlides(t *testing.T) { + const window = 120_000 // 120s + p := newBare([]string{"a"}, window) + + // 300s of frames at 10Hz. + const hz = 10 + const dur = 300_000 + for ms := int64(0); ms <= dur; ms += 1000 / hz { + p.latest["a"] = float64(ms) + p.ingest(ms, false /*not paused*/) + } + + newest := p.times[len(p.times)-1] + oldest := p.times[0] + + // Retention: compaction keeps at most ~2x the window, and never less than + // the window once full. + span := newest - oldest + if span > 2*window+1000 { + t.Fatalf("retained span %dms exceeds 2x window", span) + } + if span < window-1000 { + t.Fatalf("retained span %dms dropped below window", span) + } + + // Visible window in follow mode: right edge at newest, ~window wide, and the + // oldest visible sample is well after the very first frame (old data phased + // out). + start, end, rightT := viewRange(p.times, true, 0, fullSpan(p)) + if rightT != newest { + t.Fatalf("follow right edge = %d, want newest %d", rightT, newest) + } + if start == 0 { + t.Fatalf("visible window still starts at index 0; old entries never phased out") + } + visibleSpan := p.times[end-1] - p.times[start] + if visibleSpan > window+1000 || visibleSpan < window-2000 { + t.Fatalf("visible span %dms, want ~%dms", visibleSpan, window) + } +} + +func TestPausedFreezesAndRetains(t *testing.T) { + const window = 120_000 + p := newBare([]string{"a"}, window) + + for ms := int64(0); ms <= 200_000; ms += 100 { + p.latest["a"] = float64(ms) + p.ingest(ms, false) + } + + // Pause: anchor at the current newest, then keep ingesting without compaction. + p.follow = false + anchor := p.times[len(p.times)-1] + preLen := len(p.times) + for ms := int64(200_100); ms <= 600_000; ms += 100 { + p.latest["a"] = float64(ms) + p.ingest(ms, true /*paused*/) + } + + // Frozen view: right edge stays at the anchor regardless of new data. + start, end, rightT := viewRange(p.times, false, anchor, fullSpan(p)) + if rightT != anchor { + t.Fatalf("paused right edge = %d, want anchor %d", rightT, anchor) + } + if got := p.times[end-1]; got > anchor { + t.Fatalf("frozen view includes sample at %d past anchor %d", got, anchor) + } + visibleSpan := p.times[end-1] - p.times[start] + if visibleSpan < window-2000 { + t.Fatalf("frozen visible span %dms collapsed below window", visibleSpan) + } + + // Retention halted: nothing was trimmed while paused. + if len(p.times) <= preLen { + t.Fatalf("paused buffer did not grow: pre=%d post=%d", preLen, len(p.times)) + } + if p.times[0] != 0 { + t.Fatalf("paused buffer trimmed old data: oldest=%d, want 0", p.times[0]) + } +} From 02c72054ecd96c79de65d789aa6b5e059cda45d5 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 23:56:32 +0200 Subject: [PATCH 057/102] fix bug in matrixbuilder --- pkg/widgets/matrixbuilder/matrixbuilder.go | 37 +++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/pkg/widgets/matrixbuilder/matrixbuilder.go b/pkg/widgets/matrixbuilder/matrixbuilder.go index 92827c72..f674c338 100644 --- a/pkg/widgets/matrixbuilder/matrixbuilder.go +++ b/pkg/widgets/matrixbuilder/matrixbuilder.go @@ -384,8 +384,8 @@ func (mb *MatrixBuilder) buildUI() { buildBtn, ) - mb.xBox = container.NewVBox() - mb.yBox = container.NewHBox() + mb.xBox = container.NewHBox() + mb.yBox = container.NewVBox() mb.rebuildAxisEntries() mb.controls = container.NewVBox( @@ -421,21 +421,21 @@ func (mb *MatrixBuilder) buildUI() { mb.display = container.NewStack(mb.placeholder()) - // The Y scale runs along the vertical axis of the map; its editor sits as a - // horizontal strip beneath the display. - yPanel := container.NewBorder(nil, nil, - widget.NewLabelWithStyle("Y axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + // The X scale runs along the horizontal axis of the map; its editor sits as a + // horizontal strip above the display. + xPanel := container.NewBorder(nil, nil, + widget.NewLabelWithStyle("X axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), nil, - container.NewHScroll(mb.yBox), + container.NewHScroll(mb.xBox), ) mainSplit := container.NewHSplit( container.NewBorder( - yPanel, + xPanel, bottomBar, container.NewVBox( - widget.NewLabelWithStyle("X axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - mb.xBox, + widget.NewLabelWithStyle("Y axis values", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + mb.yBox, ), nil, mb.display, @@ -917,9 +917,10 @@ func (mb *MatrixBuilder) rebuildAxisEntries() { mb.yEntries = make([]*widget.Entry, mb.rows) mb.yBox.Objects = mb.yBox.Objects[:0] - for i := 0; i < mb.rows; i++ { + // Y runs highest-at-top to match the mapviewer, so add breakpoints in + // descending index order (the axis itself stays sorted ascending). + for i := mb.rows - 1; i >= 0; i-- { mb.yBox.Add(mb.makeAxisEntry(false, i)) - // mb.yBox.Add(xlayout.NewSpacer()) } mb.yBox.Refresh() } @@ -952,14 +953,14 @@ func (mb *MatrixBuilder) makeAxisEntry(isX bool, idx int) fyne.CanvasObject { } if isX { mb.xEntries[idx] = e - // X breakpoints stack vertically in the side panel: label beside entry. - return container.NewBorder(nil, nil, widget.NewLabel(prefix+strconv.Itoa(idx)), nil, e) + // X breakpoints run horizontally along the top: label above a + // fixed-width entry so the strip stays compact. + label := widget.NewLabelWithStyle(prefix+strconv.Itoa(idx), fyne.TextAlignLeading, fyne.TextStyle{}) + return container.NewVBox(label, layout.NewFixedWidth(64, e)) } mb.yEntries[idx] = e - // Y breakpoints run horizontally along the bottom: label above a - // fixed-width entry so the strip stays compact. - label := widget.NewLabelWithStyle(prefix+strconv.Itoa(idx), fyne.TextAlignLeading, fyne.TextStyle{}) - return container.NewVBox(label, layout.NewFixedWidth(64, e)) + // Y breakpoints stack vertically in the side panel: label beside entry. + return container.NewBorder(nil, nil, widget.NewLabel(prefix+strconv.Itoa(idx)), nil, e) } func (mb *MatrixBuilder) setCols(n int) { From 9a97b3c9c164183b8cfb973f6babce6eff7a500b Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 23:56:54 +0200 Subject: [PATCH 058/102] split out to separate minmax function in plotter --- pkg/widgets/plotter/plotter.go | 92 ++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/pkg/widgets/plotter/plotter.go b/pkg/widgets/plotter/plotter.go index bc15e00c..2e830255 100644 --- a/pkg/widgets/plotter/plotter.go +++ b/pkg/widgets/plotter/plotter.go @@ -398,6 +398,37 @@ type TimeSeries struct { valueRange float64 Color color.RGBA Enabled bool + // Auto reports that the series has no known display range and should be + // auto-ranged from its data by the caller (used by the live plotter). + Auto bool +} + +// defaultRange returns the fixed display range for the well-known symbols. ok is +// false for symbols without a preset range; the caller derives one from the data. +func defaultRange(name string) (min, max float64, ok bool) { + switch name { + case "Out.X_AccPedal", "Out.X_AccPos": + return 0, 100, true + case "ActualIn.T_Engine", "ActualIn.T_AirInlet": + return -20, 120, true + case "m_Request", "MAF.m_AirInlet", "AirMassMast.m_Request", "MAF.m_AirFromp_AirInlet": + return 0, 2200, true + case "ActualIn.p_AirInlet", "In.p_AirInlet", "ActualIn.p_AirBefThrottle", "In.p_AirBefThrottle": + return -1.0, 3.0, true + case "DisplProt.LambdaScanner", "Lambda.ADScanner", "LambdaScan.LambdaScanner", "LambdaScan.LambdaScanner2": + return 0.5, 1.5, true + case "IgnProt.fi_Offset": + return -30, 10, true + case "Lambda.LambdaInt": + return -25, 25, true + case "ECMStat.p_Diff": + return -1, 2, true + case "Lambda.External": + return 0.5, 1.5, true + case "P_medel", "Max_tryck", "Regl_tryck": + return -1, 3, true + } + return 0, 0, false } func NewTimeSeries(name string, values map[string][]float64) *TimeSeries { @@ -413,38 +444,9 @@ func NewTimeSeries(name string, values map[string][]float64) *TimeSeries { return ts } - switch name { - case "Out.X_AccPedal", "Out.X_AccPos": - ts.Min = 0 - ts.Max = 100 - case "ActualIn.T_Engine", "ActualIn.T_AirInlet": - ts.Min = -20 - ts.Max = 120 - case "m_Request", "MAF.m_AirInlet", "AirMassMast.m_Request", "MAF.m_AirFromp_AirInlet": - ts.Min = 0 - ts.Max = 2200 - case "ActualIn.p_AirInlet", "In.p_AirInlet", "ActualIn.p_AirBefThrottle", "In.p_AirBefThrottle": - ts.Min = -1.0 - ts.Max = 3.0 - case "DisplProt.LambdaScanner", "Lambda.ADScanner", "LambdaScan.LambdaScanner", "LambdaScan.LambdaScanner2": - ts.Min = 0.5 - ts.Max = 1.5 - case "IgnProt.fi_Offset": - ts.Min = -30 - ts.Max = 10 - case "Lambda.LambdaInt": - ts.Min = -25 - ts.Max = 25 - case "ECMStat.p_Diff": - ts.Min = -1 - ts.Max = 2 - case "Lambda.External": - ts.Min = 0.5 - ts.Max = 1.5 - case "P_medel", "Max_tryck", "Regl_tryck": - ts.Min = -1 - ts.Max = 3 - default: + if min, max, known := defaultRange(name); known { + ts.Min, ts.Max = min, max + } else { ts.Min, ts.Max = findMinMaxFloat64(data) } @@ -453,6 +455,32 @@ func NewTimeSeries(name string, values map[string][]float64) *TimeSeries { return ts } +// NewSeries builds a series with no data yet, for live plotting. Well-known +// symbols get their fixed display range; the rest are flagged Auto so the +// caller can range them from the live data via SetRange. +func NewSeries(name string) *TimeSeries { + ts := &TimeSeries{ + Name: name, + Color: colors.GetColor(name), + Enabled: true, + } + if min, max, known := defaultRange(name); known { + ts.SetRange(min, max) + } else { + ts.Auto = true + ts.SetRange(0, 1) + } + return ts +} + +// SetRange updates the display range used by PlotImage. valueRange is kept in +// sync so callers outside this package can re-range a series (e.g. auto-ranging +// a live signal each refresh). +func (ts *TimeSeries) SetRange(min, max float64) { + ts.Min, ts.Max = min, max + ts.valueRange = max - min +} + func (ts *TimeSeries) PlotImage(img *image.RGBA, values map[string][]float64, start, numPoints, thickness int) { dl := len(values[ts.Name]) - 1 startN, endN := min(max(start, 0), dl), min(start+numPoints, dl) From 2c6b0b108cd54e4c2e60c73f4db6013762db49c8 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 23:57:06 +0200 Subject: [PATCH 059/102] get and set value of plotter tappable text --- pkg/widgets/plotter/text.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/widgets/plotter/text.go b/pkg/widgets/plotter/text.go index eec1207e..3029587b 100644 --- a/pkg/widgets/plotter/text.go +++ b/pkg/widgets/plotter/text.go @@ -74,6 +74,17 @@ func (tt *TappableText) Refresh() { tt.text.Refresh() } +// Value returns the currently displayed value text. +func (tt *TappableText) Value() string { + return tt.value.Text +} + +// SetValue updates the displayed value text and refreshes it. +func (tt *TappableText) SetValue(s string) { + tt.value.Text = s + tt.value.Refresh() +} + func (tt *TappableText) MouseIn(e *desktop.MouseEvent) { tt.onHover(true) } From 5b26a12c28a4b35a72ef53dfef7433d51ee2437b Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 23:58:02 +0200 Subject: [PATCH 060/102] use new end of frame marker --- pkg/datalogger/baselogger.go | 7 +++++-- pkg/datalogger/t5logger.go | 2 +- pkg/datalogger/t7logger.go | 2 +- pkg/datalogger/t8logger.go | 2 +- pkg/datalogger/txbridgelogger_t5.go | 2 +- pkg/datalogger/txbridgelogger_t7.go | 2 +- pkg/datalogger/txbridgelogger_t8.go | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/datalogger/baselogger.go b/pkg/datalogger/baselogger.go index bce18dc1..cabc70e7 100644 --- a/pkg/datalogger/baselogger.go +++ b/pkg/datalogger/baselogger.go @@ -9,6 +9,7 @@ import ( "time" "github.com/roffe/gocan" + "github.com/roffe/txlogger/pkg/ebus" "github.com/roffe/txlogger/pkg/wbl" "github.com/roffe/txlogger/relayserver" ) @@ -90,13 +91,15 @@ func (bl *BaseLogger) GetRAM(address uint32, length uint32) ([]byte, error) { return req.Data, req.Wait() } -// update capture counters -func (bl *BaseLogger) onCapture() { +// update capture counters and emit the per-frame tick so live consumers can +// sample every symbol with this frame's real timestamp. +func (bl *BaseLogger) onCapture(t time.Time) { bl.captureCount++ bl.capturePerSecond++ if bl.captureCount%15 == 0 { bl.CaptureCounter(bl.captureCount) } + ebus.PublishFrame(t) } func (bl *BaseLogger) onError() { diff --git a/pkg/datalogger/t5logger.go b/pkg/datalogger/t5logger.go index 4a251882..d0481642 100644 --- a/pkg/datalogger/t5logger.go +++ b/pkg/datalogger/t5logger.go @@ -138,7 +138,7 @@ func (c *T5Client) Start() error { c.OnMessage("failed to write log: " + err.Error()) return } - c.onCapture() + c.onCapture(ts) } } }() diff --git a/pkg/datalogger/t7logger.go b/pkg/datalogger/t7logger.go index 21b93f0d..ab536d2b 100644 --- a/pkg/datalogger/t7logger.go +++ b/pkg/datalogger/t7logger.go @@ -345,7 +345,7 @@ func (c *T7Client) Start() error { c.onError() c.OnMessage("failed to write log: " + err.Error()) } - c.onCapture() + c.onCapture(timeStamp) } } }() diff --git a/pkg/datalogger/t8logger.go b/pkg/datalogger/t8logger.go index 4b6ce00f..266d01c6 100644 --- a/pkg/datalogger/t8logger.go +++ b/pkg/datalogger/t8logger.go @@ -215,7 +215,7 @@ func (c *T8Client) run(ctx context.Context, cl *gocan.Client, gm *gmlan.Client, c.OnMessage("failed to write log: " + err.Error()) } testerPresent() - c.onCapture() + c.onCapture(timeStamp) } } } diff --git a/pkg/datalogger/txbridgelogger_t5.go b/pkg/datalogger/txbridgelogger_t5.go index 36fe53d6..eb936084 100644 --- a/pkg/datalogger/txbridgelogger_t5.go +++ b/pkg/datalogger/txbridgelogger_t5.go @@ -194,7 +194,7 @@ func (c *TxBridge) t5(pctx context.Context, cl *gocan.Client) error { c.OnMessage("failed to write log: " + err.Error()) return } - c.onCapture() + c.onCapture(timeStamp) } } }() diff --git a/pkg/datalogger/txbridgelogger_t7.go b/pkg/datalogger/txbridgelogger_t7.go index c8f88f63..cbf52f84 100644 --- a/pkg/datalogger/txbridgelogger_t7.go +++ b/pkg/datalogger/txbridgelogger_t7.go @@ -258,7 +258,7 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { c.onError() c.OnMessage("failed to write log: " + err.Error()) } - c.onCapture() + c.onCapture(timeStamp) } } }() diff --git a/pkg/datalogger/txbridgelogger_t8.go b/pkg/datalogger/txbridgelogger_t8.go index b4872bd2..f705be11 100644 --- a/pkg/datalogger/txbridgelogger_t8.go +++ b/pkg/datalogger/txbridgelogger_t8.go @@ -142,7 +142,7 @@ func (c *TxBridge) t8(pctx context.Context, cl *gocan.Client) error { c.onError() c.OnMessage("failed to write log: " + err.Error()) } - c.onCapture() + c.onCapture(timeStamp) testerPresent() } } From 3b7cf9d571bbc8f7f4f735e9329f4d2418d55b2e Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 23:58:25 +0200 Subject: [PATCH 061/102] add logexport to be able to cut out sections of a log --- pkg/datalogger/log_export.go | 83 +++++++++++++++ pkg/logfile/baselog.go | 16 +++ pkg/logfile/logfile.go | 1 + pkg/widgets/logplayer/logplayer.go | 165 +++++++++++++++++++++++++---- 4 files changed, 247 insertions(+), 18 deletions(-) create mode 100644 pkg/datalogger/log_export.go diff --git a/pkg/datalogger/log_export.go b/pkg/datalogger/log_export.go new file mode 100644 index 00000000..4d16bc8f --- /dev/null +++ b/pkg/datalogger/log_export.go @@ -0,0 +1,83 @@ +package datalogger + +import ( + "errors" + "fmt" + "sort" + "strings" + + "github.com/roffe/txlogger/pkg/logfile" +) + +// ExportRecords writes the given records to a new logfile in dir, picking the +// writer from ext (one of "csv", "bpl", "t5l", "t7l", "t8l") so the exported +// clip keeps the same format as the log it was cut from. The filename is built +// from prefix plus a timestamp by createLog. It returns the full path of the +// created file. +func ExportRecords(dir, prefix, ext string, records []logfile.Record) (string, error) { + if len(records) == 0 { + return "", errors.New("no records to export") + } + + ext = strings.ToLower(strings.TrimPrefix(ext, ".")) + + file, filename, err := createLog(dir, prefix, ext) + if err != nil { + return "", err + } + + var w LogWriter + switch ext { + case "csv": + w = NewCSVWriter(file) + case "bpl": + w = NewBPLWriter(file) + case "t5l", "t7l", "t8l": + w = NewTXLWriter(file) + default: + file.Close() + return "", fmt.Errorf("unsupported export format: %s", ext) + } + + cols := recordColumns(records[0]) + + // One channel set is built up front; the backing values are rewritten for + // each record so we don't allocate a closure per cell. + values := make([]float64, len(cols)) + channels := make([]Channel, len(cols)) + for i, name := range cols { + i := i + channels[i] = Channel{ + Name: name, + read: func() float64 { return values[i] }, + format: sysvarFormat(name), + } + } + + for i := range records { + rec := records[i] + for j, name := range cols { + values[j] = rec.Values[name] + } + if err := w.Write(rec.Time, channels); err != nil { + w.Close() + return "", fmt.Errorf("failed to write record: %w", err) + } + } + + if err := w.Close(); err != nil { + return "", fmt.Errorf("failed to finalize log: %w", err) + } + return filename, nil +} + +// recordColumns returns the value column names of a record in a stable +// (alphabetical) order so the exported log has a deterministic layout. +func recordColumns(rec logfile.Record) []string { + cols := make([]string, 0, len(rec.Values)) + for k := range rec.Values { + cols = append(cols, k) + } + sort.Strings(cols) + return cols +} diff --git a/pkg/logfile/baselog.go b/pkg/logfile/baselog.go index 4406ce3d..d3f015f1 100644 --- a/pkg/logfile/baselog.go +++ b/pkg/logfile/baselog.go @@ -53,6 +53,22 @@ func (l *BaseLogfile) Pos() int { return max(l.pos, 0) } +// RecordAt returns the record at the given index without changing the playback +// position. The index is clamped to the valid range. It is safe to call +// concurrently with playback as it only reads the immutable records slice. +func (l *BaseLogfile) RecordAt(i int) Record { + if l.length == 0 { + return Record{EOF: true} + } + if i < 0 { + i = 0 + } + if i >= l.length { + i = l.length - 1 + } + return l.records[i] +} + func (l *BaseLogfile) Len() int { return l.length } diff --git a/pkg/logfile/logfile.go b/pkg/logfile/logfile.go index 937bed7d..e7d3e4af 100644 --- a/pkg/logfile/logfile.go +++ b/pkg/logfile/logfile.go @@ -20,6 +20,7 @@ type Logfile interface { Seek(int) Pos() int Len() int + RecordAt(int) Record Start() time.Time End() time.Time Close() diff --git a/pkg/widgets/logplayer/logplayer.go b/pkg/widgets/logplayer/logplayer.go index b2be8c96..7b88e19f 100644 --- a/pkg/widgets/logplayer/logplayer.go +++ b/pkg/widgets/logplayer/logplayer.go @@ -1,6 +1,7 @@ package logplayer import ( + "fmt" "sync" "time" @@ -57,6 +58,11 @@ type Logplayer struct { OnMouseDown func() + // selStart and selEnd mark the in/out points of the export selection. + // A value of -1 means the point has not been set yet. + selStart int + selEnd int + focused bool closed bool } @@ -70,6 +76,10 @@ type logplayerObjects struct { positionSlider *slider timeLabel *widget.Label speedSelect *widget.Select + setInBtn *widget.Button + setOutBtn *widget.Button + exportBtn *widget.Button + selectionLabel *widget.Label } type Config struct { @@ -77,6 +87,9 @@ type Config struct { Logfile logfile.Logfile TimeSetter func(time.Time) PlotterRenderer plotter.PlotBackend + // OnExport, when set, is called with the records of the selected range when + // the user exports a selection. When nil the selection controls are hidden. + OnExport func(records []logfile.Record) } func New(cfg *Config) *Logplayer { @@ -89,7 +102,9 @@ func New(cfg *Config) *Logplayer { objs: &logplayerObjects{ positionSlider: NewSlider(), }, - logFile: cfg.Logfile, + logFile: cfg.Logfile, + selStart: -1, + selEnd: -1, } lp.ExtendBaseWidget(lp) @@ -169,7 +184,16 @@ func (l *Logplayer) TypedKey(ev *fyne.KeyEvent) { } } -func (l *Logplayer) TypedRune(_ rune) { +func (l *Logplayer) TypedRune(r rune) { + if l.closed { + return + } + switch r { + case 'i', 'I': + l.setSelectionStart() + case 'o', 'O': + l.setSelectionEnd() + } } func (l *Logplayer) control(op *controlMsg) { @@ -230,6 +254,13 @@ func (l *Logplayer) render() { l.control(&controlMsg{Op: OpNext}) }) + l.objs.setInBtn = widget.NewButton("In", l.setSelectionStart) + l.objs.setOutBtn = widget.NewButton("Out", l.setSelectionEnd) + l.objs.exportBtn = widget.NewButtonWithIcon("Save selection", theme.DocumentSaveIcon(), l.exportSelection) + l.objs.exportBtn.Disable() + l.objs.selectionLabel = widget.NewLabel("") + l.updateSelectionLabel() + values := make(map[string][]float64) for { if rec := l.logFile.Next(); !rec.EOF { @@ -264,29 +295,47 @@ func (l *Logplayer) render() { } func (l *Logplayer) CreateRenderer() fyne.WidgetRenderer { - l.container = container.NewBorder( + controls := container.NewBorder( + nil, + nil, + container.NewGridWithColumns(4, + l.objs.rewindBtn, + l.objs.playbackToggleBtn, + l.objs.forwardBtn, + l.objs.restartBtn, + ), nil, container.NewBorder( nil, nil, - container.NewGridWithColumns(4, - l.objs.rewindBtn, - l.objs.playbackToggleBtn, - l.objs.forwardBtn, - l.objs.restartBtn, - ), nil, - container.NewBorder( - nil, - nil, - nil, - container.NewHBox( - layout.NewFixedWidth(85, l.objs.timeLabel), - layout.NewFixedWidth(75, l.objs.speedSelect), - ), - l.objs.positionSlider, + container.NewHBox( + layout.NewFixedWidth(85, l.objs.timeLabel), + layout.NewFixedWidth(75, l.objs.speedSelect), ), + l.objs.positionSlider, ), + ) + + var bottom fyne.CanvasObject = controls + if l.cfg.OnExport != nil { + selection := container.NewBorder( + nil, + nil, + container.NewHBox( + l.objs.setInBtn, + l.objs.setOutBtn, + l.objs.exportBtn, + ), + nil, + l.objs.selectionLabel, + ) + bottom = container.NewVBox(selection) + } + + l.container = container.NewBorder( + bottom, + controls, nil, nil, l.objs.plotter, @@ -367,6 +416,82 @@ func (l *Logplayer) togglePlayback() { } } +// setSelectionStart marks the in point of the export selection at the current +// playback position. +func (l *Logplayer) setSelectionStart() { + l.selStart = int(l.objs.positionSlider.Value) + l.updateSelectionLabel() +} + +// setSelectionEnd marks the out point of the export selection at the current +// playback position. +func (l *Logplayer) setSelectionEnd() { + l.selEnd = int(l.objs.positionSlider.Value) + l.updateSelectionLabel() +} + +// selectionRange returns the normalized [lo, hi] record indices of the current +// selection. An unset in point defaults to the start of the log and an unset +// out point to the end. The bounds are clamped to the log and swapped if the +// out point was set before the in point. +func (l *Logplayer) selectionRange() (int, int) { + last := l.logFile.Len() - 1 + lo, hi := l.selStart, l.selEnd + if lo == -1 { + lo = 0 + } + if hi == -1 { + hi = last + } + if lo > hi { + lo, hi = hi, lo + } + if lo < 0 { + lo = 0 + } + if hi > last { + hi = last + } + return lo, hi +} + +func (l *Logplayer) updateSelectionLabel() { + if l.objs.selectionLabel == nil { + return + } + inTxt, outTxt := "—", "—" + if l.selStart != -1 { + inTxt = l.logFile.RecordAt(l.selStart).Time.Format("15:04:05.00") + } + if l.selEnd != -1 { + outTxt = l.logFile.RecordAt(l.selEnd).Time.Format("15:04:05.00") + } + + if l.selStart == -1 && l.selEnd == -1 { + l.objs.selectionLabel.SetText("In — Out —") + l.objs.exportBtn.Disable() + return + } + + lo, hi := l.selectionRange() + l.objs.selectionLabel.SetText(fmt.Sprintf("In %s Out %s (%d samples)", inTxt, outTxt, hi-lo+1)) + l.objs.exportBtn.Enable() +} + +// exportSelection collects the records of the current selection and hands them +// to the OnExport callback so they can be written to a new logfile. +func (l *Logplayer) exportSelection() { + if l.cfg.OnExport == nil || l.logFile.Len() == 0 { + return + } + lo, hi := l.selectionRange() + records := make([]logfile.Record, 0, hi-lo+1) + for i := lo; i <= hi; i++ { + records = append(records, l.logFile.RecordAt(i)) + } + l.cfg.OnExport(records) +} + func (l *Logplayer) playLog() { speedMultiplier := 1.0 timer := time.NewTimer(0) @@ -405,6 +530,7 @@ func (l *Logplayer) playLog() { l.cfg.EBus.Publish(k, v) } timeSetter(rec.Time) + l.cfg.EBus.Publish("__frame__", float64(rec.Time.UnixMilli())) timer.Stop() } l.objs.plotter.Seek(op.Pos) @@ -424,6 +550,7 @@ func (l *Logplayer) playLog() { for k, v := range rec.Values { l.cfg.EBus.Publish(k, v) } + l.cfg.EBus.Publish("__frame__", float64(rec.Time.UnixMilli())) } l.objs.plotter.Seek(pos) @@ -443,6 +570,7 @@ func (l *Logplayer) playLog() { for k, v := range rec.Values { l.cfg.EBus.Publish(k, v) } + l.cfg.EBus.Publish("__frame__", float64(rec.Time.UnixMilli())) } if f := l.cfg.TimeSetter; f != nil { f(rec.Time) @@ -475,6 +603,7 @@ func (l *Logplayer) playLog() { for k, v := range rec.Values { l.cfg.EBus.Publish(k, v) } + l.cfg.EBus.Publish("__frame__", float64(rec.Time.UnixMilli())) if f := l.cfg.TimeSetter; f != nil { f(rec.Time) } From e8c860d18ee65d63d79bb780fbefda805f2cf2b9 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 23:58:34 +0200 Subject: [PATCH 062/102] use end of frame onCapture --- pkg/datalogger/remotelogger.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/datalogger/remotelogger.go b/pkg/datalogger/remotelogger.go index 68661083..866ccd59 100644 --- a/pkg/datalogger/remotelogger.go +++ b/pkg/datalogger/remotelogger.go @@ -3,6 +3,7 @@ package datalogger import ( "fmt" "log" + "time" "github.com/roffe/txlogger/pkg/ebus" "github.com/roffe/txlogger/relayserver" @@ -91,7 +92,7 @@ func (c *RemoteClient) Start() error { for _, va := range values { ebus.Publish(va.Name, va.Value) } - c.onCapture() + c.onCapture(time.Now()) default: log.Println("Unknown message kind:", msg.Kind.String()) } From 8e52e1adfe2690fa341a1fc18b99d102eb455813 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 18 Jun 2026 23:59:29 +0200 Subject: [PATCH 063/102] add live plot to toolbar --- pkg/windows/mainWindow.go | 18 ++++++++++++++++++ pkg/windows/mainWindow_buttons.go | 29 +++++++++++++++++++++++++++++ pkg/windows/mainWindow_toolbar.go | 1 + 3 files changed, 48 insertions(+) diff --git a/pkg/windows/mainWindow.go b/pkg/windows/mainWindow.go index bd37032b..27b32ace 100644 --- a/pkg/windows/mainWindow.go +++ b/pkg/windows/mainWindow.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "time" "fyne.io/fyne/v2" @@ -109,6 +110,7 @@ type mainWindowButtons struct { layoutRefreshBtn *widget.Button symbolListBtn *widget.Button addGaugeBtn *widget.Button + livePlotBtn *widget.Button } type mainWindowCounters struct { @@ -433,6 +435,22 @@ func (mw *MainWindow) LoadLogfile(filename string, r io.Reader, pos fyne.Positio EBus: ebus.CONTROLLER, Logfile: logz, PlotterRenderer: mw.settings.GetPlotterRenderer(), + OnExport: func(records []logfile.Record) { + ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(fp)), ".") + prefix := strings.TrimSuffix(fp, filepath.Ext(fp)) + "-clip" + logPath := mw.settings.GetLogPath() + go func() { + path, err := datalogger.ExportRecords(logPath, prefix, ext, records) + fyne.Do(func() { + if err != nil { + mw.Error(fmt.Errorf("failed to export selection: %w", err)) + return + } + mw.Log(fmt.Sprintf("exported %d samples to %s", len(records), path)) + dialog.ShowInformation("Selection exported", fmt.Sprintf("Saved %d samples to\n%s", len(records), path), mw) + }) + }() + }, }) /* content := container.NewBorder( diff --git a/pkg/windows/mainWindow_buttons.go b/pkg/windows/mainWindow_buttons.go index 5397c1f5..e3131fbd 100644 --- a/pkg/windows/mainWindow_buttons.go +++ b/pkg/windows/mainWindow_buttons.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -15,6 +16,7 @@ import ( "github.com/roffe/txlogger/pkg/datalogger" "github.com/roffe/txlogger/pkg/ebus" "github.com/roffe/txlogger/pkg/widgets/dashboard" + "github.com/roffe/txlogger/pkg/widgets/liveplotter" "github.com/roffe/txlogger/pkg/widgets/msglist" "github.com/roffe/txlogger/pkg/widgets/multiwindow" ) @@ -31,6 +33,33 @@ func (mw *MainWindow) createButtons() { mw.buttons.debugBtn = mw.newDebugBtn() mw.buttons.symbolListBtn = mw.newSymbolListBtn() mw.buttons.addGaugeBtn = mw.newaddGaugeBtn() + mw.buttons.livePlotBtn = mw.newLivePlotBtn() +} + +func (mw *MainWindow) newLivePlotBtn() *widget.Button { + return widget.NewButtonWithIcon("Live plot", theme.MediaSkipNextIcon(), func() { + if w := mw.wm.HasWindow("Live plot"); w != nil { + mw.wm.Raise(w) + return + } + + names := mw.symbolList.Names() + if len(names) == 0 { + mw.Error(fmt.Errorf("no symbols selected to plot")) + return + } + + lp := liveplotter.New(&liveplotter.Config{ + Order: names, + Window: 120 * time.Second, + }) + + lpw := multiwindow.NewInnerWindow("Live plot", lp) + lpw.Icon = theme.MediaSkipNextIcon() + lpw.OnClose = lp.Close + mw.wm.Add(lpw) + lpw.Resize(fyne.NewSize(900, 500)) + }) } func (mw *MainWindow) newaddGaugeBtn() *widget.Button { diff --git a/pkg/windows/mainWindow_toolbar.go b/pkg/windows/mainWindow_toolbar.go index b8980c5a..19c8df96 100644 --- a/pkg/windows/mainWindow_toolbar.go +++ b/pkg/windows/mainWindow_toolbar.go @@ -86,6 +86,7 @@ func (mw *MainWindow) newToolbar() *fyne.Container { mw.buttons.symbolListBtn, mw.buttons.logBtn, mw.buttons.dashboardBtn, + mw.buttons.livePlotBtn, widget.NewButtonWithIcon("Matrix", theme.GridIcon(), mw.openMatrixBuilder), widget.NewButtonWithIcon("", theme.GridIcon(), func() { mw.wm.Arrange(&multiwindow.GridArranger{}) From f0f7efff1b927dcb6e45f8a1905b1a9e3de36273 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 19 Jun 2026 00:00:32 +0200 Subject: [PATCH 064/102] add PublishFrame --- pkg/ebus/ebus.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/ebus/ebus.go b/pkg/ebus/ebus.go index 764a97da..b6db15ad 100644 --- a/pkg/ebus/ebus.go +++ b/pkg/ebus/ebus.go @@ -2,6 +2,7 @@ package ebus import ( "sync" + "time" "fyne.io/fyne/v2" "github.com/roffe/txlogger/pkg/bus" @@ -15,6 +16,11 @@ var ( const ( TOPIC_COLORBLINDMODE = "color_blind_mode" TOPIC_ECU = "selected_ecu" + // TOPIC_FRAME fires once per completed log frame, carrying the frame's + // timestamp as Unix milliseconds (float64). Subscribers use it as the frame + // boundary to sample the latest value of every symbol with a shared, real + // timestamp (see the live plotter). + TOPIC_FRAME = "__frame__" ) func init() { @@ -32,6 +38,12 @@ func Publish(topic string, data float64) { CONTROLLER.Publish(topic, data) } +// PublishFrame signals that a log frame completed at time t. The timestamp is +// carried as Unix milliseconds, which fits exactly in a float64. +func PublishFrame(t time.Time) { + CONTROLLER.Publish(TOPIC_FRAME, float64(t.UnixMilli())) +} + func SubscribeFunc(topic string, f func(float64)) func() { wrapFN := func(v float64) { fyne.Do(func() { From 6b00801e95327660c3d6f8db041f44296e9bf576 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 19 Jun 2026 00:00:52 +0200 Subject: [PATCH 065/102] update whatsnew --- pkg/assets/WHATSNEW.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index 23f33c9f..82248cba 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -1,4 +1,5 @@ # 2.1.10 +- Cut a new logfile from a selection in the logplayer: scrub to a spot and press "In" (or the `i` key) to mark the start, scrub again and press "Out" (or `o`) to mark the end, then press the save button to write just that range to a new log next to your other logs. The clip keeps the same format as the source log (csv/bpl/t5l/t7l/t8l). Leaving the In or Out point unset selects from the start or to the end of the log - Live tracking marker in the 3d mesh viewer showing where the ECU is reading from, mirroring the crosshair in the map above - Fixed the 3d mesh showing one cell less than the table in each direction; values are now cell-centered so an 18x16 map renders 18x16 cells - Performance optimization for the meshgrid From 0287f13ab728c1908a5d0b82d114b49ef0571aee Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 19 Jun 2026 00:01:14 +0200 Subject: [PATCH 066/102] update go deps --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 064176d3..3c162f0f 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 github.com/roffe/ecusymbol v1.2.0 - github.com/roffe/gocan v1.4.0 + github.com/roffe/gocan v1.4.1 go.bug.st/serial v1.6.4 golang.org/x/image v0.40.0 golang.org/x/mod v0.36.0 diff --git a/go.sum b/go.sum index cb93fdce..71a90574 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/roffe/ecusymbol v1.2.0 h1:mI5M0HG17gCgbPTbMpIR8nPJGBu4HNZp0133HcPOzYw= github.com/roffe/ecusymbol v1.2.0/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= -github.com/roffe/gocan v1.4.0 h1:OSs//lr4vy/ozyMPUbgZaNFVZWMeXzOsXhCujpA4WRs= -github.com/roffe/gocan v1.4.0/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= +github.com/roffe/gocan v1.4.1 h1:T9aAHzTxS7oXwiOlMIM2TAf75aMHcEaWAi27nKwEFQk= +github.com/roffe/gocan v1.4.1/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= From 614d6e49da2ad157e439ce87fa43eb5c2d507151 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 19 Jun 2026 14:26:03 +0200 Subject: [PATCH 067/102] better single cell viewing --- pkg/widgets/mapviewer/mapviewer.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index 8eb82724..c7d203cd 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -30,6 +30,10 @@ const ( maxTextSize = 28 ) +// singleCellColor is used for 1x1 maps where value-based color interpolation +// is meaningless (min == max yields a flat gray). ponytail: fixed accent. +var singleCellColor = color.RGBA{0xFF, 0xFF, 0xFF, 0xFF} // white + var ( // _ fyne.Tappable = (*MapViewer)(nil) _ fyne.Focusable = (*MapViewer)(nil) @@ -353,6 +357,9 @@ func (mv *MapViewer) Refresh() { } for idx, value := range mv.cfg.ZData { mv.setCellText(idx, value) + if mv.numData == 1 { + continue // single cell keeps singleCellColor + } col := colors.GetColorInterpolation( mv.zMin, mv.zMax, @@ -423,10 +430,16 @@ func (mv *MapViewer) createTextValues() { func (mv *MapViewer) createZdata() { mv.zDataRects = make([]*canvas.Rectangle, 0, mv.numData) objs := make([]fyne.CanvasObject, 0, mv.numData) + singleCell := mv.numData == 1 for _, value := range mv.cfg.ZData { - color := colors.GetColorInterpolation(mv.zMin, mv.zMax, value, mv.colorMode) - rect := &canvas.Rectangle{FillColor: color, StrokeColor: color, StrokeWidth: 0} - rect.SetMinSize(fyne.NewSize(34, 14)) + col := colors.GetColorInterpolation(mv.zMin, mv.zMax, value, mv.colorMode) + minHeight := float32(14) + if singleCell { + col = singleCellColor + minHeight = 28 + } + rect := &canvas.Rectangle{FillColor: col, StrokeColor: col, StrokeWidth: 0} + rect.SetMinSize(fyne.NewSize(34, minHeight)) mv.zDataRects = append(mv.zDataRects, rect) objs = append(objs, rect) } From e778e608893999d1b7c723742f671560aee84729 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 19 Jun 2026 14:36:05 +0200 Subject: [PATCH 068/102] refactor mainmenu again --- pkg/assets/WHATSNEW.md | 2 +- pkg/windows/mainWindow.go | 18 +- pkg/windows/mainWindow_buttons.go | 2 +- pkg/windows/mainWindow_menu.go | 269 +++++++++++++++--------------- pkg/windows/mainmenu.go | 106 +++++------- pkg/windows/mainmenu_t5.go | 118 ++++++------- pkg/windows/mainmenu_t7.go | 214 +++++++++++++----------- pkg/windows/mainmenu_t8.go | 157 ++++++++--------- pkg/windows/mainwindow_selects.go | 2 +- 9 files changed, 424 insertions(+), 464 deletions(-) diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index 82248cba..c71a2f1e 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -18,7 +18,6 @@ - Added 2d graph for viewing flat maps - Rewrote logplayer plotter to use about 50% less CPU on zoomed out views - Big refactor of the log writing logic to be simpler to maintain and be more performant -- Dial and Dual Dial now uses shaders to draw the faces, dials and pips - Improved cell selection in mapviewer - Improved copy paste in mapviewer, added paste here function - Added a Matrix builder from logfiles. It learns a 2D map from one or more logs: pick which series drives the X axis, the Y axis and supplies the Z value, and every sample that lands on a cell is averaged into it. The result is shown live in a mapviewer (colored grid + 3D mesh) and the cells can be edited by hand @@ -29,6 +28,7 @@ - Visual filter / query builder: add rules like "if " and a sample only counts as a hit when it satisfies every rule. Operators: >, >=, <, <=, ==, != and ~ (approximately equal) - Filter query language: instead of the visual rules you can type a full query with and/or, () grouping and the same operators, e.g. "if (ActualIn.n_Engine > 3000 and Out.X_AccPedal > 50) or boost ~ 1.2". Series can be compared to numbers, to each other or to arithmetic of them; a non-empty query overrides the rules - Save and load configurations as presets (series, dimensions, axis breakpoints, tolerances and filter rules) +- Added a bunch of TransCal maps under Fueling on T7 # 2.1.9 - Updated default T7 preset to include MAF.m_AirFromp_AirInlet diff --git a/pkg/windows/mainWindow.go b/pkg/windows/mainWindow.go index 27b32ace..7d9a01e9 100644 --- a/pkg/windows/mainWindow.go +++ b/pkg/windows/mainWindow.go @@ -63,15 +63,15 @@ func (s *SecretText) MouseUp(e *desktop.MouseEvent) { type MainWindow struct { fyne.Window - app fyne.App - menu *MainMenu - outputData binding.StringList - selects *mainWindowSelects - buttons *mainWindowButtons - counters *mainWindowCounters - loggingRunning bool - filename string - symbolList *symbollist.Widget + app fyne.App + leadingMenus, trailingMenus []*fyne.Menu + outputData binding.StringList + selects *mainWindowSelects + buttons *mainWindowButtons + counters *mainWindowCounters + loggingRunning bool + filename string + symbolList *symbollist.Widget as2 *as2.File fw symbol.SymbolCollection diff --git a/pkg/windows/mainWindow_buttons.go b/pkg/windows/mainWindow_buttons.go index e3131fbd..510b9c75 100644 --- a/pkg/windows/mainWindow_buttons.go +++ b/pkg/windows/mainWindow_buttons.go @@ -341,7 +341,7 @@ func (mw *MainWindow) newDashboardBtn() *widget.Button { mw.Window.SetContent(db) mw.SetFullScreen(true) } else { - mw.SetMainMenu(mw.menu.GetMenu(mw.selects.ecuSelect.Selected)) + mw.SetMainMenu(mw.GetMenu(mw.selects.ecuSelect.Selected)) mw.Window.SetContent(mw.content) dbw.Close() mw.buttons.dashboardBtn.OnTapped() diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index c4a4d21e..00f029af 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -30,151 +30,13 @@ import ( "github.com/roffe/txlogger/pkg/widgets/trionic5/pgmmod" "github.com/roffe/txlogger/pkg/widgets/trionic5/pgmstatus" "github.com/roffe/txlogger/pkg/widgets/trionic7/t7esp" - "github.com/roffe/txlogger/pkg/widgets/trionic7/t7fwinfo" ) func (mw *MainWindow) setupMenu() { - getAdapter := func() (gocan.Adapter, error) { - device, err := mw.settings.GetAdapter(mw.selects.ecuSelect.Selected) - if err != nil { - mw.Error(err) - return nil, err - } - return device, nil - } - getFW := func() symbol.SymbolCollection { return mw.fw } - getECU := func() string { - return mw.selects.ecuSelect.Selected - } - - funcMap := map[string]func(string){ - "DTC Reader": func(str string) { - if w := mw.wm.HasWindow("DTC Reader"); w != nil { - mw.wm.Raise(w) - return - } - inner := multiwindow.NewInnerWindow("DTC Reader", dtcreader.New(getFW, getECU, getAdapter, mw.Log, mw.Error)) - inner.Icon = theme.InfoIcon() - mw.wm.Add(inner) - inner.Resize(fyne.Size{Width: 600, Height: 400}) - }, - "Edit Parameters": func(str string) { - if w := mw.wm.HasWindow("Edit Parameters"); w != nil { - mw.wm.Raise(w) - return - } - param := editparameters.NewEditParameters(getAdapter, mw.Error, mw.Log) - inner := multiwindow.NewInnerWindow("Edit Parameters", param) - inner.Icon = theme.InfoIcon() - mw.wm.Add(inner) - }, - "Register EU0D": func(str string) { - if w := mw.wm.HasWindow("Register EU0D"); w != nil { - mw.wm.Raise(w) - return - } - inner := multiwindow.NewInnerWindow("Register EU0D", NewMyrtilosRegistration(mw)) - inner.Icon = theme.InfoIcon() - mw.wm.Add(inner) - }, - "ESP Calibration": func(str string) { - if w := mw.wm.HasWindow("ESP Calibration selection"); w != nil { - mw.wm.Raise(w) - return - } - if t, ok := mw.fw.(*symbol.T7File); ok { - esp := t7esp.New(mw.filename, t) - inner := multiwindow.NewInnerWindow("ESP Calibration selection", esp) - inner.Icon = theme.InfoIcon() - inner.DisableResize = true - mw.wm.Add(inner) - } else { - mw.Error(errors.New("not a T7 file")) - } - }, - "Firmware information": func(str string) { - if w := mw.wm.HasWindow("Firmware info"); w != nil { - mw.wm.Raise(w) - return - } - if t, ok := mw.fw.(*symbol.T7File); ok { - fwinfo := t7fwinfo.New(t) - inner := multiwindow.NewInnerWindow("Firmware info", fwinfo) - inner.Icon = theme.InfoIcon() - mw.wm.Add(inner) - } - }, - "Firmware info edit": func(str string) { - if w := mw.wm.HasWindow("Firmware info edit"); w != nil { - mw.wm.Raise(w) - return - } - tf := new(t8file.T8File) - filename := fyne.CurrentApp().Preferences().String("lastBinFile") - tf.GetInfo(filename) - tf.ShowEditT8Dialog(mw) - }, - "Pgm_mod!": func(str string) { - if w := mw.wm.HasWindow("Pgm_mod!"); w != nil { - mw.wm.Raise(w) - return - } - symZ := mw.fw.GetByName("Pgm_mod!") - pgm := pgmmod.New() - pgm.LoadFunc = func() ([]byte, error) { - if mw.dlc != nil { - log.Printf("Loading Pgm_mod! from ECU $%X", symZ.SramOffset) - data, err := mw.dlc.GetRAM(symZ.SramOffset, uint32(symZ.Length)) - if err != nil { - return nil, err - } - return data, nil - } - log.Printf("Loading Pgm_mod! from Binary $%X", symZ.Address) - return symZ.Bytes(), nil - } - - pgm.SaveFunc = func(data []byte) error { - if len(data) != int(symZ.Length) { - return fmt.Errorf("data length mismatch: got %d, want %d", len(data), symZ.Length) - } - if mw.dlc != nil { - log.Printf("Saving Pgm_mod! to ECU $%X", symZ.SramOffset) - if err := mw.dlc.SetRAM(symZ.SramOffset, data); err != nil { - return err - } - return nil - } - log.Printf("Saving Pgm_mod! to Binary $%X", symZ.Address) - return symZ.SetData(data) - } - - pgm.Set(symZ.Bytes()) - mapWindow := multiwindow.NewInnerWindow("Pgm_mod!", pgm) - mapWindow.Icon = theme.GridIcon() - mw.wm.Add(mapWindow) - }, - "Pgm_status": func(str string) { - if w := mw.wm.HasWindow("Pgm_status"); w != nil { - return - } - pgs := pgmstatus.New() - cancel := ebus.SubscribeFunc("Pgm_status", pgs.Set) - iw := multiwindow.NewInnerWindow("Pgm_status", pgs) - iw.Icon = theme.InfoIcon() - iw.OnClose = func() { - if cancel != nil { - cancel() - } - } - mw.wm.Add(iw) - }, - } - openItem := fyne.NewMenuItemWithIcon("Open", theme.FolderIcon(), nil) openItem.ChildMenu = fyne.NewMenu("File", fyne.NewMenuItemWithIcon("Open binary", theme.DocumentIcon(), mw.loadBinary), @@ -279,7 +141,136 @@ func (mw *MainWindow) setupMenu() { ), } - mw.menu = NewMenu(mw, leading, trailing, mw.openMap, funcMap) + mw.leadingMenus = leading + mw.trailingMenus = trailing +} + +func (mw *MainWindow) getAdapter() (gocan.Adapter, error) { + device, err := mw.settings.GetAdapter(mw.selects.ecuSelect.Selected) + if err != nil { + mw.Error(err) + return nil, err + } + return device, nil +} + +func (mw *MainWindow) openDTCReader() { + if w := mw.wm.HasWindow("DTC Reader"); w != nil { + mw.wm.Raise(w) + return + } + getFW := func() symbol.SymbolCollection { return mw.fw } + getECU := func() string { return mw.selects.ecuSelect.Selected } + inner := multiwindow.NewInnerWindow("DTC Reader", dtcreader.New(getFW, getECU, mw.getAdapter, mw.Log, mw.Error)) + inner.Icon = theme.InfoIcon() + mw.wm.Add(inner) + inner.Resize(fyne.Size{Width: 600, Height: 400}) +} + +func (mw *MainWindow) openEditParameters() { + if w := mw.wm.HasWindow("Edit Parameters"); w != nil { + mw.wm.Raise(w) + return + } + param := editparameters.NewEditParameters(mw.getAdapter, mw.Error, mw.Log) + inner := multiwindow.NewInnerWindow("Edit Parameters", param) + inner.Icon = theme.InfoIcon() + mw.wm.Add(inner) +} + +func (mw *MainWindow) openRegisterEU0D() { + if w := mw.wm.HasWindow("Register EU0D"); w != nil { + mw.wm.Raise(w) + return + } + inner := multiwindow.NewInnerWindow("Register EU0D", NewMyrtilosRegistration(mw)) + inner.Icon = theme.InfoIcon() + mw.wm.Add(inner) +} + +func (mw *MainWindow) openESPCalibration() { + if w := mw.wm.HasWindow("ESP Calibration selection"); w != nil { + mw.wm.Raise(w) + return + } + t, ok := mw.fw.(*symbol.T7File) + if !ok { + mw.Error(errors.New("not a T7 file")) + return + } + esp := t7esp.New(mw.filename, t) + inner := multiwindow.NewInnerWindow("ESP Calibration selection", esp) + inner.Icon = theme.InfoIcon() + inner.DisableResize = true + mw.wm.Add(inner) +} + +func (mw *MainWindow) openFirmwareInfoEdit() { + if w := mw.wm.HasWindow("Firmware info edit"); w != nil { + mw.wm.Raise(w) + return + } + tf := new(t8file.T8File) + filename := fyne.CurrentApp().Preferences().String("lastBinFile") + tf.GetInfo(filename) + tf.ShowEditT8Dialog(mw) +} + +func (mw *MainWindow) openPgmMod() { + if w := mw.wm.HasWindow("Pgm_mod!"); w != nil { + mw.wm.Raise(w) + return + } + symZ := mw.fw.GetByName("Pgm_mod!") + pgm := pgmmod.New() + pgm.LoadFunc = func() ([]byte, error) { + if mw.dlc != nil { + log.Printf("Loading Pgm_mod! from ECU $%X", symZ.SramOffset) + data, err := mw.dlc.GetRAM(symZ.SramOffset, uint32(symZ.Length)) + if err != nil { + return nil, err + } + return data, nil + } + log.Printf("Loading Pgm_mod! from Binary $%X", symZ.Address) + return symZ.Bytes(), nil + } + + pgm.SaveFunc = func(data []byte) error { + if len(data) != int(symZ.Length) { + return fmt.Errorf("data length mismatch: got %d, want %d", len(data), symZ.Length) + } + if mw.dlc != nil { + log.Printf("Saving Pgm_mod! to ECU $%X", symZ.SramOffset) + if err := mw.dlc.SetRAM(symZ.SramOffset, data); err != nil { + return err + } + return nil + } + log.Printf("Saving Pgm_mod! to Binary $%X", symZ.Address) + return symZ.SetData(data) + } + + pgm.Set(symZ.Bytes()) + mapWindow := multiwindow.NewInnerWindow("Pgm_mod!", pgm) + mapWindow.Icon = theme.GridIcon() + mw.wm.Add(mapWindow) +} + +func (mw *MainWindow) openPgmStatus() { + if w := mw.wm.HasWindow("Pgm_status"); w != nil { + return + } + pgs := pgmstatus.New() + cancel := ebus.SubscribeFunc("Pgm_status", pgs.Set) + iw := multiwindow.NewInnerWindow("Pgm_status", pgs) + iw.Icon = theme.InfoIcon() + iw.OnClose = func() { + if cancel != nil { + cancel() + } + } + mw.wm.Add(iw) } func (mw *MainWindow) loadBinary() { diff --git a/pkg/windows/mainmenu.go b/pkg/windows/mainmenu.go index 165cf718..472b0315 100644 --- a/pkg/windows/mainmenu.go +++ b/pkg/windows/mainmenu.go @@ -1,90 +1,68 @@ package windows import ( - "strings" - "fyne.io/fyne/v2" "fyne.io/fyne/v2/theme" symbol "github.com/roffe/ecusymbol" ) -type MainMenu struct { - w fyne.Window - leading, trailing []*fyne.Menu - openFunc func(symbol.ECUType, string, string) - //multiFunc func(symbol.ECUType, ...string) - funcMap map[string]func(string) -} - -func NewMenu(w fyne.Window, leading, trailing []*fyne.Menu, openFunc func(symbol.ECUType, string, string), funcMap map[string]func(string)) *MainMenu { - return &MainMenu{ - w: w, - openFunc: openFunc, - leading: leading, - trailing: trailing, - funcMap: funcMap, - } +type MenuItem struct { + Name string + Children []MenuItem + Func func() + Data string } -func (mw *MainMenu) GetMenu(name string) *fyne.MainMenu { - var order []string - var ecuM map[string][]string +func (mw *MainWindow) GetMenu(name string) *fyne.MainMenu { + var tree []MenuItem var typ symbol.ECUType switch name { case "T5": - order = T5SymbolsTuningOrder - ecuM = T5SymbolsTuning + tree = mw.t5Menu() typ = symbol.ECU_T5 case "T7": - order = T7SymbolsTuningOrder - ecuM = T7SymbolsTuning + tree = mw.t7Menu() typ = symbol.ECU_T7 case "T8": - order = T8SymbolsTuningOrder - ecuM = T8SymbolsTuning + tree = mw.t8Menu() typ = symbol.ECU_T8 } - menus := append([]*fyne.Menu{}, mw.leading...) - - for _, category := range order { - var items []*fyne.MenuItem - for _, mapName := range ecuM[category] { - if f, ok := mw.funcMap[mapName]; ok { - itm := fyne.NewMenuItemWithIcon(mapName, theme.ComputerIcon(), func() { - f(mapName) - }) - items = append(items, itm) - continue - } + menus := append([]*fyne.Menu{}, mw.leadingMenus...) + for _, category := range tree { + menus = append(menus, fyne.NewMenu(category.Name, mw.buildItems(typ, category.Children)...)) + } + menus = append(menus, mw.trailingMenus...) - if strings.Contains(mapName, "|") { - parts := strings.Split(mapName, "|") - names := parts[1:] - if len(parts) == 2 { - itm := fyne.NewMenuItemWithIcon(parts[0], theme.GridIcon(), func() { - mw.openFunc(typ, parts[0], names[0]) - }) - items = append(items, itm) - continue - } - //itm := fyne.NewMenuItem(parts[0], func() { - // mw.multiFunc(typ, names...) - //}) - //items = append(items, itm) - continue - } + return fyne.NewMainMenu(menus...) +} - itm := fyne.NewMenuItemWithIcon(mapName, theme.GridIcon(), func() { - mw.openFunc(typ, "", mapName) - }) - items = append(items, itm) - } - menus = append(menus, fyne.NewMenu(category, items...)) +func (mw *MainWindow) buildItems(typ symbol.ECUType, items []MenuItem) []*fyne.MenuItem { + var out []*fyne.MenuItem + for _, item := range items { + out = append(out, mw.buildItem(typ, item)) } + return out +} - menus = append(menus, mw.trailing...) - - return fyne.NewMainMenu(menus...) +func (mw *MainWindow) buildItem(typ symbol.ECUType, item MenuItem) *fyne.MenuItem { + switch { + case len(item.Children) > 0: + itm := fyne.NewMenuItemWithIcon(item.Name, theme.FolderIcon(), nil) + itm.ChildMenu = fyne.NewMenu(item.Name, mw.buildItems(typ, item.Children)...) + return itm + case item.Func != nil: + return fyne.NewMenuItemWithIcon(item.Name, theme.ComputerIcon(), item.Func) + case item.Data != "": + // title + symbol: open as a map + return fyne.NewMenuItemWithIcon(item.Name, theme.GridIcon(), func() { + mw.openMap(typ, item.Name, item.Data) + }) + default: + // Name is itself a symbol + return fyne.NewMenuItemWithIcon(item.Name, theme.GridIcon(), func() { + mw.openMap(typ, "", item.Name) + }) + } } diff --git a/pkg/windows/mainmenu_t5.go b/pkg/windows/mainmenu_t5.go index 75eca147..d31e37a9 100644 --- a/pkg/windows/mainmenu_t5.go +++ b/pkg/windows/mainmenu_t5.go @@ -1,67 +1,57 @@ package windows -var T5SymbolsTuningOrder = []string{ - "Diagnostics", - "Options", - "Injection [Fuel]", - "Ignition", - "Turbo control [M]", - "Turbo control [A]", - "Knock detection", - "Warmup", - "Idle", -} - -var T5SymbolsTuning = map[string][]string{ - "Diagnostics": { - "DTC Reader", - "Pgm_status", - }, - "Options": { - "Pgm_mod!", - }, - "Injection [Fuel]": { - "VE map - normal|Insp_mat!", - "VE map - knock|Fuel_knock_mat!", - "Injector scaling|Inj_konst!", - "Battery correction map|Batt_korr_tab!", - "Fuel cut in overboost|Tryck_vakt_tab!", - }, - "Ignition": { - "Ignition normal|Ign_map_0!", - "Ignition knock|Ign_map_2!", - "Ignition warmup|Ign_map_4!", - }, - "Turbo control [M]": { - "Boost request map|Tryck_mat!", - "Boost control bias|Reg_kon_mat!", - "P factors|P_fors!", - "I factors|I_fors!", - "D factors|D_fors!", - "Boost limit in 1st gear|Regl_tryck_fgm!", - "Boost limit in 2nd gear|Regl_tryck_sgm!", - }, - "Turbo control [A]": { - "Boost request map|Tryck_mat_a!", - "Boost control bias|Reg_kon_mat_a!", - "P factors|P_fors_a!", - "I factors|I_fors_a!", - "D factors|D_fors_a!", - "Boost limit in 1st gear|Regl_tryck_fgaut!", - }, - "Knock detection": { - "Knock sensitivity map|Knock_ref_matrix!", - "Ignition retard limit|Knock_lim_tab!", - "Boost reduction map|Apc_knock_tab!", - }, - "Warmup": { - "Afterstart enrichment (1)|Eftersta_fak!", - "Afterstart enrichment (2)|Eftersta_fak2!", - }, - "Idle": { - "Idle target RPM|Idle_rpm_tab!", - "Idle ignition|Ign_idle_angle!", - "Idle ignition correction|Ign_map_1!", - "Idle fuel map|Idle_fuel_korr!", - }, +func (mw *MainWindow) t5Menu() []MenuItem { + return []MenuItem{ + {Name: "Diagnostics", Children: []MenuItem{ + {Name: "DTC Reader", Func: mw.openDTCReader}, + {Name: "Pgm_status", Func: mw.openPgmStatus}, + }}, + {Name: "Options", Children: []MenuItem{ + {Name: "Pgm_mod!", Func: mw.openPgmMod}, + }}, + {Name: "Injection [Fuel]", Children: []MenuItem{ + {Name: "VE map - normal", Data: "Insp_mat!"}, + {Name: "VE map - knock", Data: "Fuel_knock_mat!"}, + {Name: "Injector scaling", Data: "Inj_konst!"}, + {Name: "Battery correction map", Data: "Batt_korr_tab!"}, + {Name: "Fuel cut in overboost", Data: "Tryck_vakt_tab!"}, + }}, + {Name: "Ignition", Children: []MenuItem{ + {Name: "Ignition normal", Data: "Ign_map_0!"}, + {Name: "Ignition knock", Data: "Ign_map_2!"}, + {Name: "Ignition warmup", Data: "Ign_map_4!"}, + }}, + {Name: "Turbo control [M]", Children: []MenuItem{ + {Name: "Boost request map", Data: "Tryck_mat!"}, + {Name: "Boost control bias", Data: "Reg_kon_mat!"}, + {Name: "P factors", Data: "P_fors!"}, + {Name: "I factors", Data: "I_fors!"}, + {Name: "D factors", Data: "D_fors!"}, + {Name: "Boost limit in 1st gear", Data: "Regl_tryck_fgm!"}, + {Name: "Boost limit in 2nd gear", Data: "Regl_tryck_sgm!"}, + }}, + {Name: "Turbo control [A]", Children: []MenuItem{ + {Name: "Boost request map", Data: "Tryck_mat_a!"}, + {Name: "Boost control bias", Data: "Reg_kon_mat_a!"}, + {Name: "P factors", Data: "P_fors_a!"}, + {Name: "I factors", Data: "I_fors_a!"}, + {Name: "D factors", Data: "D_fors_a!"}, + {Name: "Boost limit in 1st gear", Data: "Regl_tryck_fgaut!"}, + }}, + {Name: "Knock detection", Children: []MenuItem{ + {Name: "Knock sensitivity map", Data: "Knock_ref_matrix!"}, + {Name: "Ignition retard limit", Data: "Knock_lim_tab!"}, + {Name: "Boost reduction map", Data: "Apc_knock_tab!"}, + }}, + {Name: "Warmup", Children: []MenuItem{ + {Name: "Afterstart enrichment (1)", Data: "Eftersta_fak!"}, + {Name: "Afterstart enrichment (2)", Data: "Eftersta_fak2!"}, + }}, + {Name: "Idle", Children: []MenuItem{ + {Name: "Idle target RPM", Data: "Idle_rpm_tab!"}, + {Name: "Idle ignition", Data: "Ign_idle_angle!"}, + {Name: "Idle ignition correction", Data: "Ign_map_1!"}, + {Name: "Idle fuel map", Data: "Idle_fuel_korr!"}, + }}, + } } diff --git a/pkg/windows/mainmenu_t7.go b/pkg/windows/mainmenu_t7.go index d34e2f65..2dcc4c26 100644 --- a/pkg/windows/mainmenu_t7.go +++ b/pkg/windows/mainmenu_t7.go @@ -1,106 +1,116 @@ package windows -var T7SymbolsTuningOrder = []string{ - "Diagnostics", - "Calibration", - "Injectors", - "Fuel", - "Ignition", - "Airmass", - "Boost", - "Knock", - "Limiters", - "Adaption", - "Myrtilos", -} +func (mw *MainWindow) t7Menu() []MenuItem { + return []MenuItem{ + {Name: "Diagnostics", Children: []MenuItem{ + {Name: "DTC Reader", Func: mw.openDTCReader}, + {Name: "F_KnkDetAdap.FKnkCntMap"}, + {Name: "F_KnkDetAdap.RKnkCntMap"}, + {Name: "KnkDetAdap.KnkCntMap"}, + {Name: "MissfAdap.MissfCntMap"}, + }}, + {Name: "Calibration", Children: []MenuItem{ + {Name: "ESP Calibration", Func: mw.openESPCalibration}, + {Name: "AirCompCal.PressMap"}, + {Name: "Ethanol adaption value", Data: "E85.X_EthAct_Tech2"}, + {Name: "MAFCal.m_RedundantAirMap"}, + {Name: "TCompCal.EnrFacE85Tab"}, + {Name: "TCompCal.EnrFacTab"}, + {Name: "VIOSMAFCal.FreqSP"}, + {Name: "VIOSMAFCal.Q_AirInletTab2"}, + }}, + {Name: "Injectors", Children: []MenuItem{ + {Name: "Injector dead time", Data: "InjCorrCal.BattCorrTab"}, + {Name: "Injector dead time (Y)", Data: "InjCorrCal.BattCorrSP"}, + {Name: "Injector constant", Data: "InjCorrCal.InjectorConst"}, + }}, + {Name: "Airmass", Children: []MenuItem{ + {Name: "Pedal request map", Data: "PedalMapCal.m_RequestMap"}, + {Name: "Pedal request airmass (Y)", Data: "TorqueCal.m_PedYSP"}, + {Name: "Air/Torque calibration", Data: "TorqueCal.m_AirTorqMap"}, + {Name: "Air/Torque (X)", Data: "TorqueCal.M_EngXSP"}, + {Name: "Nom. torque map", Data: "TorqueCal.M_NominalMap"}, + {Name: "Nom. torque map (X)", Data: "TorqueCal.m_AirXSP"}, + }}, + {Name: "Fuel", Children: []MenuItem{ + {Name: "TransCal", Children: []MenuItem{ + {Name: "TransCal.ST_Enable"}, + {Name: "TransCal.ST_DecNoLim"}, + {Name: "TransCal.AccRampFac"}, + {Name: "TransCal.DecRampFac"}, + {Name: "TransCal.AccFacMap"}, + {Name: "TransCal.DecFacMap"}, + {Name: "TransCal.RpmSP (Y)", Data: "TransCal.RpmSP"}, + {Name: "TransCal.AccSP (X)", Data: "TransCal.AccSP"}, + {Name: "TransCal.DecSP (X)", Data: "TransCal.DecSP"}, + {Name: "TransCal.RetMul"}, + {Name: "TransCal.AccMul"}, + {Name: "TransCal.RetMulConst"}, + {Name: "TransCal.AccMulConst"}, + {Name: "TransCal.Delay1"}, + {Name: "TransCal.Delay2"}, -var T7SymbolsTuning = map[string][]string{ - "Diagnostics": { - "DTC Reader", - // "Firmware information", - "F_KnkDetAdap.FKnkCntMap", - "F_KnkDetAdap.RKnkCntMap", - "KnkDetAdap.KnkCntMap", - "MissfAdap.MissfCntMap", - }, - "Calibration": { - "ESP Calibration", - "AirCompCal.PressMap", - "Ethanol adaption value|E85.X_EthAct_Tech2", - "MAFCal.m_RedundantAirMap", - "TCompCal.EnrFacE85Tab", - "TCompCal.EnrFacTab", - "VIOSMAFCal.FreqSP", - "VIOSMAFCal.Q_AirInletTab2", - }, - "Injectors": { - "Injector dead time|InjCorrCal.BattCorrTab", - "Injector dead time (Y)|InjCorrCal.BattCorrSP", - "Injector constant|InjCorrCal.InjectorConst", - }, - "Airmass": { - "Pedal request map|PedalMapCal.m_RequestMap", - "Pedal request airmass (Y)|TorqueCal.m_PedYSP", - "Air/Torque calibration|TorqueCal.m_AirTorqMap", - "Air/Torque (X)|TorqueCal.M_EngXSP", - "Nom. torque map|TorqueCal.M_NominalMap", - "Nom. torque map (X)|TorqueCal.m_AirXSP", - }, - "Fuel": { - "VE map|BFuelCal.Map", - "Startup VE map / E85 VE map|BFuelCal.StartMap", - "Gas VE map|BFuelCal.GasMap", - "Enrichment factor during starting|StartCal.EnrFacTab", - "Enrichment factor during starting E85|StartCal.EnrFacE85Tab", - }, - "Ignition": { - "Ignition map|IgnNormCal.Map", - "Ignition for E85|IgnE85Cal.fi_AbsMap", - "Ignition for Gas|IgnNormCal.GasMap", - "Ignition Idle|IgnIdleCal.fi_IdleMap", - "Ignition Start|IgnStartCal.fi_StartMap", - "Knock pull map|IgnKnkCal.IndexMap", - "Max knock pull|KnkFuelCal.fi_MapMaxOff", - }, - "Boost": { - "Boost calibration|BoostCal.RegMap", - "P factor|BoostCal.PMap", - "I factor|BoostCal.IMap", - "D factor|BoostCal.DMap", - }, - "Knock": { - "Knock enrichment|KnkFuelCal.EnrichmentMap", - "Knock sensitivity|KnkDetCal.RefFactorMap", - }, - "Limiters": { - "Airmass (M)|BstKnkCal.MaxAirmass", - "Engine torque (M)|TorqueCal.M_EngMaxTab", - "Engine torque for E85 (M)|TorqueCal.M_EngMaxE85Tab", - "Gear Torque (M)|TorqueCal.M_ManGearLim", - "Gear Torque (5th)|TorqueCal.M_5GearLimTab", - "Airmass (A)|BstKnkCal.MaxAirmassAu", - "Engine torque (A)|TorqueCal.M_EngMaxAutTab", - "Engine torque for E85 (A)|TorqueCal.M_EngMaxE85TabAut", - "RPM limiter|MaxSpdCal.n_EngLimAir", - "Fuel cut|FCutCal.m_AirInletLimit", - "Speed limiter|MaxVehicCal.v_MaxSpeed", - "Overboost|TorqueCal.M_OverBoostTab", - }, - "Adaption": { - "Temp limit for adaption|AdpFuelCal.T_AdaptLim", - "Fuelcut enabled|FCutCal.ST_Enable", - "Closed loop regulation|LambdaCal.ST_Enable", - "Purge enabled|PurgeCal.ST_PurgeEnable", - "Biopower enabled|E85Cal.ST_Enable", - }, - "Myrtilos": { - "Register EU0D", - "MyrtilosCal.Launch_DisableSpeed", - "MyrtilosCal.Launch_Ign_fi_Min", - "MyrtilosCal.Launch_RPM", - "MyrtilosCal.Launch_InjFac_at_rpm", - "MyrtilosCal.Launch_PWM_max_at_stand", - "MyrtilosAdap.WBLambda_FeedbackMap", - "MyrtilosAdap.WBLambda_FFMap", - }, + // {Name: "TransCal."}, + }}, + {Name: "VE map", Data: "BFuelCal.Map"}, + {Name: "Startup VE map / E85 VE map", Data: "BFuelCal.StartMap"}, + {Name: "Gas VE map", Data: "BFuelCal.GasMap"}, + {Name: "Enrichment factor during starting", Data: "StartCal.EnrFacTab"}, + {Name: "Enrichment factor during starting E85", Data: "StartCal.EnrFacE85Tab"}, + }}, + {Name: "Ignition", Children: []MenuItem{ + {Name: "Ignition map", Data: "IgnNormCal.Map"}, + {Name: "Ignition for E85", Data: "IgnE85Cal.fi_AbsMap"}, + {Name: "Ignition for Gas", Data: "IgnNormCal.GasMap"}, + {Name: "Ignition Idle", Data: "IgnIdleCal.fi_IdleMap"}, + {Name: "Ignition Start", Data: "IgnStartCal.fi_StartMap"}, + {Name: "Knock pull map", Data: "IgnKnkCal.IndexMap"}, + {Name: "Max knock pull", Data: "KnkFuelCal.fi_MapMaxOff"}, + }}, + {Name: "Boost", Children: []MenuItem{ + {Name: "Boost calibration", Data: "BoostCal.RegMap"}, + {Name: "P factor", Data: "BoostCal.PMap"}, + {Name: "I factor", Data: "BoostCal.IMap"}, + {Name: "D factor", Data: "BoostCal.DMap"}, + }}, + {Name: "Knock", Children: []MenuItem{ + {Name: "Knock enrichment", Data: "KnkFuelCal.EnrichmentMap"}, + {Name: "Knock sensitivity", Data: "KnkDetCal.RefFactorMap"}, + }}, + {Name: "Limiters", Children: []MenuItem{ + {Name: "Manual", Children: []MenuItem{ + {Name: "Airmass (M)", Data: "BstKnkCal.MaxAirmass"}, + {Name: "Engine torque (M)", Data: "TorqueCal.M_EngMaxTab"}, + {Name: "Engine torque for E85 (M)", Data: "TorqueCal.M_EngMaxE85Tab"}, + {Name: "Gear Torque (M)", Data: "TorqueCal.M_ManGearLim"}, + {Name: "Gear Torque (5th)", Data: "TorqueCal.M_5GearLimTab"}, + }}, + {Name: "Automatic", Children: []MenuItem{ + {Name: "Airmass (A)", Data: "BstKnkCal.MaxAirmassAu"}, + {Name: "Engine torque (A)", Data: "TorqueCal.M_EngMaxAutTab"}, + {Name: "Engine torque for E85 (A)", Data: "TorqueCal.M_EngMaxE85TabAut"}, + }}, + {Name: "RPM limiter", Data: "MaxSpdCal.n_EngLimAir"}, + {Name: "Fuel cut", Data: "FCutCal.m_AirInletLimit"}, + {Name: "Speed limiter", Data: "MaxVehicCal.v_MaxSpeed"}, + {Name: "Overboost", Data: "TorqueCal.M_OverBoostTab"}, + }}, + {Name: "Adaption", Children: []MenuItem{ + {Name: "Temp limit for adaption", Data: "AdpFuelCal.T_AdaptLim"}, + {Name: "Fuelcut enabled", Data: "FCutCal.ST_Enable"}, + {Name: "Closed loop regulation", Data: "LambdaCal.ST_Enable"}, + {Name: "Purge enabled", Data: "PurgeCal.ST_PurgeEnable"}, + {Name: "Biopower enabled", Data: "E85Cal.ST_Enable"}, + }}, + {Name: "Myrtilos", Children: []MenuItem{ + {Name: "Register EU0D", Func: mw.openRegisterEU0D}, + {Name: "MyrtilosCal.Launch_DisableSpeed"}, + {Name: "MyrtilosCal.Launch_Ign_fi_Min"}, + {Name: "MyrtilosCal.Launch_RPM"}, + {Name: "MyrtilosCal.Launch_InjFac_at_rpm"}, + {Name: "MyrtilosCal.Launch_PWM_max_at_stand"}, + {Name: "MyrtilosAdap.WBLambda_FeedbackMap"}, + {Name: "MyrtilosAdap.WBLambda_FFMap"}, + }}, + } } diff --git a/pkg/windows/mainmenu_t8.go b/pkg/windows/mainmenu_t8.go index 7c62f361..c57e5abe 100644 --- a/pkg/windows/mainmenu_t8.go +++ b/pkg/windows/mainmenu_t8.go @@ -1,86 +1,77 @@ package windows -var T8SymbolsTuningOrder = []string{ - "Diagnostics", - "Airmass", - "Torque", - "Injectors", - "Fuel", - "Boost", - "Ignition", - "Pedal", -} - -var T8SymbolsTuning = map[string][]string{ - "Diagnostics": { - "DTC Reader", - "Edit Parameters", - "Firmware info edit", - }, - "Airmass": { - "Max airmass map (manual)|BstKnkCal.MaxAirmass", - "Max airmass map (auto)|BstKnkCal.MaxAirmassAu", - "Airmass Fuelcut|FCutCal.m_AirInletLimit", - "AirCtrlCal.AirmassLimiter", - "AirCtrlCal.PRatioMaxTab", - }, - "Torque": { - "Nominal torque map|TrqMastCal.Trq_NominalMap", - "Airmass torque map|TrqMastCal.m_AirTorqMap", - "Ambient pressure trq limiter|TrqLimCal.Trq_CompressorNoiseRedLimMAP", - "Trq limit in overboost|TrqLimCal.Trq_OverBoostTab", - "Trq limit manual 150hp|TrqLimCal.Trq_MaxEngineManTab2", - "Trq limit manual 175+hp|TrqLimCal.Trq_MaxEngineManTab1", - "Trq limit auto 150hp|TrqLimCal.Trq_MaxEngineAutTab2", - "Trq limit auto 175+hp|TrqLimCal.Trq_MaxEngineAutTab1", - "Manual gear trq limit|TrqLimCal.Trq_ManGear", - "RPM limiter|MaxEngSpdCal.n_EngLimTab", - "TrqLimCal.Trq_MaxEngineTab1", - "TrqLimCal.Trq_MaxEngineTab2", - "FFTrqCal.FFTrq_MaxEngineTab1", - "FFTrqCal.FFTrq_MaxEngineTab2", - }, - "Injectors": { - "Inj. Constant|InjCorrCal.InjectorConst", - "Inj. dead time|InjCorrCal.BattCorrTab", - "Inj. dead time (Y)|InjCorrCal.BattCorrSP", - }, - "Fuel": { - "Fuel correction map|BFuelCal.LambdaOneFacMap", - "Enrichment Petrol|BFuelCal.TempEnrichFacMap", - "Enrichment E85|FFFuelCal.TempEnrichFacMAP", - "Knock fuel map|KnkFuelCal.EnrichmentMap", - "Injection end angle map|InjAnglCal.Map", - "Jerk enrichment petrol|BFuelCal.m_AirJerkTab", - "Jerk enrichment Fuelmaster|BFuelCal.JerkEnrichFacTab", - "PurgeCal.ST_PurgeEnable", - "LambdaCal.ST_Enable", - "FCutCal.ST_Enable", - "FFFuelCal.ST_enable", - "FuelDynCal.ST_Enable", - "TCompCal.ST_Enable", - }, - "Boost": { - "Boost regulation map|AirCtrlCal.RegMap", - "P factor|AirCtrlCal.Ppart_BoostMap", - "I factor|AirCtrlCal.Ipart_BoostMap", - "D factor|AirCtrlCal.Dpart_BoostMap", - "AirCtrlCal.ST_BoostEnable", - "BoostAdapCal.ST_enable", - "FrompAdapCal.ST_enable", - "AreaAdapCal.ST_enable", - }, - "Ignition": { - "Normal ignition map|IgnAbsCal.fi_NormalMAP", - "High octane map|IgnAbsCal.fi_highOctanMAP", - "Low octane map|IgnAbsCal.fi_lowOctanMAP", - "MBT ignition map|IgnAbsCal.fi_IgnMBTMAP", - "Fuel cut ignition map|IgnAbsCal.fi_FuelCutMAP", - "Startup map|IgnAbsCal.fi_StartMAP", - "IgnAbsCal.ST_EnableOctanMaps", - }, - "Pedal": { - "Pedal position map|TrqMastCal.X_AccPedalMAP", - "Torque request map|PedalMapCal.Trq_RequestMap", - }, +func (mw *MainWindow) t8Menu() []MenuItem { + return []MenuItem{ + {Name: "Diagnostics", Children: []MenuItem{ + {Name: "DTC Reader", Func: mw.openDTCReader}, + {Name: "Edit Parameters", Func: mw.openEditParameters}, + {Name: "Firmware info edit", Func: mw.openFirmwareInfoEdit}, + }}, + {Name: "Airmass", Children: []MenuItem{ + {Name: "Max airmass map (manual)", Data: "BstKnkCal.MaxAirmass"}, + {Name: "Max airmass map (auto)", Data: "BstKnkCal.MaxAirmassAu"}, + {Name: "Airmass Fuelcut", Data: "FCutCal.m_AirInletLimit"}, + {Name: "AirCtrlCal.AirmassLimiter"}, + {Name: "AirCtrlCal.PRatioMaxTab"}, + }}, + {Name: "Torque", Children: []MenuItem{ + {Name: "Nominal torque map", Data: "TrqMastCal.Trq_NominalMap"}, + {Name: "Airmass torque map", Data: "TrqMastCal.m_AirTorqMap"}, + {Name: "Ambient pressure trq limiter", Data: "TrqLimCal.Trq_CompressorNoiseRedLimMAP"}, + {Name: "Trq limit in overboost", Data: "TrqLimCal.Trq_OverBoostTab"}, + {Name: "Trq limit manual 150hp", Data: "TrqLimCal.Trq_MaxEngineManTab2"}, + {Name: "Trq limit manual 175+hp", Data: "TrqLimCal.Trq_MaxEngineManTab1"}, + {Name: "Trq limit auto 150hp", Data: "TrqLimCal.Trq_MaxEngineAutTab2"}, + {Name: "Trq limit auto 175+hp", Data: "TrqLimCal.Trq_MaxEngineAutTab1"}, + {Name: "Manual gear trq limit", Data: "TrqLimCal.Trq_ManGear"}, + {Name: "RPM limiter", Data: "MaxEngSpdCal.n_EngLimTab"}, + {Name: "TrqLimCal.Trq_MaxEngineTab1"}, + {Name: "TrqLimCal.Trq_MaxEngineTab2"}, + {Name: "FFTrqCal.FFTrq_MaxEngineTab1"}, + {Name: "FFTrqCal.FFTrq_MaxEngineTab2"}, + }}, + {Name: "Injectors", Children: []MenuItem{ + {Name: "Inj. Constant", Data: "InjCorrCal.InjectorConst"}, + {Name: "Inj. dead time", Data: "InjCorrCal.BattCorrTab"}, + {Name: "Inj. dead time (Y)", Data: "InjCorrCal.BattCorrSP"}, + }}, + {Name: "Fuel", Children: []MenuItem{ + {Name: "Fuel correction map", Data: "BFuelCal.LambdaOneFacMap"}, + {Name: "Enrichment Petrol", Data: "BFuelCal.TempEnrichFacMap"}, + {Name: "Enrichment E85", Data: "FFFuelCal.TempEnrichFacMAP"}, + {Name: "Knock fuel map", Data: "KnkFuelCal.EnrichmentMap"}, + {Name: "Injection end angle map", Data: "InjAnglCal.Map"}, + {Name: "Jerk enrichment petrol", Data: "BFuelCal.m_AirJerkTab"}, + {Name: "Jerk enrichment Fuelmaster", Data: "BFuelCal.JerkEnrichFacTab"}, + {Name: "PurgeCal.ST_PurgeEnable"}, + {Name: "LambdaCal.ST_Enable"}, + {Name: "FCutCal.ST_Enable"}, + {Name: "FFFuelCal.ST_enable"}, + {Name: "FuelDynCal.ST_Enable"}, + {Name: "TCompCal.ST_Enable"}, + }}, + {Name: "Boost", Children: []MenuItem{ + {Name: "Boost regulation map", Data: "AirCtrlCal.RegMap"}, + {Name: "P factor", Data: "AirCtrlCal.Ppart_BoostMap"}, + {Name: "I factor", Data: "AirCtrlCal.Ipart_BoostMap"}, + {Name: "D factor", Data: "AirCtrlCal.Dpart_BoostMap"}, + {Name: "AirCtrlCal.ST_BoostEnable"}, + {Name: "BoostAdapCal.ST_enable"}, + {Name: "FrompAdapCal.ST_enable"}, + {Name: "AreaAdapCal.ST_enable"}, + }}, + {Name: "Ignition", Children: []MenuItem{ + {Name: "Normal ignition map", Data: "IgnAbsCal.fi_NormalMAP"}, + {Name: "High octane map", Data: "IgnAbsCal.fi_highOctanMAP"}, + {Name: "Low octane map", Data: "IgnAbsCal.fi_lowOctanMAP"}, + {Name: "MBT ignition map", Data: "IgnAbsCal.fi_IgnMBTMAP"}, + {Name: "Fuel cut ignition map", Data: "IgnAbsCal.fi_FuelCutMAP"}, + {Name: "Startup map", Data: "IgnAbsCal.fi_StartMAP"}, + {Name: "IgnAbsCal.ST_EnableOctanMaps"}, + }}, + {Name: "Pedal", Children: []MenuItem{ + {Name: "Pedal position map", Data: "TrqMastCal.X_AccPedalMAP"}, + {Name: "Torque request map", Data: "PedalMapCal.Trq_RequestMap"}, + }}, + } } diff --git a/pkg/windows/mainwindow_selects.go b/pkg/windows/mainwindow_selects.go index 46fe0e45..f2316fc3 100644 --- a/pkg/windows/mainwindow_selects.go +++ b/pkg/windows/mainwindow_selects.go @@ -32,7 +32,7 @@ func (mw *MainWindow) createSelects() { mw.app.Preferences().SetString(prefsSelectedECU, s) idx := symbol.ECUTypeFromString(s) ebus.Publish(ebus.TOPIC_ECU, float64(idx)) - mw.SetMainMenu(mw.menu.GetMenu(s)) + mw.SetMainMenu(mw.GetMenu(s)) pres := mw.app.Preferences().StringWithFallback(s+prefsSelectedPreset, s+" Dash") mw.selects.presetSelect.SetSelected(pres) }) From 9c5a1cf91aaaeb4722acdb0003c49e3b55a4080d Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 19 Jun 2026 16:05:00 +0200 Subject: [PATCH 069/102] update ecusymbol --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3c162f0f..57aa941c 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 - github.com/roffe/ecusymbol v1.2.0 + github.com/roffe/ecusymbol v1.2.1 github.com/roffe/gocan v1.4.1 go.bug.st/serial v1.6.4 golang.org/x/image v0.40.0 diff --git a/go.sum b/go.sum index 71a90574..de1065dd 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/roffe/ecusymbol v1.2.0 h1:mI5M0HG17gCgbPTbMpIR8nPJGBu4HNZp0133HcPOzYw= -github.com/roffe/ecusymbol v1.2.0/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= +github.com/roffe/ecusymbol v1.2.1 h1:qqeXLT9NX3ckTQneCg/JImQ/3QeBSX+1GtmZ3Y9e7Fw= +github.com/roffe/ecusymbol v1.2.1/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= github.com/roffe/gocan v1.4.1 h1:T9aAHzTxS7oXwiOlMIM2TAf75aMHcEaWAi27nKwEFQk= github.com/roffe/gocan v1.4.1/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= From 92f3186c76625cee865974bf94b90930f5f845a0 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 01:29:05 +0200 Subject: [PATCH 070/102] add button to decr or incr --- pkg/widgets/mapviewer/mapviewer.go | 26 +++++++++++++++++++++++- pkg/widgets/mapviewer/mapviewer_focus.go | 14 ++----------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index c7d203cd..820a2df1 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -203,9 +203,14 @@ func (mv *MapViewer) render() fyne.CanvasObject { buttons := mv.createButtons() + var stepButtons fyne.CanvasObject + if mv.cfg.Editable { + stepButtons = mv.createStepButtons() + } + mapview := container.NewBorder( mv.xAxisLabelContainer, - nil, + stepButtons, mv.yAxisLabelContainer, nil, mv.innerView, @@ -524,6 +529,25 @@ func (mv *MapViewer) setXY() error { return nil } +// stepSelected adjusts every selected cell by one ZPrecision step. sign is +1 +// (incr) or -1 (decr). Same behaviour as the +/- keyboard shortcut. +func (mv *MapViewer) stepSelected(sign float64) { + increment := sign * math.Pow(10, -float64(mv.cfg.ZPrecision)) + for _, cell := range mv.selectedCells { + mv.cfg.ZData[cell] += increment + } + mv.updateCells() + mv.Refresh() +} + +func (mv *MapViewer) createStepButtons() *fyne.Container { + decr := widget.NewButtonWithIcon("Decr", theme.ContentRemoveIcon(), func() { mv.stepSelected(-1) }) + incr := widget.NewButtonWithIcon("Incr", theme.ContentAddIcon(), func() { mv.stepSelected(1) }) + decr.Importance = widget.LowImportance + incr.Importance = widget.LowImportance + return container.NewGridWithColumns(2, decr, incr) +} + func (mv *MapViewer) createButtons() *fyne.Container { noButtons := len(mv.cfg.Buttons) if noButtons > 0 { diff --git a/pkg/widgets/mapviewer/mapviewer_focus.go b/pkg/widgets/mapviewer/mapviewer_focus.go index 690ba9c2..e9c7591d 100644 --- a/pkg/widgets/mapviewer/mapviewer_focus.go +++ b/pkg/widgets/mapviewer/mapviewer_focus.go @@ -123,19 +123,9 @@ func (mv *MapViewer) TypedKey(key *fyne.KeyEvent) { mv.updateCells() refresh = true case "+", "A": - increment := math.Pow(10, -float64(mv.cfg.ZPrecision)) - for _, cell := range mv.selectedCells { - mv.cfg.ZData[cell] += increment - } - mv.updateCells() - refresh = true + mv.stepSelected(1) case "-", "Z": - increment := math.Pow(10, -float64(mv.cfg.ZPrecision)) - for _, cell := range mv.selectedCells { - mv.cfg.ZData[cell] -= increment - } - mv.updateCells() - refresh = true + mv.stepSelected(-1) case "Up": mv.SelectedY++ if mv.SelectedY >= mv.numRows { From f00f7b639bd534e76f18db7893159ab667d60948 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 01:29:21 +0200 Subject: [PATCH 071/102] multimap --- pkg/windows/mainWindow_menu.go | 158 ++++++++++++++++++++++++++------- pkg/windows/mainmenu.go | 7 ++ pkg/windows/mainmenu_t7.go | 4 +- 3 files changed, 133 insertions(+), 36 deletions(-) diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index 00f029af..0992aa75 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -13,7 +13,9 @@ import ( "time" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" symbol "github.com/roffe/ecusymbol" "github.com/roffe/gocan" "github.com/roffe/txlogger/pkg/colors" @@ -222,6 +224,10 @@ func (mw *MainWindow) openPgmMod() { return } symZ := mw.fw.GetByName("Pgm_mod!") + if symZ == nil { + mw.Error(errors.New("Pgm_mod! symbol not found in loaded binary")) + return + } pgm := pgmmod.New() pgm.LoadFunc = func() ([]byte, error) { if mw.dlc != nil { @@ -291,19 +297,17 @@ func (mw *MainWindow) loadBinary() { var openMapLock sync.Mutex -func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) { - if mw.fw == nil { - mw.Error(fmt.Errorf("no binary loaded")) - return - } - +// newMapViewer builds a fully wired MapViewer for a single symbol (file/ECU +// load+save funcs, live X/Y crosshair subscriptions) but does not create a +// window. openMap wraps one; openMultiMap arranges several in a grid. The +// returned cancelFuncs must be called when the containing window closes. +func (mw *MainWindow) newMapViewer(typ symbol.ECUType, mapName string) (*mapviewer.MapViewer, *mapviewer.Config, symbol.Axis, []func(), error) { var axis symbol.Axis if mw.as2 != nil { axis.Z = mapName axes := mw.as2.Axes(mapName) if len(axes) == 0 { - mw.Error(fmt.Errorf("map %q not found in as2 file", mapName)) - return + return nil, nil, axis, nil, fmt.Errorf("map %q not found in as2 file", mapName) } if len(axes) == 1 { axis.Y = axes[0].SupportPoints @@ -327,16 +331,6 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) axis = symbol.GetInfo(typ, mapName) } - windowName := axis.Z - if title != "" { - windowName += " - " + title - } - - if w := mw.wm.HasWindow(windowName); w != nil { - mw.wm.Raise(w) - return - } - symX := mw.fw.GetByName(axis.X) if symX == nil { switch axis.X { @@ -360,8 +354,7 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) } if symZ == nil { - mw.Error(fmt.Errorf("failed to find symbol %s", axis.Z)) - return + return nil, nil, axis, nil, fmt.Errorf("failed to find symbol %s", axis.Z) } var xData, yData, zData []float64 @@ -381,8 +374,7 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) kyltempSteg := mw.fw.GetByName("Kyltemp_steg!") kyltempTab := mw.fw.GetByName("Kyltemp_tab!") if kyltempSteg == nil || kyltempTab == nil { - mw.Error(fmt.Errorf("missing coolant temperature symbols")) - return + return nil, nil, axis, nil, fmt.Errorf("missing coolant temperature symbols") } realTemp := LookupCoolantTemperature(val, kyltempSteg.Ints(), kyltempTab.Ints()) yData[idx] = float64(realTemp) @@ -633,40 +625,124 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) var err error mv, err = mapviewer.New(cfg) + if err != nil { + return nil, nil, axis, nil, err + } + + var cancelFuncs []func() + if axis.XFrom != "" { + cancelFuncs = append(cancelFuncs, ebus.SubscribeFunc(axis.XFrom, mv.SetX)) + } + if axis.YFrom != "" { + cancelFuncs = append(cancelFuncs, ebus.SubscribeFunc(axis.YFrom, mv.SetY)) + } + cancelFuncs = append(cancelFuncs, ebus.SubscribeFunc(ebus.TOPIC_COLORBLINDMODE, func(value float64) { + mv.SetColorBlindMode(colors.ColorBlindMode(int(value))) + })) + + return mv, cfg, axis, cancelFuncs, nil +} + +func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) { + if mw.fw == nil { + mw.Error(fmt.Errorf("no binary loaded")) + return + } + + mv, cfg, axis, cancelFuncs, err := mw.newMapViewer(typ, mapName) if err != nil { mw.Error(err) return } + windowName := axis.Z + if title != "" { + windowName += " - " + title + } + if w := mw.wm.HasWindow(windowName); w != nil { + mw.wm.Raise(w) + for _, fn := range cancelFuncs { + fn() + } + return + } + + mapWindow := multiwindow.NewInnerWindow(axis.Z+" - "+axis.ZDescription, mv) + mapWindow.Icon = theme.GridIcon() + + cfg.OnMouseDown = func() { + mw.wm.Raise(mapWindow) + } + + mapWindow.OnClose = func() { + for _, fn := range cancelFuncs { + fn() + } + } + if mw.settings.GetAutoLoad() && mw.dlc != nil { go func() { openMapLock.Lock() defer openMapLock.Unlock() p := progressmodal.New(mw.Window.Canvas(), "Loading "+axis.Z) fyne.DoAndWait(p.Show) - loadRamFunc() + cfg.LoadECUFunc() fyne.Do(p.Hide) }() } - mapWindow := multiwindow.NewInnerWindow(axis.Z+" - "+axis.ZDescription, mv) - mapWindow.Icon = theme.GridIcon() + mw.wm.Add(mapWindow) +} - cfg.OnMouseDown = func() { - mw.wm.Raise(mapWindow) +// openMultiMap opens several maps (data = symbol names joined by "|") tightly +// arranged in one window for an at-a-glance overview, e.g. boost RegMap plus +// P/I/D factors. ponytail: plain 2-column grid of MapViewers, no dedicated +// widget — add one only if these views ever need shared crosshair/selection. +func (mw *MainWindow) openMultiMap(typ symbol.ECUType, title string, data string) { + if mw.fw == nil { + mw.Error(fmt.Errorf("no binary loaded")) + return + } + if w := mw.wm.HasWindow(title); w != nil { + mw.wm.Raise(w) + return } + grid := container.NewGridWithColumns(2) + var cfgs []*mapviewer.Config + var loadFuncs []func() var cancelFuncs []func() - if axis.XFrom != "" { - cancelFuncs = append(cancelFuncs, ebus.SubscribeFunc(axis.XFrom, mv.SetX)) + + for _, name := range strings.Split(data, "|") { + mv, cfg, axis, cancels, err := mw.newMapViewer(typ, strings.TrimSpace(name)) + if err != nil { + mw.Error(err) + continue + } + cfgs = append(cfgs, cfg) + cancelFuncs = append(cancelFuncs, cancels...) + if cfg.LoadECUFunc != nil { + loadFuncs = append(loadFuncs, cfg.LoadECUFunc) + } + label := axis.Z + if axis.ZDescription != "" { + label += " - " + axis.ZDescription + } + grid.Add(container.NewBorder(widget.NewLabel(label), nil, nil, nil, mv)) } - if axis.YFrom != "" { - cancelFuncs = append(cancelFuncs, ebus.SubscribeFunc(axis.YFrom, mv.SetY)) + + if len(grid.Objects) == 0 { + return } - cancelFuncs = append(cancelFuncs, ebus.SubscribeFunc(ebus.TOPIC_COLORBLINDMODE, func(value float64) { - mv.SetColorBlindMode(colors.ColorBlindMode(int(value))) - })) + mapWindow := multiwindow.NewInnerWindow(title, grid) + mapWindow.Icon = theme.GridIcon() + + for _, cfg := range cfgs { + cfg.OnMouseDown = func() { + mw.wm.Raise(mapWindow) + } + } mapWindow.OnClose = func() { for _, fn := range cancelFuncs { @@ -674,7 +750,21 @@ func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) } } + if mw.settings.GetAutoLoad() && mw.dlc != nil { + go func() { + openMapLock.Lock() + defer openMapLock.Unlock() + p := progressmodal.New(mw.Window.Canvas(), "Loading "+title) + fyne.DoAndWait(p.Show) + for _, fn := range loadFuncs { + fn() + } + fyne.Do(p.Hide) + }() + } + mw.wm.Add(mapWindow) + mapWindow.Resize(fyne.NewSize(1000, 750)) } func LookupCoolantTemperature(axisvalue int, kyltempSteg, kyltempTab []int) int { diff --git a/pkg/windows/mainmenu.go b/pkg/windows/mainmenu.go index 472b0315..c669b2a9 100644 --- a/pkg/windows/mainmenu.go +++ b/pkg/windows/mainmenu.go @@ -1,6 +1,8 @@ package windows import ( + "strings" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/theme" symbol "github.com/roffe/ecusymbol" @@ -54,6 +56,11 @@ func (mw *MainWindow) buildItem(typ symbol.ECUType, item MenuItem) *fyne.MenuIte return itm case item.Func != nil: return fyne.NewMenuItemWithIcon(item.Name, theme.ComputerIcon(), item.Func) + case strings.Contains(item.Data, "|"): + // title + multiple symbols joined by "|": open tightly arranged together + return fyne.NewMenuItemWithIcon(item.Name, theme.GridIcon(), func() { + mw.openMultiMap(typ, item.Name, item.Data) + }) case item.Data != "": // title + symbol: open as a map return fyne.NewMenuItemWithIcon(item.Name, theme.GridIcon(), func() { diff --git a/pkg/windows/mainmenu_t7.go b/pkg/windows/mainmenu_t7.go index 2dcc4c26..476154a5 100644 --- a/pkg/windows/mainmenu_t7.go +++ b/pkg/windows/mainmenu_t7.go @@ -49,14 +49,13 @@ func (mw *MainWindow) t7Menu() []MenuItem { {Name: "TransCal.AccMulConst"}, {Name: "TransCal.Delay1"}, {Name: "TransCal.Delay2"}, - - // {Name: "TransCal."}, }}, {Name: "VE map", Data: "BFuelCal.Map"}, {Name: "Startup VE map / E85 VE map", Data: "BFuelCal.StartMap"}, {Name: "Gas VE map", Data: "BFuelCal.GasMap"}, {Name: "Enrichment factor during starting", Data: "StartCal.EnrFacTab"}, {Name: "Enrichment factor during starting E85", Data: "StartCal.EnrFacE85Tab"}, + {Name: "Enable cloosed loop regulation", Data: "LambdaCal.ST_Enable"}, }}, {Name: "Ignition", Children: []MenuItem{ {Name: "Ignition map", Data: "IgnNormCal.Map"}, @@ -68,6 +67,7 @@ func (mw *MainWindow) t7Menu() []MenuItem { {Name: "Max knock pull", Data: "KnkFuelCal.fi_MapMaxOff"}, }}, {Name: "Boost", Children: []MenuItem{ + {Name: "Boost regulation overview", Data: "BoostCal.RegMap|BoostCal.PMap|BoostCal.IMap|BoostCal.DMap"}, {Name: "Boost calibration", Data: "BoostCal.RegMap"}, {Name: "P factor", Data: "BoostCal.PMap"}, {Name: "I factor", Data: "BoostCal.IMap"}, From e4ee2378787033899000a5b60f32d6b7db88e8cb Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 01:41:56 +0200 Subject: [PATCH 072/102] toggle 3d mesh from context menu --- pkg/widgets/mapviewer/mapviewer.go | 45 +++++++++++++++--------- pkg/widgets/mapviewer/mapviewer_mouse.go | 6 +++- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index 820a2df1..635aaefc 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -71,8 +71,10 @@ type MapViewer struct { selectedX, SelectedY int - mesh *meshgrid.Meshgrid - graph *graph2d.Graph + mesh *meshgrid.Meshgrid + meshSplit *container.Split + meshModeBtn *widget.Button + graph *graph2d.Graph // Mouse mousePos fyne.Position @@ -119,6 +121,21 @@ func New(config *Config) (*MapViewer, error) { return mv, nil } +func (mv *MapViewer) toggleMesh() { + if mv.mesh == nil || mv.meshSplit == nil { + return + } + if mv.mesh.Visible() { + mv.mesh.Hide() + mv.meshModeBtn.Hide() + mv.meshSplit.SetOffset(1) + } else { + mv.mesh.Show() + mv.meshModeBtn.Show() + mv.meshSplit.SetOffset(0.2) + } +} + func (mv *MapViewer) SetColorBlindMode(mode colors.ColorBlindMode) { if mv.colorMode != mode { mv.colorMode = mode @@ -138,9 +155,8 @@ func (mv *MapViewer) CreateRenderer() fyne.WidgetRenderer { mv.createZdata() mv.createSelectionOverlay() mv.createTextValues() - // Start with cell 0,0 selected so keyboard editing works before any click. - mv.selectedCells = []int{0} - mv.drawSelectionVisual() + // Start with nothing selected; a cell is selected on first click/keypress. + mv.selectedCells = nil mv.content = mv.render() return widget.NewSimpleRenderer(mv.content) // return &mapViewerRenderer{mv: mv} @@ -284,23 +300,20 @@ func (mv *MapViewer) render() fyne.CanvasObject { if err == nil { meshModeBtn := widget.NewButtonWithIcon("", theme.GridIcon(), mv.mesh.CycleRenderMode) meshModeBtn.Importance = widget.LowImportance + mv.meshModeBtn = meshModeBtn split := container.NewVSplit( mapview, - container.NewBorder( - nil, - buttons, - nil, - nil, - container.NewStack( - mv.mesh, - container.NewVBox( - container.NewHBox(fynelayout.NewSpacer(), meshModeBtn), - ), + container.NewStack( + mv.mesh, + container.NewVBox( + container.NewHBox(fynelayout.NewSpacer(), meshModeBtn), ), ), ) split.Offset = 0.2 - return split + mv.meshSplit = split + // buttons live outside the split so they stay visible when the mesh is toggled off. + return container.NewBorder(nil, buttons, nil, nil, split) } else { log.Println("MapViewer meshview failed:", err) } diff --git a/pkg/widgets/mapviewer/mapviewer_mouse.go b/pkg/widgets/mapviewer/mapviewer_mouse.go index 3982ac46..44b21d1c 100644 --- a/pkg/widgets/mapviewer/mapviewer_mouse.go +++ b/pkg/widgets/mapviewer/mapviewer_mouse.go @@ -214,7 +214,6 @@ func (mv *MapViewer) showPopupMenu(pos fyne.Position) { }), ) if mv.cfg.Editable { - pasteMenu := fyne.NewMenuItem("Paste", nil) pasteMenu.ChildMenu = fyne.NewMenu("Paste Options", fyne.NewMenuItem("At original position", func() { @@ -232,6 +231,11 @@ func (mv *MapViewer) showPopupMenu(pos fyne.Position) { }), ) } + + if mv.mesh != nil { + menu.Items = append(menu.Items, fyne.NewMenuItem("Toggle 3D Mesh", mv.toggleMesh)) + } + popupMenu := widget.NewPopUpMenu(menu, fyne.CurrentApp().Driver().CanvasForObject(mv), ) From b991ed54e46b72e73fb34ae83104d9e0f2d2d7f3 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 01:53:26 +0200 Subject: [PATCH 073/102] minimize support --- pkg/widgets/multiwindow/innerwindow.go | 39 +++++++++++++++++- pkg/widgets/multiwindow/layout.go | 4 ++ pkg/widgets/multiwindow/multiplewindows.go | 48 +++++++++++++++++++++- 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/pkg/widgets/multiwindow/innerwindow.go b/pkg/widgets/multiwindow/innerwindow.go index d01185dc..3656a74b 100644 --- a/pkg/widgets/multiwindow/innerwindow.go +++ b/pkg/widgets/multiwindow/innerwindow.go @@ -30,6 +30,9 @@ const ( modeIcon ) +// minimizedWidth is the fixed width of a window collapsed into the bottom tray. +const minimizedWidth float32 = 200 + type resizeDirection int const ( @@ -72,11 +75,15 @@ type InnerWindow struct { content *fyne.Container maximized bool + minimized bool active bool preMaximizedSize fyne.Size preMaximizedPos fyne.Position + preMinimizedSize fyne.Size + preMinimizedPos fyne.Position + onClose func() `json:"-"` } @@ -336,6 +343,16 @@ func (i *innerWindowRenderer) Layout(size fyne.Size) { i.bar.Move(fyne.NewPos(padding, 0)) i.bar.Resize(fyne.NewSize(size.Width-doublePadd, barHeight)) + // When minimized only the title bar is shown (tray-style). + if i.win.minimized { + i.contentBG.Hide() + i.win.content.Hide() + i.setBordersVisible(false) + return + } + i.contentBG.Show() + i.win.content.Show() + // Layout main content area contentPos := fyne.NewPos(padding, barHeight) contentDimensions := fyne.NewSize(adjustedWidth, contentSize.Height-padding-barHeight) @@ -347,10 +364,27 @@ func (i *innerWindowRenderer) Layout(size fyne.Size) { // Layout corners if !i.win.DisableResize { + i.setBordersVisible(true) i.layoutCorners(size) } } +func (i *innerWindowRenderer) setBordersVisible(visible bool) { + if i.win.DisableResize { + return + } + for _, b := range []fyne.CanvasObject{ + i.topBorder, i.bottomBorder, i.leftBorder, i.rightBorder, + i.leftTopCorner, i.rightTopCorner, i.leftBottomCorner, i.rightBottomCorner, + } { + if visible { + b.Show() + } else { + b.Hide() + } + } +} + // Helper method to handle corner layout func (i *innerWindowRenderer) layoutCorners(size fyne.Size) { cornerSize := fyne.NewSize(10, 10) @@ -383,8 +417,11 @@ func (i *innerWindowRenderer) layoutCorners(size fyne.Size) { func (i *innerWindowRenderer) MinSize() fyne.Size { th := i.win.Theme() pad := th.Size(theme.SizeNamePadding) - contentMin := i.win.content.MinSize() barHeight := th.Size(theme.SizeNameWindowTitleBarHeight) + if i.win.minimized { + return fyne.NewSize(minimizedWidth, barHeight+pad) + } + contentMin := i.win.content.MinSize() innerWidth := fyne.Max(i.bar.MinSize().Width, contentMin.Width) return fyne.NewSize(innerWidth+pad*2, contentMin.Height+pad+barHeight) } diff --git a/pkg/widgets/multiwindow/layout.go b/pkg/widgets/multiwindow/layout.go index ae63c655..de7e71b3 100644 --- a/pkg/widgets/multiwindow/layout.go +++ b/pkg/widgets/multiwindow/layout.go @@ -3,12 +3,16 @@ package multiwindow import "fyne.io/fyne/v2" type multiWinLayout struct { + mw *MultipleWindows } func (m *multiWinLayout) Layout(objects []fyne.CanvasObject, _ fyne.Size) { for _, w := range objects { // update the windows so they have real size w.Resize(w.MinSize().Max(w.Size())) } + if m.mw != nil { + m.mw.layoutTray() // keep minimized windows docked to the bottom on resize + } } func (m *multiWinLayout) MinSize(_ []fyne.CanvasObject) fyne.Size { diff --git a/pkg/widgets/multiwindow/multiplewindows.go b/pkg/widgets/multiwindow/multiplewindows.go index 201d3e33..ac88d8b8 100644 --- a/pkg/widgets/multiwindow/multiplewindows.go +++ b/pkg/widgets/multiwindow/multiplewindows.go @@ -52,7 +52,7 @@ func NewMultipleWindows(wins ...*InnerWindow) *MultipleWindows { } func (m *MultipleWindows) CreateRenderer() fyne.WidgetRenderer { - m.content = container.New(&multiWinLayout{}) + m.content = container.New(&multiWinLayout{mw: m}) m.refreshChildren() return widget.NewSimpleRenderer(m.content) } @@ -191,6 +191,26 @@ func (m *MultipleWindows) raise(w *InnerWindow) { m.refreshChildren() } +// layoutTray docks every minimized window into a row along the bottom edge, +// mimicking a taskbar/system tray. +func (m *MultipleWindows) layoutTray() { + if m.content == nil { + return + } + const margin float32 = 5 + x := margin + bottom := m.content.Size().Height + for _, w := range m.windows { + if !w.minimized { + continue + } + size := w.MinSize() + w.Resize(size) + w.Move(fyne.NewPos(x, bottom-size.Height-margin)) + x += size.Width + margin + } +} + func (m *MultipleWindows) refreshChildren() { if m.content == nil { return @@ -316,7 +336,23 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { } w.OnTappedBar = func() { - //m.Raise(w) + if w.minimized { + w.OnMinimized() // single click on a tray bar restores it + } + } + + w.OnMinimized = func() { + w.minimized = !w.minimized + if w.minimized { + w.preMinimizedSize = w.Size() + w.preMinimizedPos = w.Position() + } else { + w.Resize(w.preMinimizedSize) + w.Move(w.preMinimizedPos) + m.Raise(w) + } + w.Refresh() + m.layoutTray() } w.OnMouseDown = func() { @@ -330,6 +366,10 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { } w.OnMaximized = func() { + if w.minimized { + w.OnMinimized() // restore from tray instead of maximizing + return + } if !w.maximized { w.preMaximizedSize = w.Size() w.preMaximizedPos = w.Position() @@ -414,6 +454,10 @@ func (wm *MultipleWindows) JsonLayout() ([]byte, error) { } pos := w.Position() size := w.Size() + if w.minimized { // save real geometry, not the tiny tray bar + pos = w.preMinimizedPos + size = w.preMinimizedSize + } preMaxPos := w.PreMaximizedPos() preMaxSize := w.PreMaximizedSize() From 02cb2fe6fcc0f370dce834a2af735a697cddb89e Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 01:53:34 +0200 Subject: [PATCH 074/102] cache borders --- pkg/widgets/multiwindow/innerwindow.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/widgets/multiwindow/innerwindow.go b/pkg/widgets/multiwindow/innerwindow.go index 3656a74b..559c53c4 100644 --- a/pkg/widgets/multiwindow/innerwindow.go +++ b/pkg/widgets/multiwindow/innerwindow.go @@ -241,6 +241,7 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { var leftTopCorner, rightTopCorner, leftBottomCorner, rightBottomCorner *draggableCorner var topBorder, bottomBorder, leftBorder, rightBorder *draggableBorder + var borders []fyne.CanvasObject objects := []fyne.CanvasObject{w.bg, contentBG, bar, w.content} @@ -254,8 +255,8 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { leftBottomCorner = newDraggableCorner(w, resizeDownLeft) rightBottomCorner = newDraggableCorner(w, resizeDownRight) - // objects = append(objects, leftCorner, rightCorner) - objects = append(objects, topBorder, bottomBorder, leftBorder, rightBorder, leftTopCorner, rightTopCorner, leftBottomCorner, rightBottomCorner) + borders = []fyne.CanvasObject{topBorder, bottomBorder, leftBorder, rightBorder, leftTopCorner, rightTopCorner, leftBottomCorner, rightBottomCorner} + objects = append(objects, borders...) } r := &innerWindowRenderer{ @@ -273,6 +274,7 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { rightTopCorner: rightTopCorner, leftBottomCorner: leftBottomCorner, rightBottomCorner: rightBottomCorner, + borders: borders, contentBG: contentBG} r.Layout(w.Size()) return r @@ -322,6 +324,8 @@ type innerWindowRenderer struct { leftBottomCorner fyne.CanvasObject rightBottomCorner fyne.CanvasObject + borders []fyne.CanvasObject // all border/corner handles, for show/hide + *ShadowingRenderer } @@ -370,13 +374,7 @@ func (i *innerWindowRenderer) Layout(size fyne.Size) { } func (i *innerWindowRenderer) setBordersVisible(visible bool) { - if i.win.DisableResize { - return - } - for _, b := range []fyne.CanvasObject{ - i.topBorder, i.bottomBorder, i.leftBorder, i.rightBorder, - i.leftTopCorner, i.rightTopCorner, i.leftBottomCorner, i.rightBottomCorner, - } { + for _, b := range i.borders { // empty when DisableResize if visible { b.Show() } else { From 1029260c4addbb71ba14e1754504f34ea517751e Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 01:53:43 +0200 Subject: [PATCH 075/102] fix possible crash --- pkg/widgets/multiwindow/arrange.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/widgets/multiwindow/arrange.go b/pkg/widgets/multiwindow/arrange.go index 60c74f51..9e6d517f 100644 --- a/pkg/widgets/multiwindow/arrange.go +++ b/pkg/widgets/multiwindow/arrange.go @@ -75,6 +75,9 @@ func (f *FloatingArranger) Layout(maxSize fyne.Size, confined bool, windows []*I (maxSize.Width-initOffset)/20, (maxSize.Height-initOffset)/20, )) + if maxSteps < 1 { + maxSteps = 1 // ponytail: guard i%maxSteps panic when viewport is tiny/unsized + } for i, window := range windows { step := i % maxSteps From a7dd4a466a662126189e749eba55f0e5714d0a11 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 02:25:33 +0200 Subject: [PATCH 076/102] restructure toolbar and menu some --- pkg/windows/mainWindow.go | 8 +- pkg/windows/mainWindow_menu.go | 23 ++++++ pkg/windows/mainWindow_toolbar.go | 120 ++++++++++++++---------------- 3 files changed, 79 insertions(+), 72 deletions(-) diff --git a/pkg/windows/mainWindow.go b/pkg/windows/mainWindow.go index 7d9a01e9..91ca4c2f 100644 --- a/pkg/windows/mainWindow.go +++ b/pkg/windows/mainWindow.go @@ -277,13 +277,7 @@ func (mw *MainWindow) render() { footer := container.NewBorder( nil, nil, - container.NewBorder( - nil, - nil, - nil, - mw.buttons.layoutRefreshBtn, - mw.selects.layoutSelect, - ), + nil, container.NewHBox( container.NewHBox( mw.gocanGatewayLED, diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index 0992aa75..b44b6136 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -23,6 +23,7 @@ import ( "github.com/roffe/txlogger/pkg/ecu/t8/t8file" "github.com/roffe/txlogger/pkg/update" "github.com/roffe/txlogger/pkg/widgets" + "github.com/roffe/txlogger/pkg/widgets/canflasher" "github.com/roffe/txlogger/pkg/widgets/dtcreader" "github.com/roffe/txlogger/pkg/widgets/editparameters" "github.com/roffe/txlogger/pkg/widgets/mapviewer" @@ -124,6 +125,9 @@ func (mw *MainWindow) setupMenu() { update.UpdateCheck(mw.app, mw.Window) }), ), + fyne.NewMenu("Tools", + fyne.NewMenuItemWithIcon("Matrix Builder", theme.InfoIcon(), mw.openMatrixBuilder), + ), } trailing := []*fyne.Menu{ @@ -143,6 +147,25 @@ func (mw *MainWindow) setupMenu() { ), } + if mw.previewFeatures { + leading[len(leading)-1].Items = append( + leading[len(leading)-1].Items, + fyne.NewMenuItemWithIcon("Canflasher", theme.UploadIcon(), func() { + if w := mw.wm.HasWindow("Canflasher"); w != nil { + mw.wm.Raise(w) + return + } + inner := multiwindow.NewInnerWindow("Canflasher", canflasher.New(&canflasher.Config{ + CSW: mw.settings, + })) + inner.Icon = theme.UploadIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(450, 250)) + }), + fyne.NewMenuItemWithIcon("Boost Auto-Tuner", theme.MediaFastForwardIcon(), mw.openBoostTuner), + ) + } + mw.leadingMenus = leading mw.trailingMenus = trailing } diff --git a/pkg/windows/mainWindow_toolbar.go b/pkg/windows/mainWindow_toolbar.go index 19c8df96..cf1c9513 100644 --- a/pkg/windows/mainWindow_toolbar.go +++ b/pkg/windows/mainWindow_toolbar.go @@ -6,10 +6,10 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/roffe/txlogger/pkg/widgets/boosttuner" - "github.com/roffe/txlogger/pkg/widgets/canflasher" "github.com/roffe/txlogger/pkg/widgets/matrixbuilder" "github.com/roffe/txlogger/pkg/widgets/multiwindow" ) @@ -85,83 +85,73 @@ func (mw *MainWindow) newToolbar() *fyne.Container { widget.NewSeparator(), mw.buttons.symbolListBtn, mw.buttons.logBtn, - mw.buttons.dashboardBtn, mw.buttons.livePlotBtn, - widget.NewButtonWithIcon("Matrix", theme.GridIcon(), mw.openMatrixBuilder), + mw.buttons.dashboardBtn, + mw.buttons.addGaugeBtn, widget.NewButtonWithIcon("", theme.GridIcon(), func() { mw.wm.Arrange(&multiwindow.GridArranger{}) }), - mw.buttons.addGaugeBtn, - widget.NewButtonWithIcon("", theme.ContentClearIcon(), func() { - mw.wm.CloseAll() - }), + widget.NewButtonWithIcon("", theme.ContentClearIcon(), mw.wm.CloseAll), + layout.NewSpacer(), + container.NewBorder( + nil, + nil, + nil, + mw.buttons.layoutRefreshBtn, + mw.selects.layoutSelect, + ), ) - if mw.previewFeatures { - toolbar.Add(widget.NewButtonWithIcon("", theme.UploadIcon(), func() { - if w := mw.wm.HasWindow("Canflasher"); w != nil { + //if mw.previewFeatures { + /* + toolbar.Add(widget.NewButtonWithIcon("", theme.DocumentIcon(), func() { + if w := mw.wm.HasWindow("txweb"); w != nil { mw.wm.Raise(w) return } - inner := multiwindow.NewInnerWindow("Canflasher", canflasher.New(&canflasher.Config{ - CSW: mw.settings, - })) - inner.Icon = theme.UploadIcon() + txb := txweb.New() + txb.LoadFileFunc = func(name string, data []byte) error { + switch filepath.Ext(name) { + case ".bin": + if err := mw.LoadSymbolsFromBytes(name, data); err != nil { + return err + } + return nil + case ".csv": // ".t5l", ".t7l", ".t8l", + mw.LoadLogfile(name, bytes.NewReader(data), fyne.NewPos(100, 100)) + return nil + } + return nil + } + inner := multiwindow.NewInnerWindow("txweb", txb) + inner.Icon = theme.FileApplicationIcon() mw.wm.Add(inner) - inner.Resize(fyne.NewSize(450, 250)) + inner.Resize(fyne.NewSize(700, 500)) }), ) + */ - toolbar.Add(widget.NewButtonWithIcon("Boost", theme.MediaFastForwardIcon(), mw.openBoostTuner)) - /* - toolbar.Add(widget.NewButtonWithIcon("", theme.DocumentIcon(), func() { - if w := mw.wm.HasWindow("txweb"); w != nil { - mw.wm.Raise(w) - return - } - txb := txweb.New() - txb.LoadFileFunc = func(name string, data []byte) error { - switch filepath.Ext(name) { - case ".bin": - if err := mw.LoadSymbolsFromBytes(name, data); err != nil { - return err - } - return nil - case ".csv": // ".t5l", ".t7l", ".t8l", - mw.LoadLogfile(name, bytes.NewReader(data), fyne.NewPos(100, 100)) - return nil - } - return nil - } - inner := multiwindow.NewInnerWindow("txweb", txb) - inner.Icon = theme.FileApplicationIcon() - mw.wm.Add(inner) - inner.Resize(fyne.NewSize(700, 500)) - }), + /* + widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() { + if w := mw.wm.HasWindow("Map"); w != nil { + mw.wm.Raise(w) + return + } + mapp := maps.NewMap() + cnt := container.NewBorder( + nil, + widget.NewButtonWithIcon("", theme.ContentClearIcon(), func() { + mapp.SetCenter(59.644810, 17.058252) + }), + nil, + nil, + mapp, ) - */ - - /* - widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() { - if w := mw.wm.HasWindow("Map"); w != nil { - mw.wm.Raise(w) - return - } - mapp := maps.NewMap() - cnt := container.NewBorder( - nil, - widget.NewButtonWithIcon("", theme.ContentClearIcon(), func() { - mapp.SetCenter(59.644810, 17.058252) - }), - nil, - nil, - mapp, - ) - inner := multiwindow.NewInnerWindow("Map", cnt) - inner.Icon = theme.NavigateNextIcon() - mw.wm.Add(inner) - }), - */ - } + inner := multiwindow.NewInnerWindow("Map", cnt) + inner.Icon = theme.NavigateNextIcon() + mw.wm.Add(inner) + }), + */ + //} return toolbar } From bb8619b3af3710abe3c3aead6e57cd37d4cf461c Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 02:25:43 +0200 Subject: [PATCH 077/102] fix bug when toggling 3d mesh --- pkg/widgets/meshgrid/meshgrid_render_test.go | 27 ++++++++++++++++++++ pkg/widgets/meshgrid/meshgrid_widget.go | 6 +++++ 2 files changed, 33 insertions(+) diff --git a/pkg/widgets/meshgrid/meshgrid_render_test.go b/pkg/widgets/meshgrid/meshgrid_render_test.go index 5dad007e..b4602425 100644 --- a/pkg/widgets/meshgrid/meshgrid_render_test.go +++ b/pkg/widgets/meshgrid/meshgrid_render_test.go @@ -2,6 +2,7 @@ package meshgrid import ( "image/png" + "math" "os" "testing" @@ -43,6 +44,32 @@ func axisValues(cols, rows int) (xData, yData []float64) { return xData, yData } +// TestToggleZoomStable guards the regression where toggling the mesh off (the +// split pane collapses to zero height) and back on made it creep more zoomed-in +// each cycle: a degenerate size must not become the layout baseline. +func TestToggleZoomStable(t *testing.T) { + m := testGrid(t) + m.size = fyne.Size{} // force the first Layout to fit + m.refreshPending = true // suppress the async throttledRefresh in tests + r := &meshgridRenderer{MG: m} + + full := fyne.NewSize(800, 500) + r.Layout(full) // initial auto-fit + want := m.scale + + // One toggle cycle as Fyne actually lays it out: off collapses the pane to + // zero height, on restores it through a non-zero intermediate. The zero frame + // must not become the baseline, or the restore over-grows the scale. + for i := 0; i < 5; i++ { + r.Layout(fyne.NewSize(800, 0)) // off: pane collapses to zero + r.Layout(fyne.NewSize(800, 100)) // on: pane reappears small + r.Layout(full) // on: pane expands to full + } + if math.Abs(m.scale-want)/want > 1e-9 { + t.Fatalf("scale drifted after toggles: got %v want %v", m.scale, want) + } +} + // TestRenderRotated renders an asymmetric surface (tall corner spike) from // four yaw angles so painter's-order mistakes show up as the spike being // overdrawn by cells that are behind it. diff --git a/pkg/widgets/meshgrid/meshgrid_widget.go b/pkg/widgets/meshgrid/meshgrid_widget.go index 9c330c0a..c56d3129 100644 --- a/pkg/widgets/meshgrid/meshgrid_widget.go +++ b/pkg/widgets/meshgrid/meshgrid_widget.go @@ -674,6 +674,12 @@ func (m *meshgridRenderer) Layout(size fyne.Size) { if size == m.MG.size { return } + // A collapsed split pane (toggling the mesh off) lays us out at zero height. + // Letting that become the baseline breaks adaptZoom's reversible scaling and + // the mesh creeps more zoomed-in on every toggle, so keep the last good size. + if size.Width <= 0 || size.Height <= 0 { + return + } oldSize := m.MG.size m.MG.size = size // Auto-fit on the first real size and scale with the window thereafter, From 0ff8cbc92d26ac06925b34a760b57f580a877e38 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 02:44:50 +0200 Subject: [PATCH 078/102] symbolcompare --- pkg/widgets/symbolcompare/symbolcompare.go | 78 ++++++++ pkg/windows/mainWindow_compare.go | 217 +++++++++++++++++++++ pkg/windows/mainWindow_menu.go | 1 + 3 files changed, 296 insertions(+) create mode 100644 pkg/widgets/symbolcompare/symbolcompare.go create mode 100644 pkg/windows/mainWindow_compare.go diff --git a/pkg/widgets/symbolcompare/symbolcompare.go b/pkg/widgets/symbolcompare/symbolcompare.go new file mode 100644 index 00000000..9a5d310a --- /dev/null +++ b/pkg/widgets/symbolcompare/symbolcompare.go @@ -0,0 +1,78 @@ +package symbolcompare + +import ( + "fmt" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" +) + +// Config drives the compare list. Diffs is the (already sorted) list of symbol +// names that differ between the two binaries. The callbacks open the actual +// map views, which live in the windows package so they can reuse MapViewer. +type Config struct { + Diffs []string + OnShowDiff func(name string) + OnShowSideBySide func(name string) +} + +// New returns a list of differing symbols. Double-click a row to compare it side +// by side; right-click for a context menu to show the differences map. +func New(cfg *Config) fyne.CanvasObject { + list := widget.NewList( + func() int { return len(cfg.Diffs) }, + func() fyne.CanvasObject { return newCompareRow(cfg) }, + func(i widget.ListItemID, o fyne.CanvasObject) { + o.(*compareRow).setName(cfg.Diffs[i]) + }, + ) + + header := widget.NewLabel(fmt.Sprintf("%d symbols differ", len(cfg.Diffs))) + return container.NewBorder(header, nil, nil, nil, list) +} + +// compareRow is a list row that reacts to double-tap and right-click. It does +// not implement Tapped, so single taps still propagate to the List for +// selection highlighting. +type compareRow struct { + widget.BaseWidget + label *widget.Label + name string + cfg *Config +} + +func newCompareRow(cfg *Config) *compareRow { + r := &compareRow{label: widget.NewLabel(""), cfg: cfg} + r.ExtendBaseWidget(r) + return r +} + +func (r *compareRow) setName(name string) { + r.name = name + r.label.SetText(name) +} + +func (r *compareRow) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(r.label) +} + +func (r *compareRow) DoubleTapped(_ *fyne.PointEvent) { + if r.name != "" && r.cfg.OnShowSideBySide != nil { + r.cfg.OnShowSideBySide(r.name) + } +} + +func (r *compareRow) TappedSecondary(e *fyne.PointEvent) { + if r.name == "" || r.cfg.OnShowDiff == nil { + return + } + c := fyne.CurrentApp().Driver().CanvasForObject(r) + if c == nil { + return + } + menu := fyne.NewMenu("", fyne.NewMenuItem("Show differences map", func() { + r.cfg.OnShowDiff(r.name) + })) + widget.NewPopUpMenu(menu, c).ShowAtPosition(e.AbsolutePosition) +} diff --git a/pkg/windows/mainWindow_compare.go b/pkg/windows/mainWindow_compare.go new file mode 100644 index 00000000..331ee443 --- /dev/null +++ b/pkg/windows/mainWindow_compare.go @@ -0,0 +1,217 @@ +package windows + +import ( + "bytes" + "errors" + "fmt" + "io" + "path/filepath" + "sort" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/theme" + symbol "github.com/roffe/ecusymbol" + "github.com/roffe/txlogger/pkg/widgets" + "github.com/roffe/txlogger/pkg/widgets/mapviewer" + "github.com/roffe/txlogger/pkg/widgets/multiwindow" + "github.com/roffe/txlogger/pkg/widgets/symbolcompare" +) + +// openSymbolCompare lets the user pick a second binary and lists every symbol +// whose data differs from the currently loaded one. +func (mw *MainWindow) openSymbolCompare() { + if mw.fw == nil { + mw.Error(fmt.Errorf("no binary loaded")) + return + } + if mw.dlc != nil { + mw.Error(errors.New("stop logging before comparing binaries")) + return + } + widgets.SelectFile(func(r fyne.URIReadCloser) { + defer r.Close() + data, err := io.ReadAll(r) + if err != nil { + mw.Error(err) + return + } + filename := filepath.Base(r.URI().Path()) + otherEcu, other, err := symbol.Load(filename, data, mw.Log) + if err != nil { + mw.Error(fmt.Errorf("failed to load %s: %w", filename, err)) + return + } + typ := symbol.ECUTypeFromString(mw.selects.ecuSelect.Selected) + if otherEcu != typ { + mw.Error(fmt.Errorf("ECU type mismatch: current is %s, %s is %s", typ, filename, otherEcu)) + return + } + mw.showSymbolCompare(typ, filename, other) + }, "Binary file", "bin") +} + +func (mw *MainWindow) showSymbolCompare(typ symbol.ECUType, otherName string, other symbol.SymbolCollection) { + var diffs []string + for _, s := range mw.fw.Symbols() { + o := other.GetByName(s.Name) + if o == nil { + continue // ponytail: only symbols present in both; added/removed skipped + } + if !bytes.Equal(s.Bytes(), o.Bytes()) { + diffs = append(diffs, s.Name) + } + } + sort.Strings(diffs) + + if len(diffs) == 0 { + dialog.ShowInformation("No differences", "The two binaries have identical symbol data", mw) + return + } + + cmp := symbolcompare.New(&symbolcompare.Config{ + Diffs: diffs, + OnShowDiff: func(name string) { mw.openCompareDiff(typ, otherName, other, name) }, + OnShowSideBySide: func(name string) { mw.openCompareTabs(typ, otherName, other, name) }, + }) + inner := multiwindow.NewInnerWindow("Compare with "+otherName, cmp) + inner.Icon = theme.SearchReplaceIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(400, 600)) +} + +// openCompareTabs shows the current and other binary's version of a map in two +// tabs. ponytail: native AppTabs, no custom multi-map wrapper widget. +func (mw *MainWindow) openCompareTabs(typ symbol.ECUType, otherName string, other symbol.SymbolCollection, mapName string) { + winName := mapName + " - compare" + if w := mw.wm.HasWindow(winName); w != nil { + mw.wm.Raise(w) + return + } + cur, err := mw.compareMapViewer(mw.fw, typ, mapName) + if err != nil { + mw.Error(err) + return + } + oth, err := mw.compareMapViewer(other, typ, mapName) + if err != nil { + mw.Error(err) + return + } + tabs := container.NewAppTabs( + container.NewTabItem("Current", cur), + container.NewTabItem(otherName, oth), + ) + inner := multiwindow.NewInnerWindow(winName, tabs) + inner.Icon = theme.GridIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(900, 700)) +} + +// openCompareDiff shows a single read-only map of (current - other) per cell. +func (mw *MainWindow) openCompareDiff(typ symbol.ECUType, otherName string, other symbol.SymbolCollection, mapName string) { + winName := mapName + " - diff" + if w := mw.wm.HasWindow(winName); w != nil { + mw.wm.Raise(w) + return + } + axis, xData, yData, curZ, xPrec, yPrec, zPrec, err := compareMapData(mw.fw, typ, mapName) + if err != nil { + mw.Error(err) + return + } + _, _, _, othZ, _, _, _, err := compareMapData(other, typ, mapName) + if err != nil { + mw.Error(err) + return + } + if len(curZ) != len(othZ) { + mw.Error(fmt.Errorf("%s has different dimensions in the two binaries (%d vs %d)", mapName, len(curZ), len(othZ))) + return + } + diff := make([]float64, len(curZ)) + for i := range curZ { + diff[i] = curZ[i] - othZ[i] + } + mv, err := mw.readonlyMapViewer(mapName, "Δ "+axis.ZDescription, axis, xData, yData, diff, xPrec, yPrec, zPrec) + if err != nil { + mw.Error(err) + return + } + inner := multiwindow.NewInnerWindow(winName+" (current - "+otherName+")", mv) + inner.Icon = theme.GridIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(800, 600)) +} + +// compareMapViewer builds a read-only MapViewer for mapName from an arbitrary +// collection. Unlike newMapViewer it has no file/ECU load+save wiring. +func (mw *MainWindow) compareMapViewer(coll symbol.SymbolCollection, typ symbol.ECUType, mapName string) (*mapviewer.MapViewer, error) { + axis, x, y, z, xp, yp, zp, err := compareMapData(coll, typ, mapName) + if err != nil { + return nil, err + } + return mw.readonlyMapViewer(mapName, axis.ZDescription, axis, x, y, z, xp, yp, zp) +} + +func (mw *MainWindow) readonlyMapViewer(name, zLabel string, axis symbol.Axis, xData, yData, zData []float64, xPrec, yPrec, zPrec int) (*mapviewer.MapViewer, error) { + return mapviewer.New(&mapviewer.Config{ + Name: name, + XData: xData, + YData: yData, + ZData: zData, + XPrecision: xPrec, + YPrecision: yPrec, + ZPrecision: zPrec, + XLabel: axis.XDescription, + YLabel: axis.YDescription, + ZLabel: zLabel, + MeshView: mw.settings.GetMeshView(), + MeshRenderer: mw.settings.GetMeshRenderer(), + Editable: false, + ColorblindMode: mw.settings.GetColorBlindMode(), + }) +} + +// compareMapData resolves a map's axes + data from one collection. A trimmed +// version of newMapViewer's resolution: no as2, no T5 coolant special-case. +func compareMapData(coll symbol.SymbolCollection, typ symbol.ECUType, mapName string) (axis symbol.Axis, xData, yData, zData []float64, xPrec, yPrec, zPrec int, err error) { + axis = symbol.GetInfo(typ, mapName) + + symX := coll.GetByName(axis.X) + if symX == nil && axis.X == "BstKnkCal.fi_offsetXSP" { + symX = coll.GetByName("BstKnkCal.OffsetXSP") + } + symY := coll.GetByName(axis.Y) + symZ := coll.GetByName(axis.Z) + if symZ == nil { + err = fmt.Errorf("symbol %s not found", axis.Z) + return + } + + zData = symZ.Float64s() + zPrec = symbol.GetPrecision(symZ.Correctionfactor) + + if symX != nil { + xData = symX.Float64s() + xPrec = symbol.GetPrecision(symX.Correctionfactor) + } else { + xData = []float64{0} + } + + switch { + case symY != nil: + yData = symY.Float64s() + yPrec = symbol.GetPrecision(symY.Correctionfactor) + case len(xData) <= 1 && len(zData) > 1: + // 1xN column with no Y axis: index it so the viewer can lay it out + yData = make([]float64, len(zData)) + for i := range yData { + yData[i] = float64(i) + } + default: + yData = []float64{0} + } + return +} diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index b44b6136..4d741af1 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -126,6 +126,7 @@ func (mw *MainWindow) setupMenu() { }), ), fyne.NewMenu("Tools", + fyne.NewMenuItemWithIcon("Compare symbols with other binary", theme.SearchReplaceIcon(), mw.openSymbolCompare), fyne.NewMenuItemWithIcon("Matrix Builder", theme.InfoIcon(), mw.openMatrixBuilder), ), } From e44b0d309a9b707c68e85e882749c855e6820eee Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 13:16:47 +0200 Subject: [PATCH 079/102] symbolcompare tweaking --- pkg/assets/WHATSNEW.md | 3 +- pkg/widgets/symbolcompare/symbolcompare.go | 27 +++-------- pkg/windows/mainWindow_compare.go | 55 +++++++--------------- 3 files changed, 24 insertions(+), 61 deletions(-) diff --git a/pkg/assets/WHATSNEW.md b/pkg/assets/WHATSNEW.md index c71a2f1e..f31f4aaf 100644 --- a/pkg/assets/WHATSNEW.md +++ b/pkg/assets/WHATSNEW.md @@ -1,4 +1,5 @@ # 2.1.10 +- Added "Compare symbols with other binary" under the Tools menu. Pick a second binary of the same ECU type and get a list of every symbol whose data differs from the currently loaded one. Double-click a row to open that map in three tabs: Current, the other binary, and a Diff tab showing the per-cell difference (current - other). Logging must be stopped while comparing - Cut a new logfile from a selection in the logplayer: scrub to a spot and press "In" (or the `i` key) to mark the start, scrub again and press "Out" (or `o`) to mark the end, then press the save button to write just that range to a new log next to your other logs. The clip keeps the same format as the source log (csv/bpl/t5l/t7l/t8l). Leaving the In or Out point unset selects from the start or to the end of the log - Live tracking marker in the 3d mesh viewer showing where the ECU is reading from, mirroring the crosshair in the map above - Fixed the 3d mesh showing one cell less than the table in each direction; values are now cell-centered so an 18x16 map renders 18x16 cells @@ -15,7 +16,7 @@ - WBL reconnect COM port while logging. If the COM port dies for a reason during logging it will try to re-connect - Performance improvements in many widget to allow slower computers to run txlogger better - Improved camera handling in the 3d mesh viewer - now behaves like the t5/7/8 suites -- Added 2d graph for viewing flat maps +- Added 2D graph for viewing flat maps - Rewrote logplayer plotter to use about 50% less CPU on zoomed out views - Big refactor of the log writing logic to be simpler to maintain and be more performant - Improved cell selection in mapviewer diff --git a/pkg/widgets/symbolcompare/symbolcompare.go b/pkg/widgets/symbolcompare/symbolcompare.go index 9a5d310a..a71680cc 100644 --- a/pkg/widgets/symbolcompare/symbolcompare.go +++ b/pkg/widgets/symbolcompare/symbolcompare.go @@ -9,16 +9,15 @@ import ( ) // Config drives the compare list. Diffs is the (already sorted) list of symbol -// names that differ between the two binaries. The callbacks open the actual -// map views, which live in the windows package so they can reuse MapViewer. +// names that differ between the two binaries. OnShowSideBySide opens the actual +// map view, which lives in the windows package so it can reuse MapViewer. type Config struct { Diffs []string - OnShowDiff func(name string) OnShowSideBySide func(name string) } // New returns a list of differing symbols. Double-click a row to compare it side -// by side; right-click for a context menu to show the differences map. +// by side. func New(cfg *Config) fyne.CanvasObject { list := widget.NewList( func() int { return len(cfg.Diffs) }, @@ -32,9 +31,9 @@ func New(cfg *Config) fyne.CanvasObject { return container.NewBorder(header, nil, nil, nil, list) } -// compareRow is a list row that reacts to double-tap and right-click. It does -// not implement Tapped, so single taps still propagate to the List for -// selection highlighting. +// compareRow is a list row that reacts to double-tap. It does not implement +// Tapped, so single taps still propagate to the List for selection +// highlighting. type compareRow struct { widget.BaseWidget label *widget.Label @@ -62,17 +61,3 @@ func (r *compareRow) DoubleTapped(_ *fyne.PointEvent) { r.cfg.OnShowSideBySide(r.name) } } - -func (r *compareRow) TappedSecondary(e *fyne.PointEvent) { - if r.name == "" || r.cfg.OnShowDiff == nil { - return - } - c := fyne.CurrentApp().Driver().CanvasForObject(r) - if c == nil { - return - } - menu := fyne.NewMenu("", fyne.NewMenuItem("Show differences map", func() { - r.cfg.OnShowDiff(r.name) - })) - widget.NewPopUpMenu(menu, c).ShowAtPosition(e.AbsolutePosition) -} diff --git a/pkg/windows/mainWindow_compare.go b/pkg/windows/mainWindow_compare.go index 331ee443..259dbcb7 100644 --- a/pkg/windows/mainWindow_compare.go +++ b/pkg/windows/mainWindow_compare.go @@ -72,7 +72,6 @@ func (mw *MainWindow) showSymbolCompare(typ symbol.ECUType, otherName string, ot cmp := symbolcompare.New(&symbolcompare.Config{ Diffs: diffs, - OnShowDiff: func(name string) { mw.openCompareDiff(typ, otherName, other, name) }, OnShowSideBySide: func(name string) { mw.openCompareTabs(typ, otherName, other, name) }, }) inner := multiwindow.NewInnerWindow("Compare with "+otherName, cmp) @@ -81,78 +80,56 @@ func (mw *MainWindow) showSymbolCompare(typ symbol.ECUType, otherName string, ot inner.Resize(fyne.NewSize(400, 600)) } -// openCompareTabs shows the current and other binary's version of a map in two -// tabs. ponytail: native AppTabs, no custom multi-map wrapper widget. +// openCompareTabs shows the current and other binary's version of a map plus a +// per-cell diff in three tabs. ponytail: native AppTabs, no custom wrapper. func (mw *MainWindow) openCompareTabs(typ symbol.ECUType, otherName string, other symbol.SymbolCollection, mapName string) { winName := mapName + " - compare" if w := mw.wm.HasWindow(winName); w != nil { mw.wm.Raise(w) return } - cur, err := mw.compareMapViewer(mw.fw, typ, mapName) + axis, xData, yData, curZ, xPrec, yPrec, zPrec, err := compareMapData(mw.fw, typ, mapName) if err != nil { mw.Error(err) return } - oth, err := mw.compareMapViewer(other, typ, mapName) + _, _, _, othZ, _, _, _, err := compareMapData(other, typ, mapName) if err != nil { mw.Error(err) return } - tabs := container.NewAppTabs( - container.NewTabItem("Current", cur), - container.NewTabItem(otherName, oth), - ) - inner := multiwindow.NewInnerWindow(winName, tabs) - inner.Icon = theme.GridIcon() - mw.wm.Add(inner) - inner.Resize(fyne.NewSize(900, 700)) -} - -// openCompareDiff shows a single read-only map of (current - other) per cell. -func (mw *MainWindow) openCompareDiff(typ symbol.ECUType, otherName string, other symbol.SymbolCollection, mapName string) { - winName := mapName + " - diff" - if w := mw.wm.HasWindow(winName); w != nil { - mw.wm.Raise(w) + if len(curZ) != len(othZ) { + mw.Error(fmt.Errorf("%s has different dimensions in the two binaries (%d vs %d)", mapName, len(curZ), len(othZ))) return } - axis, xData, yData, curZ, xPrec, yPrec, zPrec, err := compareMapData(mw.fw, typ, mapName) + cur, err := mw.readonlyMapViewer(mapName, axis.ZDescription, axis, xData, yData, curZ, xPrec, yPrec, zPrec) if err != nil { mw.Error(err) return } - _, _, _, othZ, _, _, _, err := compareMapData(other, typ, mapName) + oth, err := mw.readonlyMapViewer(mapName, axis.ZDescription, axis, xData, yData, othZ, xPrec, yPrec, zPrec) if err != nil { mw.Error(err) return } - if len(curZ) != len(othZ) { - mw.Error(fmt.Errorf("%s has different dimensions in the two binaries (%d vs %d)", mapName, len(curZ), len(othZ))) - return - } diff := make([]float64, len(curZ)) for i := range curZ { diff[i] = curZ[i] - othZ[i] } - mv, err := mw.readonlyMapViewer(mapName, "Δ "+axis.ZDescription, axis, xData, yData, diff, xPrec, yPrec, zPrec) + diffMv, err := mw.readonlyMapViewer(mapName, "Δ "+axis.ZDescription, axis, xData, yData, diff, xPrec, yPrec, zPrec) if err != nil { mw.Error(err) return } - inner := multiwindow.NewInnerWindow(winName+" (current - "+otherName+")", mv) + tabs := container.NewAppTabs( + container.NewTabItem("Diff", diffMv), + container.NewTabItem("Current", cur), + container.NewTabItem(otherName, oth), + ) + inner := multiwindow.NewInnerWindow(winName, tabs) inner.Icon = theme.GridIcon() mw.wm.Add(inner) - inner.Resize(fyne.NewSize(800, 600)) -} - -// compareMapViewer builds a read-only MapViewer for mapName from an arbitrary -// collection. Unlike newMapViewer it has no file/ECU load+save wiring. -func (mw *MainWindow) compareMapViewer(coll symbol.SymbolCollection, typ symbol.ECUType, mapName string) (*mapviewer.MapViewer, error) { - axis, x, y, z, xp, yp, zp, err := compareMapData(coll, typ, mapName) - if err != nil { - return nil, err - } - return mw.readonlyMapViewer(mapName, axis.ZDescription, axis, x, y, z, xp, yp, zp) + inner.Resize(fyne.NewSize(900, 700)) } func (mw *MainWindow) readonlyMapViewer(name, zLabel string, axis symbol.Axis, xData, yData, zData []float64, xPrec, yPrec, zPrec int) (*mapviewer.MapViewer, error) { From 308bc44e22ab07b6b63d479e9922eed8db7c5e42 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 19:55:50 +0200 Subject: [PATCH 080/102] add region axis to display open and closed region border --- pkg/widgets/mapviewer/mapviewer.go | 107 ++++++++++++++++++++++-- pkg/widgets/mapviewer/mapviewer_opts.go | 9 ++ pkg/windows/closedloopregion_test.go | 33 ++++++++ pkg/windows/mainWindow_menu.go | 91 ++++++++++++++++---- pkg/windows/mainmenu.go | 8 +- pkg/windows/mainmenu_t7.go | 14 +++- pkg/windows/mainwindow_layout.go | 2 +- 7 files changed, 236 insertions(+), 28 deletions(-) create mode 100644 pkg/windows/closedloopregion_test.go diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index 635aaefc..7298b2ec 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -63,6 +63,7 @@ type MapViewer struct { valueRects *fyne.Container valueTexts *fyne.Container selectionOverlay *fyne.Container + regionOverlay *fyne.Container crosshair *canvas.Rectangle @@ -154,6 +155,7 @@ func (mv *MapViewer) CreateRenderer() fyne.WidgetRenderer { mv.createXAxis() mv.createZdata() mv.createSelectionOverlay() + mv.createRegionOverlay() mv.createTextValues() // Start with nothing selected; a cell is selected on first click/keypress. mv.selectedCells = nil @@ -208,14 +210,17 @@ func (mv *MapViewer) render() fyne.CanvasObject { mv.crosshair.Resize(fyne.NewSize(34, 14)) mv.crosshair.Hide() - mv.innerView = container.NewStack( - mv.valueRects, - mv.selectionOverlay, + layers := []fyne.CanvasObject{mv.valueRects, mv.selectionOverlay} + if mv.regionOverlay != nil { + layers = append(layers, mv.regionOverlay) + } + layers = append(layers, container.New(&movingRectsLayout{mv: mv}, mv.crosshair, ), mv.valueTexts, ) + mv.innerView = container.NewStack(layers...) buttons := mv.createButtons() @@ -402,7 +407,8 @@ func (mv *MapViewer) Refresh() { func (mv *MapViewer) createYAxis() { mv.yAxisTexts = make([]*canvas.Text, 0, mv.numRows) objs := make([]fyne.CanvasObject, 0, mv.numRows) - for i := mv.numRows - 1; i >= 0; i-- { + // ponytail: single value view has only a "0" axis label, skip it + for i := mv.numRows - 1; i >= 0 && mv.numData > 1; i-- { text := &canvas.Text{ Alignment: fyne.TextAlignCenter, Text: strconv.FormatFloat(mv.cfg.YData[i], 'f', mv.cfg.YPrecision, 64), @@ -417,7 +423,8 @@ func (mv *MapViewer) createYAxis() { func (mv *MapViewer) createXAxis() { mv.xAxisTexts = make([]*canvas.Text, 0, mv.numColumns) objs := make([]fyne.CanvasObject, 0, mv.numColumns) - for i := 0; i < mv.numColumns; i++ { + // ponytail: single value view has only a "0" axis label, skip it + for i := 0; i < mv.numColumns && mv.numData > 1; i++ { text := &canvas.Text{ Alignment: fyne.TextAlignCenter, Text: strconv.FormatFloat(mv.cfg.XData[i], 'f', mv.cfg.XPrecision, 64), @@ -481,6 +488,96 @@ func (mv *MapViewer) createSelectionOverlay() { mv.selectionOverlay = container.New(layout.NewGrid(mv.numColumns, mv.numRows, 1.32), objs...) } +// createRegionOverlay builds a thin line layer that traces the boundary between +// the cells flagged in cfg.RegionBorder and the rest — e.g. the closed-loop / +// open-loop fuel transition — as a staircase that cuts through the map instead +// of boxing each cell. Leaves regionOverlay nil when there is no region or the +// region has no internal boundary (all cells in or all out). +func (mv *MapViewer) createRegionOverlay() { + if len(mv.cfg.RegionBorder) != mv.numData || mv.numColumns <= 1 || mv.numRows <= 1 { + return + } + edges := mv.regionEdges() + if len(edges) == 0 { + return + } + borderCol := mv.cfg.RegionBorderColor + if borderCol.A == 0 { + borderCol = color.RGBA{0x70, 0x80, 0x90, 0xFF} // ponytail: default slate boundary + } + lines := make([]*canvas.Line, len(edges)) + objs := make([]fyne.CanvasObject, len(edges)) + for i := range edges { + ln := canvas.NewLine(borderCol) + ln.StrokeWidth = 4 + lines[i] = ln + objs[i] = ln + } + mv.regionOverlay = container.New(®ionBorderLayout{mv: mv, edges: edges, lines: lines}, objs...) +} + +// regionEdge marks one shared cell edge on the region boundary. vertical = the +// edge to the right of cell (r,c); otherwise the edge on top of cell (r,c), +// shared with row r+1. Indices use the same row-major order as ZData. +type regionEdge struct { + r, c int + vertical bool +} + +// regionEdges collects every edge where a flagged cell touches an unflagged one. +// Only internal transitions are returned, so the map's outer border is never +// drawn — just the closed/open interface. +func (mv *MapViewer) regionEdges() []regionEdge { + rb := mv.cfg.RegionBorder + var edges []regionEdge + for r := 0; r < mv.numRows; r++ { + for c := 0; c < mv.numColumns; c++ { + in := rb[r*mv.numColumns+c] + if c+1 < mv.numColumns && in != rb[r*mv.numColumns+c+1] { + edges = append(edges, regionEdge{r: r, c: c, vertical: true}) + } + if r+1 < mv.numRows && in != rb[(r+1)*mv.numColumns+c] { + edges = append(edges, regionEdge{r: r, c: c, vertical: false}) + } + } + } + return edges +} + +// regionBorderLayout positions the boundary lines onto cell edges, recomputing +// on resize. Cell slots are size/count (matching the value grid's slot pitch and +// the crosshair layout), and row 0 sits at the bottom, so Y is flipped. +type regionBorderLayout struct { + mv *MapViewer + edges []regionEdge + lines []*canvas.Line + oldSize fyne.Size +} + +func (l *regionBorderLayout) MinSize(_ []fyne.CanvasObject) fyne.Size { return fyne.Size{} } + +func (l *regionBorderLayout) Layout(_ []fyne.CanvasObject, size fyne.Size) { + if size == l.oldSize { + return + } + l.oldSize = size + cw := size.Width / float32(l.mv.numColumns) + ch := size.Height / float32(l.mv.numRows) + for i, e := range l.edges { + ln := l.lines[i] + if e.vertical { + x := float32(e.c+1) * cw + ln.Position1 = fyne.NewPos(x, size.Height-float32(e.r+1)*ch) + ln.Position2 = fyne.NewPos(x, size.Height-float32(e.r)*ch) + } else { + y := size.Height - float32(e.r+1)*ch + ln.Position1 = fyne.NewPos(float32(e.c)*cw, y) + ln.Position2 = fyne.NewPos(float32(e.c+1)*cw, y) + } + ln.Refresh() + } +} + // clearSelectionVisual hides the highlight for every currently selected cell. // Call it before mutating mv.selectedCells, then call drawSelectionVisual after. func (mv *MapViewer) clearSelectionVisual() { diff --git a/pkg/widgets/mapviewer/mapviewer_opts.go b/pkg/widgets/mapviewer/mapviewer_opts.go index d1ea6361..7b72db6b 100644 --- a/pkg/widgets/mapviewer/mapviewer_opts.go +++ b/pkg/widgets/mapviewer/mapviewer_opts.go @@ -1,6 +1,8 @@ package mapviewer import ( + "image/color" + "fyne.io/fyne/v2" "github.com/roffe/txlogger/pkg/colors" "github.com/roffe/txlogger/pkg/widgets/meshgrid" @@ -36,6 +38,13 @@ type Config struct { ColorblindMode colors.ColorBlindMode + // RegionBorder marks cells (same flat row-major order as ZData) that should + // be drawn with a contrasting border, e.g. to outline the closed-loop fuel + // area. nil or wrong length = no border drawn. + RegionBorder []bool + // RegionBorderColor is the border colour; zero value falls back to a default. + RegionBorderColor color.RGBA + Buttons []*MapViewerButton } diff --git a/pkg/windows/closedloopregion_test.go b/pkg/windows/closedloopregion_test.go new file mode 100644 index 00000000..977d130a --- /dev/null +++ b/pkg/windows/closedloopregion_test.go @@ -0,0 +1,33 @@ +package windows + +import "testing" + +func TestLookup1D(t *testing.T) { + // MaxLoadNormTab rpm axis + values from the reference T7 binary. + rpm := []float64{700, 880, 1260, 1640, 2020, 2400, 2780, 3160, 3540, 3920, 4300, 4680, 5060, 5440, 5820, 6200} + max := []float64{650, 650, 650, 650, 650, 650, 660, 660, 660, 650, 570, 510, 450, 330, 330, 330} + + cases := []struct { + x, want float64 + }{ + {700, 650}, // first breakpoint + {6200, 330}, // last breakpoint + {300, 650}, // below range -> clamp low + {9000, 330}, // above range -> clamp high + {5060, 450}, // exact breakpoint mid-table + {4490, 540}, // halfway between 4300(570) and 4680(510) + } + + for _, c := range cases { + if got := lookup1D(rpm, max, c.x); got != c.want { + t.Errorf("lookup1D(%g) = %g, want %g", c.x, got, c.want) + } + } + + // Boundary mapping: at rpm 5060 the closed-loop limit is 450 mg/c, so airmass + // 420 is closed loop and 480 is open loop. + limit := lookup1D(rpm, max, 5060) + if !(420 <= limit) || 480 <= limit { + t.Errorf("closed-loop boundary wrong: limit=%g, want 420<=limit<480", limit) + } +} diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index 4d741af1..e9f5383c 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -101,6 +101,13 @@ func (mw *MainWindow) setupMenu() { mw.wm.Add(inner) }), openItem, + fyne.NewMenuItemWithIcon("Settings", theme.SettingsIcon(), mw.openSettings), + fyne.NewMenuItemWithIcon("What's new", theme.InfoIcon(), mw.showWhatsNew), + fyne.NewMenuItemWithIcon("Check for updates", theme.ViewRefreshIcon(), func() { + update.UpdateCheck(mw.app, mw.Window) + }), + ), + fyne.NewMenu("Tools", fyne.NewMenuItemWithIcon("Symbol Browser", theme.ListIcon(), func() { if w := mw.wm.HasWindow("Symbol Browser"); w != nil { mw.wm.Raise(w) @@ -109,23 +116,15 @@ func (mw *MainWindow) setupMenu() { getECU := func() symbol.ECUType { return symbol.ECUTypeFromString(mw.selects.ecuSelect.Selected) } - browser := symbolbrowser.New(getFW, getECU, mw.openMap, mw.Error) + openMap := func(typ symbol.ECUType, title, mapName string) { + mw.openMap(typ, title, mapName, "") + } + browser := symbolbrowser.New(getFW, getECU, openMap, mw.Error) inner := multiwindow.NewInnerWindow("Symbol Browser", browser) inner.Icon = theme.ListIcon() mw.wm.Add(inner) inner.Resize(fyne.Size{Width: 760, Height: 520}) }), - fyne.NewMenuItemWithIcon("Settings", theme.SettingsIcon(), func() { - mw.openSettings() - }), - fyne.NewMenuItemWithIcon("What's new", theme.InfoIcon(), func() { - mw.showWhatsNew() - }), - fyne.NewMenuItemWithIcon("Check for updates", theme.ViewRefreshIcon(), func() { - update.UpdateCheck(mw.app, mw.Window) - }), - ), - fyne.NewMenu("Tools", fyne.NewMenuItemWithIcon("Compare symbols with other binary", theme.SearchReplaceIcon(), mw.openSymbolCompare), fyne.NewMenuItemWithIcon("Matrix Builder", theme.InfoIcon(), mw.openMatrixBuilder), ), @@ -325,7 +324,7 @@ var openMapLock sync.Mutex // load+save funcs, live X/Y crosshair subscriptions) but does not create a // window. openMap wraps one; openMultiMap arranges several in a grid. The // returned cancelFuncs must be called when the containing window closes. -func (mw *MainWindow) newMapViewer(typ symbol.ECUType, mapName string) (*mapviewer.MapViewer, *mapviewer.Config, symbol.Axis, []func(), error) { +func (mw *MainWindow) newMapViewer(typ symbol.ECUType, mapName string, regionMap string) (*mapviewer.MapViewer, *mapviewer.Config, symbol.Axis, []func(), error) { var axis symbol.Axis if mw.as2 != nil { axis.Z = mapName @@ -574,6 +573,15 @@ func (mw *MainWindow) newMapViewer(typ symbol.ECUType, mapName string) (*mapview zPrecision = symbol.GetPrecision(symZ.Correctionfactor) } + if mapName == "TransCal.m_TriggMaxTab" { + yData = []float64{0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500} + } + + var regionBorder []bool + if regionMap != "" { + regionBorder = mw.closedLoopRegion(typ, regionMap, xData, yData) + } + cfg := &mapviewer.Config{ Name: symZ.Name, @@ -581,6 +589,8 @@ func (mw *MainWindow) newMapViewer(typ symbol.ECUType, mapName string) (*mapview YData: yData, ZData: zData, + RegionBorder: regionBorder, + XPrecision: xPrecision, YPrecision: yPrecision, ZPrecision: zPrecision, @@ -667,13 +677,13 @@ func (mw *MainWindow) newMapViewer(typ symbol.ECUType, mapName string) (*mapview return mv, cfg, axis, cancelFuncs, nil } -func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string) { +func (mw *MainWindow) openMap(typ symbol.ECUType, title string, mapName string, regionMap string) { if mw.fw == nil { mw.Error(fmt.Errorf("no binary loaded")) return } - mv, cfg, axis, cancelFuncs, err := mw.newMapViewer(typ, mapName) + mv, cfg, axis, cancelFuncs, err := mw.newMapViewer(typ, mapName, regionMap) if err != nil { mw.Error(err) return @@ -738,7 +748,7 @@ func (mw *MainWindow) openMultiMap(typ symbol.ECUType, title string, data string var cancelFuncs []func() for _, name := range strings.Split(data, "|") { - mv, cfg, axis, cancels, err := mw.newMapViewer(typ, strings.TrimSpace(name)) + mv, cfg, axis, cancels, err := mw.newMapViewer(typ, strings.TrimSpace(name), "") if err != nil { mw.Error(err) continue @@ -791,6 +801,55 @@ func (mw *MainWindow) openMultiMap(typ symbol.ECUType, title string, data string mapWindow.Resize(fyne.NewSize(1000, 750)) } +// closedLoopRegion flags the cells of a fuel map (X = airmass, Y = rpm) that lie +// in the closed-loop area: airmass <= the per-rpm max load read from a LambdaCal +// MaxLoad table (regionMap). The table is indexed by its own rpm axis, so the +// limit is linearly interpolated onto each map rpm row. Result is row-major +// (rpmIdx*len(xData)+airIdx), matching ZData order. Returns nil if anything is +// missing so the caller simply skips the outline. +func (mw *MainWindow) closedLoopRegion(typ symbol.ECUType, regionMap string, xData, yData []float64) []bool { + sym := mw.fw.GetByName(regionMap) + if sym == nil { + return nil + } + rpmSym := mw.fw.GetByName(symbol.GetInfo(typ, regionMap).Y) + if rpmSym == nil { + return nil + } + limitRpm := rpmSym.Float64s() + limit := sym.Float64s() + if len(limitRpm) == 0 || len(limit) != len(limitRpm) { + return nil + } + region := make([]bool, len(xData)*len(yData)) + for r, rpm := range yData { + maxAir := lookup1D(limitRpm, limit, rpm) + for c, air := range xData { + region[r*len(xData)+c] = air <= maxAir + } + } + return region +} + +// lookup1D does a clamped linear interpolation of ys over the (ascending) xs +// breakpoints. xs and ys must be the same non-zero length. +func lookup1D(xs, ys []float64, x float64) float64 { + n := len(xs) + if x <= xs[0] { + return ys[0] + } + if x >= xs[n-1] { + return ys[n-1] + } + for i := 1; i < n; i++ { + if x <= xs[i] { + t := (x - xs[i-1]) / (xs[i] - xs[i-1]) + return ys[i-1] + t*(ys[i]-ys[i-1]) + } + } + return ys[n-1] +} + func LookupCoolantTemperature(axisvalue int, kyltempSteg, kyltempTab []int) int { index := -1 retval := -1 diff --git a/pkg/windows/mainmenu.go b/pkg/windows/mainmenu.go index c669b2a9..394edab8 100644 --- a/pkg/windows/mainmenu.go +++ b/pkg/windows/mainmenu.go @@ -13,6 +13,10 @@ type MenuItem struct { Children []MenuItem Func func() Data string + // Region is an optional LambdaCal MaxLoad table whose per-rpm airmass limit + // outlines the closed-loop region on the opened map. Only used for single-map + // items (Data without "|"). + Region string } func (mw *MainWindow) GetMenu(name string) *fyne.MainMenu { @@ -64,12 +68,12 @@ func (mw *MainWindow) buildItem(typ symbol.ECUType, item MenuItem) *fyne.MenuIte case item.Data != "": // title + symbol: open as a map return fyne.NewMenuItemWithIcon(item.Name, theme.GridIcon(), func() { - mw.openMap(typ, item.Name, item.Data) + mw.openMap(typ, item.Name, item.Data, item.Region) }) default: // Name is itself a symbol return fyne.NewMenuItemWithIcon(item.Name, theme.GridIcon(), func() { - mw.openMap(typ, "", item.Name) + mw.openMap(typ, "", item.Name, "") }) } } diff --git a/pkg/windows/mainmenu_t7.go b/pkg/windows/mainmenu_t7.go index 476154a5..953c40e6 100644 --- a/pkg/windows/mainmenu_t7.go +++ b/pkg/windows/mainmenu_t7.go @@ -33,6 +33,10 @@ func (mw *MainWindow) t7Menu() []MenuItem { {Name: "Nom. torque map (X)", Data: "TorqueCal.m_AirXSP"}, }}, {Name: "Fuel", Children: []MenuItem{ + {Name: "Closed Loop", Children: []MenuItem{ + {Name: "Max load norm", Data: "LambdaCal.MaxLoadNormTab"}, + {Name: "Max load E85", Data: "LambdaCal.MaxLoadE85Tab"}, + }}, {Name: "TransCal", Children: []MenuItem{ {Name: "TransCal.ST_Enable"}, {Name: "TransCal.ST_DecNoLim"}, @@ -49,17 +53,19 @@ func (mw *MainWindow) t7Menu() []MenuItem { {Name: "TransCal.AccMulConst"}, {Name: "TransCal.Delay1"}, {Name: "TransCal.Delay2"}, + {Name: "TransCal.m_TriggMaxTab"}, }}, - {Name: "VE map", Data: "BFuelCal.Map"}, - {Name: "Startup VE map / E85 VE map", Data: "BFuelCal.StartMap"}, + {Name: "VE map", Data: "BFuelCal.Map", Region: "LambdaCal.MaxLoadNormTab"}, + {Name: "Startup VE map / E85 VE map", Data: "BFuelCal.StartMap", Region: "LambdaCal.MaxLoadE85Tab"}, {Name: "Gas VE map", Data: "BFuelCal.GasMap"}, {Name: "Enrichment factor during starting", Data: "StartCal.EnrFacTab"}, {Name: "Enrichment factor during starting E85", Data: "StartCal.EnrFacE85Tab"}, {Name: "Enable cloosed loop regulation", Data: "LambdaCal.ST_Enable"}, + {Name: "Common for tuning", Data: "AdpFuelCal.T_AdaptLim|E85Cal.ST_Enable|FCutCal.ST_Enable|LambdaCal.ST_Enable|PurgeCal.ST_PurgeEnable|TorqueCal.M_BrakeLimit"}, }}, {Name: "Ignition", Children: []MenuItem{ - {Name: "Ignition map", Data: "IgnNormCal.Map"}, - {Name: "Ignition for E85", Data: "IgnE85Cal.fi_AbsMap"}, + {Name: "Ignition map", Data: "IgnNormCal.Map", Region: "LambdaCal.MaxLoadNormTab"}, + {Name: "Ignition for E85", Data: "IgnE85Cal.fi_AbsMap", Region: "LambdaCal.MaxLoadE85Tab"}, {Name: "Ignition for Gas", Data: "IgnNormCal.GasMap"}, {Name: "Ignition Idle", Data: "IgnIdleCal.fi_IdleMap"}, {Name: "Ignition Start", Data: "IgnStartCal.fi_StartMap"}, diff --git a/pkg/windows/mainwindow_layout.go b/pkg/windows/mainwindow_layout.go index 655ba1c0..ff43213d 100644 --- a/pkg/windows/mainwindow_layout.go +++ b/pkg/windows/mainwindow_layout.go @@ -184,7 +184,7 @@ func (mw *MainWindow) LoadLayout(name string) error { } if openMap { - mw.openMap(symbol.ECUTypeFromString(layout.ECU), h.Title, parts[0]) + mw.openMap(symbol.ECUTypeFromString(layout.ECU), h.Title, parts[0], "") } } From 8517a26bbe8ec2c797160ffc39d616b61af945f7 Mon Sep 17 00:00:00 2001 From: roffe Date: Sat, 20 Jun 2026 20:06:16 +0200 Subject: [PATCH 081/102] update deps --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 57aa941c..35095b57 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,13 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.7.5-0.20260614063241-4c0c29f7d5a7 + fyne.io/fyne/v2 v2.7.5-0.20260620165746-4a6045473bc5 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 - github.com/roffe/ecusymbol v1.2.1 + github.com/roffe/ecusymbol v1.2.3 github.com/roffe/gocan v1.4.1 go.bug.st/serial v1.6.4 golang.org/x/image v0.40.0 diff --git a/go.sum b/go.sum index de1065dd..9d329135 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -fyne.io/fyne/v2 v2.7.5-0.20260614063241-4c0c29f7d5a7 h1:2auph/jcheGuecJjdA5JahXIFFeLjglROzorkhmLxiU= -fyne.io/fyne/v2 v2.7.5-0.20260614063241-4c0c29f7d5a7/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= +fyne.io/fyne/v2 v2.7.5-0.20260620165746-4a6045473bc5 h1:E/XnR1+hWNRnHoS1A9DK/JYFmCZ7GgE8L8NoRz3StQs= +fyne.io/fyne/v2 v2.7.5-0.20260620165746-4a6045473bc5/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= @@ -136,8 +136,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/roffe/ecusymbol v1.2.1 h1:qqeXLT9NX3ckTQneCg/JImQ/3QeBSX+1GtmZ3Y9e7Fw= -github.com/roffe/ecusymbol v1.2.1/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= +github.com/roffe/ecusymbol v1.2.3 h1:/Ng+yRyIaDRp72KpBOjwS+wa4mcKp83b2fBn2rY/DuE= +github.com/roffe/ecusymbol v1.2.3/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= github.com/roffe/gocan v1.4.1 h1:T9aAHzTxS7oXwiOlMIM2TAf75aMHcEaWAi27nKwEFQk= github.com/roffe/gocan v1.4.1/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= From df1d6763b842252e5d3597f7ebc7347951578bef Mon Sep 17 00:00:00 2001 From: roffe Date: Sun, 21 Jun 2026 00:38:20 +0200 Subject: [PATCH 082/102] liveplot --- pkg/widgets/liveplotter/liveplotter.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/widgets/liveplotter/liveplotter.go b/pkg/widgets/liveplotter/liveplotter.go index 602b3e63..9bce410b 100644 --- a/pkg/widgets/liveplotter/liveplotter.go +++ b/pkg/widgets/liveplotter/liveplotter.go @@ -477,9 +477,11 @@ func (p *Widget) CreateRenderer() fyne.WidgetRenderer { p.setFollow(!p.follow) }) - p.windowSel = widget.NewSelect([]string{"30s", "60s", "120s", "300s"}, func(s string) { + p.windowSel = widget.NewSelect([]string{"15s", "30s", "60s", "120s", "300s"}, func(s string) { var d time.Duration switch s { + case "15s": + d = 15 * time.Second case "30s": d = 30 * time.Second case "60s": From b6cc041d41724a08b2c088595ceda798ba4d6528 Mon Sep 17 00:00:00 2001 From: roffe Date: Sun, 21 Jun 2026 21:40:46 +0200 Subject: [PATCH 083/102] add ctrl-shift click --- pkg/widgets/mapviewer/mapviewer.go | 5 +++++ pkg/widgets/mapviewer/mapviewer_mouse.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index 7298b2ec..58b5c730 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -595,6 +595,11 @@ func (mv *MapViewer) drawSelectionVisual() { mv.selectionRects[cell].Show() } } + // Show() only flips the Hidden flag. On a freshly opened window nothing has + // dirtied the canvas yet, so the newly shown rects aren't painted until some + // unrelated event (resize, button hover) forces a repaint. Refresh the + // overlay container to repaint immediately. See handlePrimaryCtrlClick. + canvas.Refresh(mv.selectionOverlay) } func (mv *MapViewer) setXY() error { diff --git a/pkg/widgets/mapviewer/mapviewer_mouse.go b/pkg/widgets/mapviewer/mapviewer_mouse.go index 44b21d1c..ba346601 100644 --- a/pkg/widgets/mapviewer/mapviewer_mouse.go +++ b/pkg/widgets/mapviewer/mapviewer_mouse.go @@ -67,6 +67,9 @@ func (mv *MapViewer) MouseDown(event *desktop.MouseEvent) { case event.Button == desktop.MouseButtonPrimary && event.Modifier == fyne.KeyModifierShift: mv.handlePrimaryClickWithShift(event) + case event.Button == desktop.MouseButtonPrimary && event.Modifier == fyne.KeyModifierControl|fyne.KeyModifierShift: + mv.handlePrimaryCtrlShiftClick(event) + case event.Button == desktop.MouseButtonPrimary && event.Modifier == fyne.KeyModifierControl: mv.handlePrimaryCtrlClick(event) @@ -113,6 +116,25 @@ func (mv *MapViewer) handlePrimaryCtrlClick(event *desktop.MouseEvent) { canvas.Refresh(mv.zDataRects[newCell]) } +// handlePrimaryCtrlShiftClick adds the rectangular block between the anchor cell +// (selectedX/SelectedY, the last clicked cell) and the clicked corner to the +// current selection without clearing it. +func (mv *MapViewer) handlePrimaryCtrlShiftClick(event *desktop.MouseEvent) { + nx, ny := mv.calculateSelectionBounds(event.Position) + minX, maxX := min(mv.selectedX, nx), max(mv.selectedX, nx) + minY, maxY := min(mv.SelectedY, ny), max(mv.SelectedY, ny) + for y := minY; y <= maxY; y++ { + for x := minX; x <= maxX; x++ { + cell := y*mv.numColumns + x + if !slices.Contains(mv.selectedCells, cell) { + mv.selectedCells = append(mv.selectedCells, cell) + } + } + } + mv.dragCornerX, mv.dragCornerY = nx, ny + mv.drawSelectionVisual() +} + // handlePrimaryClickWithShift extends the selection from the current anchor to // the clicked cell and begins a drag. func (mv *MapViewer) handlePrimaryClickWithShift(event *desktop.MouseEvent) { From acaacc4699203ce1271b1518a942a6d1cd184a1d Mon Sep 17 00:00:00 2001 From: roffe Date: Sun, 21 Jun 2026 21:41:02 +0200 Subject: [PATCH 084/102] update t7 menus --- pkg/windows/mainmenu_t7.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/windows/mainmenu_t7.go b/pkg/windows/mainmenu_t7.go index 953c40e6..2d06e69a 100644 --- a/pkg/windows/mainmenu_t7.go +++ b/pkg/windows/mainmenu_t7.go @@ -33,6 +33,11 @@ func (mw *MainWindow) t7Menu() []MenuItem { {Name: "Nom. torque map (X)", Data: "TorqueCal.m_AirXSP"}, }}, {Name: "Fuel", Children: []MenuItem{ + {Name: "Fuel cut", Children: []MenuItem{ + {Name: "FCutCal.ST_Enable"}, + {Name: "FCutCal.FuelFactor"}, + {Name: "FCutCal.n_CombSP (Y)", Data: "FCutCal.n_CombSP"}, + }}, {Name: "Closed Loop", Children: []MenuItem{ {Name: "Max load norm", Data: "LambdaCal.MaxLoadNormTab"}, {Name: "Max load E85", Data: "LambdaCal.MaxLoadE85Tab"}, From c12c612fa8c38b4f86de991d0ec52274f5e90036 Mon Sep 17 00:00:00 2001 From: roffe Date: Mon, 22 Jun 2026 22:14:08 +0200 Subject: [PATCH 085/102] some menu stuff and experimental scaler --- go.mod | 2 +- go.sum | 4 +- pkg/widgets/rescaler/rescaler.go | 186 ++++++++++++++++++++++++++ pkg/widgets/rescaler/rescaler_test.go | 39 ++++++ pkg/windows/mainWindow_menu.go | 152 ++++++++++++++++++++- pkg/windows/mainWindow_toolbar.go | 64 --------- pkg/windows/mainmenu_t7.go | 3 +- pkg/windows/mainmenu_t8.go | 5 + 8 files changed, 383 insertions(+), 72 deletions(-) create mode 100644 pkg/widgets/rescaler/rescaler.go create mode 100644 pkg/widgets/rescaler/rescaler_test.go diff --git a/go.mod b/go.mod index 35095b57..246e7606 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.7.5-0.20260620165746-4a6045473bc5 + fyne.io/fyne/v2 v2.7.5-0.20260622115008-9d9a9461ca01 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 diff --git a/go.sum b/go.sum index 9d329135..a2e0f595 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -fyne.io/fyne/v2 v2.7.5-0.20260620165746-4a6045473bc5 h1:E/XnR1+hWNRnHoS1A9DK/JYFmCZ7GgE8L8NoRz3StQs= -fyne.io/fyne/v2 v2.7.5-0.20260620165746-4a6045473bc5/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= +fyne.io/fyne/v2 v2.7.5-0.20260622115008-9d9a9461ca01 h1:G0GqajRHUPTm6LTMvLdklI1EEqY7c6Vh6YLNWiMEUCQ= +fyne.io/fyne/v2 v2.7.5-0.20260622115008-9d9a9461ca01/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= diff --git a/pkg/widgets/rescaler/rescaler.go b/pkg/widgets/rescaler/rescaler.go new file mode 100644 index 00000000..525c1281 --- /dev/null +++ b/pkg/widgets/rescaler/rescaler.go @@ -0,0 +1,186 @@ +// Package rescaler resamples a 2D map onto new axis support points while +// preserving the underlying surface — the local reimplementation of the +// Trionic Map Scaler (gray-plant-037f86003.3.azurestaticapps.net), which did +// the same thing through a server-side API and required pasting values by hand. +// Here the values are read straight from the loaded binary instead. +package rescaler + +import ( + "fmt" + "strconv" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + + "github.com/roffe/txlogger/pkg/interpolate" + "github.com/roffe/txlogger/pkg/widgets/mapviewer" +) + +// Config describes one map to rescale. Axes and data are in engineering units +// (correction factor already applied). ZData is row-major: index = y*len(X)+x, +// matching MapViewer and interpolate.Interpolate64. +type Config struct { + Name string + XLabel, YLabel, ZLabel string + + XData, YData, ZData []float64 + + XPrecision, YPrecision, ZPrecision int + + // Apply writes the rescaled result back (e.g. to the binary symbols + disk). + // newX/newY have the same length as the originals; newZ keeps the same dims. + Apply func(newX, newY, newZ []float64) error +} + +// Rescale resamples z (defined on oldX × oldY, row-major row=Y col=X) onto the +// newX × newY grid with clamped bilinear interpolation. Points outside the old +// axis range clamp to the nearest edge — no extrapolation. newX/newY may hold +// different values than the originals but interpolate.Interpolate64 wants the +// same Z layout, so callers keep the breakpoint counts unchanged. +func Rescale(oldX, oldY, z, newX, newY []float64) []float64 { + out := make([]float64, len(newX)*len(newY)) + for yi, yv := range newY { + for xi, xv := range newX { + _, _, v, _ := interpolate.Interpolate64(oldX, oldY, z, xv, yv) + out[yi*len(newX)+xi] = v + } + } + return out +} + +// Rescaler is the editable axis + live preview widget. +type Rescaler struct { + widget.BaseWidget + cfg *Config + + xEntry, yEntry *widget.Entry + preview *fyne.Container + status *widget.Label + + newX, newY, newZ []float64 +} + +func New(cfg *Config) *Rescaler { + r := &Rescaler{ + cfg: cfg, + preview: container.NewStack(), + status: widget.NewLabel(""), + } + r.ExtendBaseWidget(r) + + r.xEntry = widget.NewEntry() + r.xEntry.SetText(floatsToText(cfg.XData, cfg.XPrecision)) + r.yEntry = widget.NewEntry() + r.yEntry.SetText(floatsToText(cfg.YData, cfg.YPrecision)) + + r.rescale() // identity preview on open + return r +} + +func (r *Rescaler) rescale() { + newX, err := parseAxis(r.xEntry.Text, len(r.cfg.XData)) + if err != nil { + r.status.SetText("X axis: " + err.Error()) + return + } + newY, err := parseAxis(r.yEntry.Text, len(r.cfg.YData)) + if err != nil { + r.status.SetText("Y axis: " + err.Error()) + return + } + + newZ := Rescale(r.cfg.XData, r.cfg.YData, r.cfg.ZData, newX, newY) + r.newX, r.newY, r.newZ = newX, newY, newZ + + mv, err := mapviewer.New(&mapviewer.Config{ + Name: r.cfg.Name, + XData: newX, + YData: newY, + ZData: newZ, + XPrecision: r.cfg.XPrecision, + YPrecision: r.cfg.YPrecision, + ZPrecision: r.cfg.ZPrecision, + XLabel: r.cfg.XLabel, + YLabel: r.cfg.YLabel, + ZLabel: r.cfg.ZLabel, + }) + if err != nil { + r.status.SetText(err.Error()) + return + } + r.preview.Objects = []fyne.CanvasObject{mv} + r.preview.Refresh() + r.status.SetText("Rescaled — review the preview, then Apply & Save") +} + +func (r *Rescaler) apply() { + if r.cfg.Apply == nil || r.newZ == nil { + return + } + dialog.ShowConfirm("Apply & Save", + fmt.Sprintf("Overwrite %s and its axes in the binary and save to disk?", r.cfg.Name), + func(ok bool) { + if !ok { + return + } + if err := r.cfg.Apply(r.newX, r.newY, r.newZ); err != nil { + r.status.SetText("Apply failed: " + err.Error()) + return + } + r.status.SetText("Applied and saved") + }, fyne.CurrentApp().Driver().AllWindows()[0]) +} + +func (r *Rescaler) CreateRenderer() fyne.WidgetRenderer { + rescaleBtn := widget.NewButtonWithIcon("Rescale", theme.ViewRefreshIcon(), r.rescale) + rescaleBtn.Importance = widget.HighImportance + applyBtn := widget.NewButtonWithIcon("Apply & Save", theme.DocumentSaveIcon(), r.apply) + + form := container.NewVBox( + widget.NewLabel(fmt.Sprintf("New X axis (%s) — %d points:", r.cfg.XLabel, len(r.cfg.XData))), + r.xEntry, + widget.NewLabel(fmt.Sprintf("New Y axis (%s) — %d points:", r.cfg.YLabel, len(r.cfg.YData))), + r.yEntry, + container.NewHBox(rescaleBtn, applyBtn), + r.status, + ) + + content := container.NewBorder(form, nil, nil, nil, r.preview) + return widget.NewSimpleRenderer(content) +} + +func floatsToText(vals []float64, prec int) string { + parts := make([]string, len(vals)) + for i, v := range vals { + parts[i] = strconv.FormatFloat(v, 'f', prec, 64) + } + return strings.Join(parts, ", ") +} + +// parseAxis parses comma/space/newline separated numbers, requiring exactly want +// strictly-ascending values (interpolate.Interpolate64 binary-searches the axis, +// and SetData rejects a changed table length). +func parseAxis(text string, want int) ([]float64, error) { + fields := strings.FieldsFunc(text, func(r rune) bool { + return r == ',' || r == ';' || r == ' ' || r == '\t' || r == '\n' || r == '\r' + }) + if len(fields) != want { + return nil, fmt.Errorf("need %d values, got %d", want, len(fields)) + } + out := make([]float64, len(fields)) + for i, f := range fields { + v, err := strconv.ParseFloat(f, 64) + if err != nil { + return nil, fmt.Errorf("%q is not a number", f) + } + if i > 0 && v <= out[i-1] { + return nil, fmt.Errorf("values must strictly ascend (%g after %g)", v, out[i-1]) + } + out[i] = v + } + return out, nil +} diff --git a/pkg/widgets/rescaler/rescaler_test.go b/pkg/widgets/rescaler/rescaler_test.go new file mode 100644 index 00000000..7e9cdc59 --- /dev/null +++ b/pkg/widgets/rescaler/rescaler_test.go @@ -0,0 +1,39 @@ +package rescaler + +import "testing" + +func eq(a, b []float64) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestRescale(t *testing.T) { + // Surface z(x,y) = x + y on a 2x2 grid, row-major row=Y col=X. + oldX := []float64{0, 10} + oldY := []float64{0, 10} + z := []float64{0, 10, 10, 20} + + // Identity: same axes -> same data. + if got := Rescale(oldX, oldY, z, oldX, oldY); !eq(got, z) { + t.Fatalf("identity rescale changed data: %v", got) + } + + // Add a midpoint X breakpoint: linear surface => exact midpoints. + newX := []float64{0, 5, 10} + want := []float64{0, 5, 10, 10, 15, 20} + if got := Rescale(oldX, oldY, z, newX, oldY); !eq(got, want) { + t.Fatalf("midpoint rescale = %v, want %v", got, want) + } + + // Outside the old range clamps to the edge (no extrapolation). + if got := Rescale(oldX, oldY, z, []float64{-5, 0}, []float64{0, 0})[0]; got != 0 { + t.Fatalf("clamp below min = %v, want 0", got) + } +} diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index e9f5383c..4a9bb9b3 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "math" + "os" "os/exec" "runtime" "strings" @@ -23,12 +24,15 @@ import ( "github.com/roffe/txlogger/pkg/ecu/t8/t8file" "github.com/roffe/txlogger/pkg/update" "github.com/roffe/txlogger/pkg/widgets" + "github.com/roffe/txlogger/pkg/widgets/boosttuner" "github.com/roffe/txlogger/pkg/widgets/canflasher" "github.com/roffe/txlogger/pkg/widgets/dtcreader" "github.com/roffe/txlogger/pkg/widgets/editparameters" "github.com/roffe/txlogger/pkg/widgets/mapviewer" + "github.com/roffe/txlogger/pkg/widgets/matrixbuilder" "github.com/roffe/txlogger/pkg/widgets/multiwindow" "github.com/roffe/txlogger/pkg/widgets/progressmodal" + "github.com/roffe/txlogger/pkg/widgets/rescaler" "github.com/roffe/txlogger/pkg/widgets/symbolbrowser" "github.com/roffe/txlogger/pkg/widgets/trionic5/pgmmod" "github.com/roffe/txlogger/pkg/widgets/trionic5/pgmstatus" @@ -127,6 +131,9 @@ func (mw *MainWindow) setupMenu() { }), fyne.NewMenuItemWithIcon("Compare symbols with other binary", theme.SearchReplaceIcon(), mw.openSymbolCompare), fyne.NewMenuItemWithIcon("Matrix Builder", theme.InfoIcon(), mw.openMatrixBuilder), + //fyne.NewMenuItemWithIcon("Rescale AccPedalMap", theme.GridIcon(), func() { + // mw.openRescaler(symbol.ECU_T8, "TrqMastCal.X_AccPedalMAP") + //}), ), } @@ -162,7 +169,7 @@ func (mw *MainWindow) setupMenu() { mw.wm.Add(inner) inner.Resize(fyne.NewSize(450, 250)) }), - fyne.NewMenuItemWithIcon("Boost Auto-Tuner", theme.MediaFastForwardIcon(), mw.openBoostTuner), + fyne.NewMenuItemWithIcon("T7 Boost Auto-Tuner", theme.MediaFastForwardIcon(), mw.openBoostTuner), ) } @@ -399,7 +406,7 @@ func (mw *MainWindow) newMapViewer(typ symbol.ECUType, mapName string, regionMap if kyltempSteg == nil || kyltempTab == nil { return nil, nil, axis, nil, fmt.Errorf("missing coolant temperature symbols") } - realTemp := LookupCoolantTemperature(val, kyltempSteg.Ints(), kyltempTab.Ints()) + realTemp := lookupCoolantTemperature(val, kyltempSteg.Ints(), kyltempTab.Ints()) yData[idx] = float64(realTemp) } } else { @@ -573,8 +580,11 @@ func (mw *MainWindow) newMapViewer(typ symbol.ECUType, mapName string, regionMap zPrecision = symbol.GetPrecision(symZ.Correctionfactor) } - if mapName == "TransCal.m_TriggMaxTab" { + switch mapName { + case "TransCal.m_TriggMaxTab": yData = []float64{0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500} + case "TransCal.FilterConstAir": + yData = []float64{899, 2499, 3499, 3500} } var regionBorder []bool @@ -850,7 +860,7 @@ func lookup1D(xs, ys []float64, x float64) float64 { return ys[n-1] } -func LookupCoolantTemperature(axisvalue int, kyltempSteg, kyltempTab []int) int { +func lookupCoolantTemperature(axisvalue int, kyltempSteg, kyltempTab []int) int { index := -1 retval := -1 smallestDiff := 256 @@ -914,3 +924,137 @@ func LookupCoolantTemperature(axisvalue int, kyltempSteg, kyltempTab []int) int return retval } + +// openBoostTuner opens (or raises) the T7 boost auto-tuner. It reads the current +// BoostCal maps from the loaded binary and writes tuned maps back through a save +// closure that takes a one-time .bak of the file before the first write. +func (mw *MainWindow) openBoostTuner() { + if mw.fw == nil { + mw.Error(fmt.Errorf("no binary loaded")) + return + } + if w := mw.wm.HasWindow("Boost Auto-Tuner"); w != nil { + mw.wm.Raise(w) + return + } + save := func(symbolName string, data []float64) error { + sym := mw.fw.GetByName(symbolName) + if sym == nil { + return fmt.Errorf("symbol %s not found", symbolName) + } + if err := sym.SetData(sym.EncodeFloat64s(data)); err != nil { + return err + } + if mw.filename != "" { + if bak := mw.filename + ".bak"; !fileExists(bak) { + if orig, err := os.ReadFile(mw.filename); err == nil { + _ = os.WriteFile(bak, orig, 0o644) + } + } + } + return mw.fw.Save(mw.filename) + } + bt := boosttuner.New(boosttuner.Config{ + Symbols: mw.fw, + Save: save, + MeshRenderer: mw.settings.GetMeshRenderer(), + Colorblind: mw.settings.GetColorBlindMode(), + }) + inner := multiwindow.NewInnerWindow("Boost Auto-Tuner", bt) + inner.Icon = theme.GridIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(1100, 760)) +} + +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +// openMatrixBuilder opens (or raises) the matrix builder window. The builder +// loads its own log files, so it is independent of any open log player. +func (mw *MainWindow) openMatrixBuilder() { + if w := mw.wm.HasWindow("Matrix builder"); w != nil { + mw.wm.Raise(w) + return + } + inner := multiwindow.NewInnerWindow("Matrix builder", matrixbuilder.New(mw.settings.GetMeshRenderer())) + inner.Icon = theme.GridIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(1000, 720)) +} + +// openRescaler opens (or raises) the map rescaler for a single map. It reads the +// map and its X/Y axes from the loaded binary, lets the user edit the axis +// support points, resamples the surface onto them, and writes the result back +// through a save closure that takes a one-time .bak before the first write. +func (mw *MainWindow) openRescaler(typ symbol.ECUType, mapName string) { + if mw.fw == nil { + mw.Error(fmt.Errorf("no binary loaded")) + return + } + winName := "Rescale " + mapName + if w := mw.wm.HasWindow(winName); w != nil { + mw.wm.Raise(w) + return + } + + axis := symbol.GetInfo(typ, mapName) + symX := mw.fw.GetByName(axis.X) + symY := mw.fw.GetByName(axis.Y) + symZ := mw.fw.GetByName(axis.Z) + if symZ == nil || symY == nil { + mw.Error(fmt.Errorf("rescaler: missing symbol(s) for %s", mapName)) + return + } + + xData := []float64{0} + xPrecision := 0 + if symX != nil { + xData = symX.Float64s() + xPrecision = symbol.GetPrecision(symX.Correctionfactor) + } + + cfg := &rescaler.Config{ + Name: axis.Z, + XLabel: axis.X, + YLabel: axis.Y, + ZLabel: axis.Z, + XData: xData, + YData: symY.Float64s(), + ZData: symZ.Float64s(), + XPrecision: xPrecision, + YPrecision: symbol.GetPrecision(symY.Correctionfactor), + ZPrecision: symbol.GetPrecision(symZ.Correctionfactor), + Apply: func(newX, newY, newZ []float64) error { + if symX != nil { + if err := symX.SetData(symX.EncodeFloat64s(newX)); err != nil { + return err + } + } + if err := symY.SetData(symY.EncodeFloat64s(newY)); err != nil { + return err + } + if err := symZ.SetData(symZ.EncodeFloat64s(newZ)); err != nil { + return err + } + if mw.filename != "" { + if bak := mw.filename + ".bak"; !fileExists(bak) { + if orig, err := os.ReadFile(mw.filename); err == nil { + _ = os.WriteFile(bak, orig, 0o644) + } + } + } + if err := mw.fw.Save(mw.filename); err != nil { + return err + } + mw.Log("Rescaled and saved " + axis.Z) + return nil + }, + } + + inner := multiwindow.NewInnerWindow(winName, rescaler.New(cfg)) + inner.Icon = theme.GridIcon() + mw.wm.Add(inner) + inner.Resize(fyne.NewSize(900, 720)) +} diff --git a/pkg/windows/mainWindow_toolbar.go b/pkg/windows/mainWindow_toolbar.go index cf1c9513..63347bcc 100644 --- a/pkg/windows/mainWindow_toolbar.go +++ b/pkg/windows/mainWindow_toolbar.go @@ -1,78 +1,14 @@ package windows import ( - "fmt" - "os" - "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - "github.com/roffe/txlogger/pkg/widgets/boosttuner" - "github.com/roffe/txlogger/pkg/widgets/matrixbuilder" "github.com/roffe/txlogger/pkg/widgets/multiwindow" ) -// openBoostTuner opens (or raises) the T7 boost auto-tuner. It reads the current -// BoostCal maps from the loaded binary and writes tuned maps back through a save -// closure that takes a one-time .bak of the file before the first write. -func (mw *MainWindow) openBoostTuner() { - if mw.fw == nil { - mw.Error(fmt.Errorf("no binary loaded")) - return - } - if w := mw.wm.HasWindow("Boost Auto-Tuner"); w != nil { - mw.wm.Raise(w) - return - } - save := func(symbolName string, data []float64) error { - sym := mw.fw.GetByName(symbolName) - if sym == nil { - return fmt.Errorf("symbol %s not found", symbolName) - } - if err := sym.SetData(sym.EncodeFloat64s(data)); err != nil { - return err - } - if mw.filename != "" { - if bak := mw.filename + ".bak"; !fileExists(bak) { - if orig, err := os.ReadFile(mw.filename); err == nil { - _ = os.WriteFile(bak, orig, 0o644) - } - } - } - return mw.fw.Save(mw.filename) - } - bt := boosttuner.New(boosttuner.Config{ - Symbols: mw.fw, - Save: save, - MeshRenderer: mw.settings.GetMeshRenderer(), - Colorblind: mw.settings.GetColorBlindMode(), - }) - inner := multiwindow.NewInnerWindow("Boost Auto-Tuner", bt) - inner.Icon = theme.GridIcon() - mw.wm.Add(inner) - inner.Resize(fyne.NewSize(1100, 760)) -} - -func fileExists(path string) bool { - _, err := os.Stat(path) - return err == nil -} - -// openMatrixBuilder opens (or raises) the matrix builder window. The builder -// loads its own log files, so it is independent of any open log player. -func (mw *MainWindow) openMatrixBuilder() { - if w := mw.wm.HasWindow("Matrix builder"); w != nil { - mw.wm.Raise(w) - return - } - inner := multiwindow.NewInnerWindow("Matrix builder", matrixbuilder.New(mw.settings.GetMeshRenderer())) - inner.Icon = theme.GridIcon() - mw.wm.Add(inner) - inner.Resize(fyne.NewSize(1000, 720)) -} - func (mw *MainWindow) newToolbar() *fyne.Container { toolbar := container.NewHBox( container.NewBorder( diff --git a/pkg/windows/mainmenu_t7.go b/pkg/windows/mainmenu_t7.go index 2d06e69a..573e7b37 100644 --- a/pkg/windows/mainmenu_t7.go +++ b/pkg/windows/mainmenu_t7.go @@ -59,9 +59,10 @@ func (mw *MainWindow) t7Menu() []MenuItem { {Name: "TransCal.Delay1"}, {Name: "TransCal.Delay2"}, {Name: "TransCal.m_TriggMaxTab"}, + {Name: "TransCal.FilterConstAir"}, }}, {Name: "VE map", Data: "BFuelCal.Map", Region: "LambdaCal.MaxLoadNormTab"}, - {Name: "Startup VE map / E85 VE map", Data: "BFuelCal.StartMap", Region: "LambdaCal.MaxLoadE85Tab"}, + {Name: "Startup / E85 VE map", Data: "BFuelCal.StartMap", Region: "LambdaCal.MaxLoadE85Tab"}, {Name: "Gas VE map", Data: "BFuelCal.GasMap"}, {Name: "Enrichment factor during starting", Data: "StartCal.EnrFacTab"}, {Name: "Enrichment factor during starting E85", Data: "StartCal.EnrFacE85Tab"}, diff --git a/pkg/windows/mainmenu_t8.go b/pkg/windows/mainmenu_t8.go index c57e5abe..b69c3818 100644 --- a/pkg/windows/mainmenu_t8.go +++ b/pkg/windows/mainmenu_t8.go @@ -1,5 +1,7 @@ package windows +import symbol "github.com/roffe/ecusymbol" + func (mw *MainWindow) t8Menu() []MenuItem { return []MenuItem{ {Name: "Diagnostics", Children: []MenuItem{ @@ -72,6 +74,9 @@ func (mw *MainWindow) t8Menu() []MenuItem { {Name: "Pedal", Children: []MenuItem{ {Name: "Pedal position map", Data: "TrqMastCal.X_AccPedalMAP"}, {Name: "Torque request map", Data: "PedalMapCal.Trq_RequestMap"}, + {Name: "Rescale AccPedalMap", Func: func() { + mw.openRescaler(symbol.ECU_T8, "TrqMastCal.X_AccPedalMAP") + }}, }}, } } From 518e98c201ccb5e7c36d020d688e2ad8d06c5352 Mon Sep 17 00:00:00 2001 From: roffe Date: Tue, 23 Jun 2026 21:03:42 +0200 Subject: [PATCH 086/102] more maps --- pkg/windows/mainmenu_t7.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/windows/mainmenu_t7.go b/pkg/windows/mainmenu_t7.go index 573e7b37..f17e18e8 100644 --- a/pkg/windows/mainmenu_t7.go +++ b/pkg/windows/mainmenu_t7.go @@ -12,6 +12,9 @@ func (mw *MainWindow) t7Menu() []MenuItem { {Name: "Calibration", Children: []MenuItem{ {Name: "ESP Calibration", Func: mw.openESPCalibration}, {Name: "AirCompCal.PressMap"}, + {Name: "AirCtrlCal.map"}, + {Name: "AreaCal.Area"}, + {Name: "AreaCal.Table"}, {Name: "Ethanol adaption value", Data: "E85.X_EthAct_Tech2"}, {Name: "MAFCal.m_RedundantAirMap"}, {Name: "TCompCal.EnrFacE85Tab"}, From 02c5166a7148c30a8aacd1c81da4207f5401a3d0 Mon Sep 17 00:00:00 2001 From: roffe Date: Tue, 23 Jun 2026 23:11:57 +0200 Subject: [PATCH 087/102] some housekeeping --- Makefile | 7 ++- pkg/analyzer/analyzer.go | 36 ++++++------- pkg/common/math.go | 57 ++++++++++++++++++++ pkg/{widgets/plotter => common}/math_simd.go | 4 +- pkg/datalogger/datalogger.go | 4 -- pkg/widgets/dial/dial.go | 10 ---- pkg/widgets/dial/shader/dial.go | 10 ---- pkg/widgets/dualdial/dual_dial.go | 9 ---- pkg/widgets/dualdial/dual_dial.old | 6 --- pkg/widgets/dualdial/shader/dual_dial.go | 9 ---- pkg/widgets/graph2d/graph2d.go | 19 +------ pkg/widgets/mapviewer/mapviewer.go | 6 +-- pkg/widgets/math.go | 23 -------- pkg/widgets/matrixbuilder/matrixbuilder.go | 16 +----- pkg/widgets/meshgrid/meshgrid_draw.go | 12 ++--- pkg/widgets/multiwindow/layout.go | 10 ---- pkg/widgets/multiwindow/multiplewindows.go | 15 +++--- pkg/widgets/plotter/bresenham.go | 17 +++--- pkg/widgets/plotter/math.go | 16 ------ pkg/widgets/plotter/plotter.go | 26 ++------- pkg/widgets/settings/wbleditor.go | 6 +-- pkg/widgets/settings/wblgraph.go | 29 ++-------- pprof.go | 13 +++++ 23 files changed, 132 insertions(+), 228 deletions(-) create mode 100644 pkg/common/math.go rename pkg/{widgets/plotter => common}/math_simd.go (93%) delete mode 100644 pkg/widgets/math.go delete mode 100644 pkg/widgets/plotter/math.go diff --git a/Makefile b/Makefile index ba2b5839..c00e5dd2 100644 --- a/Makefile +++ b/Makefile @@ -20,9 +20,14 @@ txlogger: release: fyne package -tags=$(BUILDTAGS) --release +debug: clean cangateway + @echo Using compiler "$(CC)" + -go run -tags=$(BUILDTAGS),debug . 2>&1 | tee run.log + + run: clean cangateway @echo Using compiler "$(CC)" - -go run -tags=$(BUILDTAGS) . 2>&1 | tee run.log + -GOEXPERIMENT=simd go run -tags=$(BUILDTAGS) . 2>&1 | tee run.log clean: rm -f cangateway diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 2dfb2723..000f1a4a 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -10,41 +10,41 @@ import ( // 75 125 150 180 240 300 360 420 480 540 600 660 720 800 900 1100 1300 1500 var tolerancces []int = []int{ - 5, //75 + 5, // 75 5, - 10, //125 + 10, // 125 10, - 10, //150 + 10, // 150 10, - 10, //180 + 10, // 180 10, - 10, //240 + 10, // 240 10, - 15, //300 + 15, // 300 15, - 15, //360 + 15, // 360 15, - 15, //420 + 15, // 420 15, - 15, //480 + 15, // 480 15, - 15, //540 + 15, // 540 15, - 15, //600 + 15, // 600 15, - 20, //660 + 20, // 660 20, - 20, //720 + 20, // 720 20, - 20, //800 + 20, // 800 30, - 30, //900 + 30, // 900 40, - 40, //1100 + 40, // 1100 40, - 50, //1300 + 50, // 1300 50, - 50, //1500 + 50, // 1500 } // AnalyzeLambda analyzes lambda values based on stable pedal conditions diff --git a/pkg/common/math.go b/pkg/common/math.go new file mode 100644 index 00000000..84b57c7e --- /dev/null +++ b/pkg/common/math.go @@ -0,0 +1,57 @@ +//go:build !goexperiment.simd || !amd64 + +package common + +import "cmp" + +func Abs(n int) int { + if n < 0 { + return -n + } + return n +} + +func FindMinMaxFloat64(data []float64) (float64, float64) { + min, max := data[0], data[0] + for _, v := range data { + if v < min { + min = v + } + if v > max { + max = v + } + } + return min, max +} + +type Number interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 +} + +// Generic function that works with any numeric type +func FindMinMax[T Number](data []T) (T, T) { + if len(data) == 0 { + panic("empty slice") + } + + min, max := data[0], data[0] + for _, v := range data { + if v < min { + min = v + } + if v > max { + max = v + } + } + return min, max +} + +func Clamp[T cmp.Ordered](value, min, max T) T { + if value < min { + return min + } + if value > max { + return max + } + return value +} diff --git a/pkg/widgets/plotter/math_simd.go b/pkg/common/math_simd.go similarity index 93% rename from pkg/widgets/plotter/math_simd.go rename to pkg/common/math_simd.go index c0ba88ee..8009b1f6 100644 --- a/pkg/widgets/plotter/math_simd.go +++ b/pkg/common/math_simd.go @@ -1,12 +1,12 @@ //go:build goexperiment.simd && amd64 -package plotter +package common import ( "simd/archsimd" ) -func findMinMaxFloat64(data []float64) (float64, float64) { +func FindMinMaxFloat64(data []float64) (float64, float64) { n := len(data) if n == 0 { return 0, 0 diff --git a/pkg/datalogger/datalogger.go b/pkg/datalogger/datalogger.go index c65a89bf..402aaf37 100644 --- a/pkg/datalogger/datalogger.go +++ b/pkg/datalogger/datalogger.go @@ -28,10 +28,6 @@ type IClient interface { Close() } -type Consumer interface { - SetValue(string, float64) -} - type Config struct { FilenamePrefix string ECU string diff --git a/pkg/widgets/dial/dial.go b/pkg/widgets/dial/dial.go index 28057828..ac03dabc 100644 --- a/pkg/widgets/dial/dial.go +++ b/pkg/widgets/dial/dial.go @@ -355,13 +355,3 @@ func (c *DialRenderer) Objects() []fyne.CanvasObject { } return c.objects } - -// --- helpers --- - -// max helper that matches your float32 usage -func max(a, b float32) float32 { - if a > b { - return a - } - return b -} diff --git a/pkg/widgets/dial/shader/dial.go b/pkg/widgets/dial/shader/dial.go index acf10f0a..e9697cb3 100644 --- a/pkg/widgets/dial/shader/dial.go +++ b/pkg/widgets/dial/shader/dial.go @@ -273,13 +273,3 @@ func (c *DialRenderer) Objects() []fyne.CanvasObject { } return c.objects } - -// --- helpers --- - -// max helper that matches your float32 usage -func max(a, b float32) float32 { - if a > b { - return a - } - return b -} diff --git a/pkg/widgets/dualdial/dual_dial.go b/pkg/widgets/dualdial/dual_dial.go index 8dc44673..5bd1f64c 100644 --- a/pkg/widgets/dualdial/dual_dial.go +++ b/pkg/widgets/dualdial/dual_dial.go @@ -350,12 +350,3 @@ func (c *DualDialRenderer) Objects() []fyne.CanvasObject { } return c.objects } - -// --- helpers --- - -func max(a, b float32) float32 { - if a > b { - return a - } - return b -} diff --git a/pkg/widgets/dualdial/dual_dial.old b/pkg/widgets/dualdial/dual_dial.old index 063a6d5c..8a25f9b3 100644 --- a/pkg/widgets/dualdial/dual_dial.old +++ b/pkg/widgets/dualdial/dual_dial.old @@ -361,9 +361,3 @@ func appendFormatFloat(dst []byte, format string, v float64) []byte { return strconv.AppendFloat(dst, v, 'f', 0, 64) } -func max(a, b float32) float32 { - if a > b { - return a - } - return b -} diff --git a/pkg/widgets/dualdial/shader/dual_dial.go b/pkg/widgets/dualdial/shader/dual_dial.go index 85f9920d..42afe371 100644 --- a/pkg/widgets/dualdial/shader/dual_dial.go +++ b/pkg/widgets/dualdial/shader/dual_dial.go @@ -289,12 +289,3 @@ func (c *DualDialRenderer) Objects() []fyne.CanvasObject { } return c.objects } - -// --- helpers --- - -func max(a, b float32) float32 { - if a > b { - return a - } - return b -} diff --git a/pkg/widgets/graph2d/graph2d.go b/pkg/widgets/graph2d/graph2d.go index 008a8975..0459393e 100644 --- a/pkg/widgets/graph2d/graph2d.go +++ b/pkg/widgets/graph2d/graph2d.go @@ -14,6 +14,7 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/common" ) const ( @@ -95,7 +96,7 @@ func New(cfg *Config) *Graph { g.axis[i] = float64(i) } } - g.zMin, g.zMax = findMinMax(g.values) + g.zMin, g.zMax = common.FindMinMaxFloat64(g.values) g.ExtendBaseWidget(g) return g } @@ -560,19 +561,3 @@ func niceStep(rng float64, maxTicks int) float64 { return 10 * mag } } - -func findMinMax(values []float64) (float64, float64) { - if len(values) == 0 { - return 0, 0 - } - min, max := values[0], values[0] - for _, v := range values { - if v < min { - min = v - } - if v > max { - max = v - } - } - return min, max -} diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index 58b5c730..ad736c9e 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -18,9 +18,9 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/common" "github.com/roffe/txlogger/pkg/interpolate" "github.com/roffe/txlogger/pkg/layout" - "github.com/roffe/txlogger/pkg/widgets" "github.com/roffe/txlogger/pkg/widgets/graph2d" "github.com/roffe/txlogger/pkg/widgets/meshgrid" ) @@ -115,7 +115,7 @@ func New(config *Config) (*MapViewer, error) { if len(mv.cfg.ZData) == 0 { return nil, fmt.Errorf("mapViewer zData is empty") } - mv.zMin, mv.zMax = widgets.FindMinMax(mv.cfg.ZData) + mv.zMin, mv.zMax = common.FindMinMaxFloat64(mv.cfg.ZData) if mv.numColumns*mv.numRows != mv.numData && mv.numColumns > 1 && mv.numRows > 1 { return nil, fmt.Errorf("mapViewer columns * rows != data length") } @@ -372,7 +372,7 @@ func (mv *MapViewer) SetZData(zData []float64) error { } func (mv *MapViewer) Refresh() { - mv.zMin, mv.zMax = widgets.FindMinMax(mv.cfg.ZData) + mv.zMin, mv.zMax = common.FindMinMaxFloat64(mv.cfg.ZData) if len(mv.textValues) == 0 { // renderer not created yet; createTextValues/createZdata pick up // the current ZData and color mode when it is diff --git a/pkg/widgets/math.go b/pkg/widgets/math.go deleted file mode 100644 index 21ccf48b..00000000 --- a/pkg/widgets/math.go +++ /dev/null @@ -1,23 +0,0 @@ -package widgets - -type Number interface { - ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 -} - -// Generic function that works with any numeric type -func FindMinMax[T Number](data []T) (T, T) { - if len(data) == 0 { - panic("empty slice") - } - - min, max := data[0], data[0] - for _, v := range data { - if v < min { - min = v - } - if v > max { - max = v - } - } - return min, max -} diff --git a/pkg/widgets/matrixbuilder/matrixbuilder.go b/pkg/widgets/matrixbuilder/matrixbuilder.go index f674c338..3bbef794 100644 --- a/pkg/widgets/matrixbuilder/matrixbuilder.go +++ b/pkg/widgets/matrixbuilder/matrixbuilder.go @@ -1005,7 +1005,8 @@ func (mb *MatrixBuilder) autoFill(isX bool) { if !ok || len(data) == 0 { return } - lo, hi := minMax(data) + + lo, hi := common.FindMinMaxFloat64(data) n := len(axis) for i := 0; i < n; i++ { if n == 1 { @@ -1641,19 +1642,6 @@ func countsToFloat(counts []int) []float64 { return out } -func minMax(data []float64) (float64, float64) { - lo, hi := data[0], data[0] - for _, v := range data[1:] { - if v < lo { - lo = v - } - if v > hi { - hi = v - } - } - return lo, hi -} - // precisionFor picks a sensible decimal precision: 0 for all-integer data, // otherwise more decimals for small-magnitude values. func precisionFor(data []float64) int { diff --git a/pkg/widgets/meshgrid/meshgrid_draw.go b/pkg/widgets/meshgrid/meshgrid_draw.go index df6a660b..86b77227 100644 --- a/pkg/widgets/meshgrid/meshgrid_draw.go +++ b/pkg/widgets/meshgrid/meshgrid_draw.go @@ -7,6 +7,7 @@ import ( "slices" "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/common" ) // lineSegment indexes into the precomputed projected/color slices so we don't @@ -290,8 +291,8 @@ func drawBresenhamLine(img *image.RGBA, x0, y0, x1, y1 int, c1, c2 color.RGBA) { pix := img.Pix // Bresenham setup - dx := abs(x1 - x0) - dy := -abs(y1 - y0) + dx := common.Abs(x1 - x0) + dy := -common.Abs(y1 - y0) sx := 1 if x0 > x1 { sx = -1 @@ -433,10 +434,3 @@ func clipCohenSutherland(x0, y0, x1, y1 *int, xmin, ymin, xmax, ymax int) bool { *x0, *y0, *x1, *y1 = x0i, y0i, x1i, y1i return true } - -func abs(v int) int { - if v < 0 { - return -v - } - return v -} diff --git a/pkg/widgets/multiwindow/layout.go b/pkg/widgets/multiwindow/layout.go index de7e71b3..f3fa8af7 100644 --- a/pkg/widgets/multiwindow/layout.go +++ b/pkg/widgets/multiwindow/layout.go @@ -18,13 +18,3 @@ func (m *multiWinLayout) Layout(objects []fyne.CanvasObject, _ fyne.Size) { func (m *multiWinLayout) MinSize(_ []fyne.CanvasObject) fyne.Size { return fyne.Size{Width: 700, Height: 400} } - -func clamp32(value, min, max float32) float32 { - if value < min { - return min - } - if value > max { - return max - } - return value -} diff --git a/pkg/widgets/multiwindow/multiplewindows.go b/pkg/widgets/multiwindow/multiplewindows.go index ac88d8b8..fcbcd803 100644 --- a/pkg/widgets/multiwindow/multiplewindows.go +++ b/pkg/widgets/multiwindow/multiplewindows.go @@ -10,6 +10,7 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/common" ) type WindowRatio struct { @@ -77,11 +78,11 @@ func (m *MultipleWindows) Add(w *InnerWindow, startPosition ...fyne.Position) bo if m.LockViewport { size := w.MinSize() bounds := m.content.Size() - startPosition[0].X = clamp32(startPosition[0].X, 0, bounds.Width-size.Width) - startPosition[0].Y = clamp32(startPosition[0].Y, 0, bounds.Height-size.Height) - //bounds.Subtract(size).Max(startPosition[0]) + startPosition[0].X = common.Clamp(startPosition[0].X, 0, bounds.Width-size.Width) + startPosition[0].Y = common.Clamp(startPosition[0].Y, 0, bounds.Height-size.Height) + // bounds.Subtract(size).Max(startPosition[0]) } - //w.Move(startPosition[0].SubtractXY(w.MinSize().Width*0.5, 80)) + // w.Move(startPosition[0].SubtractXY(w.MinSize().Width*0.5, 80)) w.Move(startPosition[0]) } @@ -245,8 +246,8 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { if m.LockViewport { size := w.Size() bounds := m.content.Size() - newPos.X = clamp32(newPos.X, 0, bounds.Width-size.Width) - newPos.Y = clamp32(newPos.Y, 0, bounds.Height-size.Height) + newPos.X = common.Clamp(newPos.X, 0, bounds.Width-size.Width) + newPos.Y = common.Clamp(newPos.Y, 0, bounds.Height-size.Height) } w.Move(newPos) } @@ -360,7 +361,7 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { if c := fyne.CurrentApp().Driver().CanvasForObject(w); c != nil { c.Focus(f) } - //c.SetOnTypedKey(f.TypedKey) + // c.SetOnTypedKey(f.TypedKey) } m.Raise(w) } diff --git a/pkg/widgets/plotter/bresenham.go b/pkg/widgets/plotter/bresenham.go index a237bdbc..5b17e51f 100644 --- a/pkg/widgets/plotter/bresenham.go +++ b/pkg/widgets/plotter/bresenham.go @@ -4,6 +4,8 @@ import ( "image" "image/color" "math" + + "github.com/roffe/txlogger/pkg/common" ) // BresenhamThick draws a line of given thickness directly into img.Pix, @@ -44,8 +46,8 @@ func BresenhamThick(img *image.RGBA, x1, y1, x2, y2 int, thickness int, col colo } func bresenhamCore(pix []uint8, stride, w, h, x1, y1, x2, y2 int, col color.RGBA) { - dx := abs(x2 - x1) - dy := abs(y2 - y1) + dx := common.Abs(x2 - x1) + dy := common.Abs(y2 - y1) steep := dy > dx if steep { @@ -57,8 +59,8 @@ func bresenhamCore(pix []uint8, stride, w, h, x1, y1, x2, y2 int, col color.RGBA y1, y2 = y2, y1 } - dx = abs(x2 - x1) - dy = abs(y2 - y1) + dx = common.Abs(x2 - x1) + dy = common.Abs(y2 - y1) err := dx / 2 y := y1 ystep := 1 @@ -135,10 +137,3 @@ func fillCircle(pix []uint8, stride, w, h, centerX, centerY, radius int, col col } } } - -func abs(n int) int { - if n < 0 { - return -n - } - return n -} diff --git a/pkg/widgets/plotter/math.go b/pkg/widgets/plotter/math.go deleted file mode 100644 index a555522e..00000000 --- a/pkg/widgets/plotter/math.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !goexperiment.simd || !amd64 - -package plotter - -func findMinMaxFloat64(data []float64) (float64, float64) { - min, max := data[0], data[0] - for _, v := range data { - if v < min { - min = v - } - if v > max { - max = v - } - } - return min, max -} diff --git a/pkg/widgets/plotter/plotter.go b/pkg/widgets/plotter/plotter.go index 2e830255..a4d7dff6 100644 --- a/pkg/widgets/plotter/plotter.go +++ b/pkg/widgets/plotter/plotter.go @@ -16,6 +16,7 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" "github.com/roffe/txlogger/pkg/colors" + "github.com/roffe/txlogger/pkg/common" ) // var _ fyne.Focusable = (*Plotter)(nil) @@ -25,10 +26,6 @@ var ( _ fyne.Widget = (*Plotter)(nil) ) -type PlotterControl interface { - Seek(int) -} - // plotBackend selects how the plot is drawn. The GPU shader is the default; // TXLOGGER_PLOT_RENDERER=image selects the CPU rasterizer, which is also the // automatic fallback when the log does not fit the shader's texture layout. @@ -447,7 +444,7 @@ func NewTimeSeries(name string, values map[string][]float64) *TimeSeries { if min, max, known := defaultRange(name); known { ts.Min, ts.Max = min, max } else { - ts.Min, ts.Max = findMinMaxFloat64(data) + ts.Min, ts.Max = common.FindMinMaxFloat64(data) } ts.valueRange = ts.Max - ts.Min @@ -579,8 +576,8 @@ func (p *Plotter) layoutCursor() { // Account for zoom slider width and ensure cursor stays within plot bounds xOffset := p.zoom.Size().Width + x - xOffset = min32(xOffset, plotSize.Width+p.zoom.Size().Width) - xOffset = max32(xOffset, p.zoom.Size().Width) + xOffset = min(xOffset, plotSize.Width+p.zoom.Size().Width) + xOffset = max(xOffset, p.zoom.Size().Width) p.cursor.Position1 = fyne.NewPos(xOffset, 0) p.cursor.Position2 = fyne.NewPos(xOffset+1, plotSize.Height) @@ -595,18 +592,3 @@ func (p *Plotter) updateCursor(goroutine bool) { p.cursor.Refresh() } } - -// Helper functions -func min32(a, b float32) float32 { - if a < b { - return a - } - return b -} - -func max32(a, b float32) float32 { - if a > b { - return a - } - return b -} diff --git a/pkg/widgets/settings/wbleditor.go b/pkg/widgets/settings/wbleditor.go index b30774fe..72daadc8 100644 --- a/pkg/widgets/settings/wbleditor.go +++ b/pkg/widgets/settings/wbleditor.go @@ -106,7 +106,7 @@ func (m *WBLEditor) buildRow(r *mapRow) { if err != nil { return } - r.y = clampInt(m.yFromVolt(v), yMin, yMax) + r.y = common.Clamp(m.yFromVolt(v), yMin, yMax) r.ye.Text = strconv.Itoa(r.y) r.ye.Refresh() m.refreshGraph() @@ -120,7 +120,7 @@ func (m *WBLEditor) buildRow(r *mapRow) { if err != nil { return } - r.y = clampInt(v, yMin, yMax) + r.y = common.Clamp(v, yMin, yMax) r.vo.Text = fmt.Sprintf("%.2f", m.voltFromY(r.y)) r.vo.Refresh() m.refreshGraph() @@ -135,7 +135,7 @@ func (m *WBLEditor) buildRow(r *mapRow) { if err != nil { return } - r.z = clampFloat(v, zMin, zMax) + r.z = common.Clamp(v, zMin, zMax) m.refreshGraph() m.save() } diff --git a/pkg/widgets/settings/wblgraph.go b/pkg/widgets/settings/wblgraph.go index b6f72f2b..ba2f4640 100644 --- a/pkg/widgets/settings/wblgraph.go +++ b/pkg/widgets/settings/wblgraph.go @@ -7,6 +7,7 @@ import ( "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" + "github.com/roffe/txlogger/pkg/common" ) // --- graph view (native fyne primitives) ----------------------------------- @@ -178,12 +179,12 @@ func (r *graphRenderer) layoutGraph() { const minYSpan, minZSpan = 50, 0.2 if maxY-minY < minYSpan { mid := (maxY + minY) / 2 - minY = clampInt(mid-minYSpan/2, yMin, yMax-minYSpan) + minY = common.Clamp(mid-minYSpan/2, yMin, yMax-minYSpan) maxY = minY + minYSpan } if maxZ-minZ < minZSpan { mid := (maxZ + minZ) / 2 - minZ = clampFloat(mid-minZSpan/2, zMin, zMax-minZSpan) + minZ = common.Clamp(mid-minZSpan/2, zMin, zMax-minZSpan) maxZ = minZ + minZSpan } r.minYv, r.maxYv = minY, maxY @@ -293,8 +294,8 @@ func (p *draggablePoint) Dragged(e *fyne.DragEvent) { step := int(p.accY) p.accY -= float64(step) - p.row.y = clampInt(p.row.y+step, yMin, yMax) - p.row.z = clampFloat(p.row.z+dZ, zMin, zMax) + p.row.y = common.Clamp(p.row.y+step, yMin, yMax) + p.row.z = common.Clamp(p.row.z+dZ, zMin, zMax) p.g.editor.updateRowEntries(p.row) p.g.Refresh() @@ -304,23 +305,3 @@ func (p *draggablePoint) DragEnd() { p.accY = 0 p.g.editor.save() } - -func clampInt(v, lo, hi int) int { - if v < lo { - return lo - } - if v > hi { - return hi - } - return v -} - -func clampFloat(v, lo, hi float64) float64 { - if v < lo { - return lo - } - if v > hi { - return hi - } - return v -} diff --git a/pprof.go b/pprof.go index f86b4a01..055522d2 100644 --- a/pprof.go +++ b/pprof.go @@ -6,9 +6,22 @@ import ( "log" "net/http" _ "net/http/pprof" + "os" + "os/signal" + "runtime/pprof" + "syscall" ) func init() { + // kill -USR1 to dump leaks + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGUSR1) + go func() { + for range sig { + pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1) + } + }() + go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() From d8195464cc868ab374256c9fe0976a3dbeceb5b7 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 25 Jun 2026 22:58:35 +0200 Subject: [PATCH 088/102] another round of optimizations --- go.mod | 10 ++- go.sum | 20 +++--- pkg/common/common.go | 40 ++++++++++++ pkg/common/math.go | 41 ------------ pkg/datalogger/channel.go | 45 +++++++------ pkg/datalogger/log.go | 4 -- pkg/datalogger/log_export.go | 6 +- pkg/datalogger/log_txl.go | 24 ++++--- pkg/datalogger/log_txl_test.go | 41 ++++++++++++ pkg/datalogger/t7logger.go | 14 +++-- pkg/datalogger/txbridgelogger_t7.go | 9 +-- pkg/ecu/t7/erase.go | 5 +- pkg/widgets/cbar/cbar.go | 4 -- pkg/widgets/dashboard/dashboard.go | 6 +- pkg/widgets/dial/dial.go | 63 +++++-------------- pkg/widgets/dial/dial.old | 2 +- pkg/widgets/dial/shader/dial.go | 2 +- pkg/widgets/dualdial/dual_dial.go | 2 +- pkg/widgets/dualdial/dual_dial.old | 2 +- pkg/widgets/dualdial/shader/dual_dial.go | 2 +- pkg/widgets/gauge/gauge.go | 17 +++-- pkg/widgets/hbar/hbar.go | 4 -- pkg/widgets/icon/icon.go | 8 +-- pkg/widgets/igauge.go | 12 ---- pkg/widgets/ledicon/ledicon.go | 10 +-- pkg/widgets/logplayer/logplayer.go | 56 ++++++++++++++--- pkg/widgets/logplayer/playback_timing_test.go | 61 ++++++++++++++++++ pkg/widgets/mapviewer/mapviewer.go | 2 +- pkg/widgets/msglist/msglist.go | 60 ++++++------------ pkg/widgets/multiwindow/arrange.go | 22 +++---- pkg/widgets/multiwindow/innerwindow.go | 21 ++++--- pkg/widgets/multiwindow/multiplewindows.go | 16 ++--- pkg/widgets/symbollist/symbollist.go | 9 ++- .../tappabletext.go} | 40 ++++-------- pkg/widgets/vbar/vbar.go | 4 -- pkg/windows/help.go | 11 +++- pkg/windows/mainWindow.go | 11 +--- pkg/windows/mainWindow_menu.go | 2 +- pkg/windows/mainwindow_layout.go | 12 ++-- 39 files changed, 392 insertions(+), 328 deletions(-) create mode 100644 pkg/datalogger/log_txl_test.go delete mode 100644 pkg/widgets/igauge.go create mode 100644 pkg/widgets/logplayer/playback_timing_test.go rename pkg/widgets/{secrettext/secrettext.go => tappabletext/tappabletext.go} (65%) diff --git a/go.mod b/go.mod index 246e7606..8291d2a1 100644 --- a/go.mod +++ b/go.mod @@ -16,9 +16,9 @@ require ( github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 - github.com/roffe/ecusymbol v1.2.3 - github.com/roffe/gocan v1.4.1 - go.bug.st/serial v1.6.4 + github.com/roffe/ecusymbol v1.2.4 + github.com/roffe/gocan v1.4.2 + go.bug.st/serial v1.7.1 golang.org/x/image v0.40.0 golang.org/x/mod v0.36.0 golang.org/x/net v0.54.0 @@ -32,7 +32,7 @@ require ( github.com/ebitengine/oto/v3 v3.4.0 github.com/godbus/dbus/v5 v5.2.2 github.com/hajimehoshi/go-mp3 v0.3.4 - golang.org/x/sys v0.44.0 + golang.org/x/sys v0.46.0 kernel.org/pub/linux/libs/security/libcap/cap v1.2.78 ) @@ -40,10 +40,8 @@ require ( fyne.io/systray v1.12.2 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/FyshOS/fancyfs v0.0.1 // indirect - github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 // indirect github.com/anthonynsimon/bild v0.13.0 // indirect github.com/bendikro/dl v0.0.0-20190410215913-e41fdb9069d4 // indirect - github.com/creack/goselect v0.1.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/ebitengine/purego v0.9.1 // indirect github.com/fatih/color v1.19.0 // indirect diff --git a/go.sum b/go.sum index a2e0f595..c7da6d40 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ github.com/FyshOS/fancyfs v0.0.1 h1:kgvm7VvwOMLkYTqSflplp62SlMVWQ2uAoHw9CXwXHYg= github.com/FyshOS/fancyfs v0.0.1/go.mod h1:S5SHVz/5R72iCXOxCqdcyTPSlg3JxNd0gaHyGBSrY8A= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7 h1:m3Ayfs5OcAlIMEdLIQKubBsVLGee4YMUr14+d1256WE= -github.com/albenik/bcd v0.0.0-20170831201648-635201416bc7/go.mod h1:QIAMbrwsnQZ2ES3G26RubSrDB5SPyzsp9Hts5NJdTrI= github.com/anthonynsimon/bild v0.13.0 h1:mN3tMaNds1wBWi1BrJq0ipDBhpkooYfu7ZFSMhXt1C8= github.com/anthonynsimon/bild v0.13.0/go.mod h1:tpzzp0aYkAsMi1zmfhimaDyX1xjn2OUc1AJZK/TF0AE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -26,8 +24,6 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/creack/goselect v0.1.3 h1:MaGNMclRo7P2Jl21hBpR1Cn33ITSbKP6E49RtfblLKc= -github.com/creack/goselect v0.1.3/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -136,10 +132,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/roffe/ecusymbol v1.2.3 h1:/Ng+yRyIaDRp72KpBOjwS+wa4mcKp83b2fBn2rY/DuE= -github.com/roffe/ecusymbol v1.2.3/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= -github.com/roffe/gocan v1.4.1 h1:T9aAHzTxS7oXwiOlMIM2TAf75aMHcEaWAi27nKwEFQk= -github.com/roffe/gocan v1.4.1/go.mod h1:qGgFX3osetru/58avh4tQMwThQet+ckqdg0kGM3cG9o= +github.com/roffe/ecusymbol v1.2.4 h1:5MGAs7e6djTAaa4y8bpByATMXjgq7/khLJqff3t3Zx0= +github.com/roffe/ecusymbol v1.2.4/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= +github.com/roffe/gocan v1.4.2 h1:8kx7UjE+akgs3OwRo/t3jaCIl6Bvr3GMqDPuzosc6oE= +github.com/roffe/gocan v1.4.2/go.mod h1:8TjfD7TGUNpAZSPwdtnYRI58ICd2NPAo/gTWZUsne+k= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -168,8 +164,8 @@ github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9/go.mod h1:9BnoKCcgJ/+SLhf github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= -go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= +go.bug.st/serial v1.7.1 h1:5aP8wYL0UjEYOVs3oPAGscjaSfRQLHtCvBFXNN/rwtc= +go.bug.st/serial v1.7.1/go.mod h1:d0MmS16Qt9b1m06yoYRNUXhRRTJV5Qg2S5EKqQtnayQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= @@ -213,8 +209,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/pkg/common/common.go b/pkg/common/common.go index 27e35eef..bdb8c7d4 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -1,6 +1,7 @@ package common import ( + "cmp" "fmt" "math" "os" @@ -185,3 +186,42 @@ func SameTextBytes(s string, b []byte) bool { } return true } + +func Abs(n int) int { + if n < 0 { + return -n + } + return n +} + +type Number interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 +} + +// Generic function that works with any numeric type +func FindMinMax[T Number](data []T) (T, T) { + if len(data) == 0 { + panic("empty slice") + } + + min, max := data[0], data[0] + for _, v := range data { + if v < min { + min = v + } + if v > max { + max = v + } + } + return min, max +} + +func Clamp[T cmp.Ordered](value, min, max T) T { + if value < min { + return min + } + if value > max { + return max + } + return value +} diff --git a/pkg/common/math.go b/pkg/common/math.go index 84b57c7e..57a2482f 100644 --- a/pkg/common/math.go +++ b/pkg/common/math.go @@ -2,15 +2,6 @@ package common -import "cmp" - -func Abs(n int) int { - if n < 0 { - return -n - } - return n -} - func FindMinMaxFloat64(data []float64) (float64, float64) { min, max := data[0], data[0] for _, v := range data { @@ -23,35 +14,3 @@ func FindMinMaxFloat64(data []float64) (float64, float64) { } return min, max } - -type Number interface { - ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 -} - -// Generic function that works with any numeric type -func FindMinMax[T Number](data []T) (T, T) { - if len(data) == 0 { - panic("empty slice") - } - - min, max := data[0], data[0] - for _, v := range data { - if v < min { - min = v - } - if v > max { - max = v - } - } - return min, max -} - -func Clamp[T cmp.Ordered](value, min, max T) T { - if value < min { - return min - } - if value > max { - return max - } - return value -} diff --git a/pkg/datalogger/channel.go b/pkg/datalogger/channel.go index e347be14..90c91f6b 100644 --- a/pkg/datalogger/channel.go +++ b/pkg/datalogger/channel.go @@ -16,25 +16,32 @@ import ( // see a flat, ordered list of named channels and never need to know about // sysvars vs symbols or sync vs async values. type Channel struct { - Name string - read func() float64 - format func(float64) string + Name string + read func() float64 + // appendFmt formats the value into dst and returns the extended slice, + // letting writers format straight into a reused buffer with no per-sample + // string garbage. + appendFmt func(dst []byte, v float64) []byte } // Value returns the current value of the channel. func (c *Channel) Value() float64 { return c.read() } +// Append formats the current value as text into dst and returns the extended +// slice. Allocation-free when dst has spare capacity. +func (c *Channel) Append(dst []byte) []byte { return c.appendFmt(dst, c.read()) } + // String returns the current value formatted as text. -func (c *Channel) String() string { return c.format(c.read()) } +func (c *Channel) String() string { return string(c.appendFmt(nil, c.read())) } // newSysvarChannel reads the latest value of a named entry in the shared sysvars // map. Used for asynchronously updated values such as T7 broadcast frames, the // wideband and AD scanner lambda and other derived values. func newSysvarChannel(sysvars *ThreadSafeMap, name string) Channel { return Channel{ - Name: name, - read: func() float64 { return sysvars.Get(name) }, - format: sysvarFormat(name), + Name: name, + read: func() float64 { return sysvars.Get(name) }, + appendFmt: sysvarFormat(name), } } @@ -42,25 +49,25 @@ func newSysvarChannel(sysvars *ThreadSafeMap, name string) Channel { // payload Read. func newSymbolChannel(sym *symbol.Symbol) Channel { return Channel{ - Name: sym.Name, - read: sym.Float64, - format: symbolFormat(sym.Correctionfactor), + Name: sym.Name, + read: sym.Float64, + appendFmt: symbolFormat(sym.Correctionfactor), } } func newFunctionChannel(name string, read func() float64) Channel { return Channel{ - Name: name, - read: read, - format: func(float64) string { return strconv.FormatFloat(read(), 'f', 2, 64) }, + Name: name, + read: read, + appendFmt: func(dst []byte, v float64) []byte { return strconv.AppendFloat(dst, v, 'f', 2, 64) }, } } // sysvarFormat mirrors the precision rules the text writers used for sysvars: // whole numbers print without decimals, the external wideband lambda prints // with three decimals and everything else with two. -func sysvarFormat(name string) func(float64) string { - return func(v float64) string { +func sysvarFormat(name string) func(dst []byte, v float64) []byte { + return func(dst []byte, v float64) []byte { prec := 2 switch { case v == math.Trunc(v): @@ -68,13 +75,13 @@ func sysvarFormat(name string) func(float64) string { case name == EXTERNALWBLSYM: prec = 3 } - return strconv.FormatFloat(v, 'f', prec, 64) + return strconv.AppendFloat(dst, v, 'f', prec, 64) } } // symbolFormat mirrors symbol.StringValue: the number of decimals is derived // from the symbol correction factor. -func symbolFormat(correctionfactor float64) func(float64) string { +func symbolFormat(correctionfactor float64) func(dst []byte, v float64) []byte { prec := 0 switch correctionfactor { case 0.1: @@ -84,7 +91,7 @@ func symbolFormat(correctionfactor float64) func(float64) string { case 0.001: prec = 3 } - return func(v float64) string { - return strconv.FormatFloat(v, 'f', prec, 64) + return func(dst []byte, v float64) []byte { + return strconv.AppendFloat(dst, v, 'f', prec, 64) } } diff --git a/pkg/datalogger/log.go b/pkg/datalogger/log.go index 5efd88cd..1bb87b76 100644 --- a/pkg/datalogger/log.go +++ b/pkg/datalogger/log.go @@ -54,7 +54,3 @@ func createLog(path, prefix, extension string) (*os.File, string, error) { } return file, fullFilename, nil } - -func replaceDot(s string) string { - return strings.Replace(s, ".", ",", 1) -} diff --git a/pkg/datalogger/log_export.go b/pkg/datalogger/log_export.go index 4d16bc8f..dac69d32 100644 --- a/pkg/datalogger/log_export.go +++ b/pkg/datalogger/log_export.go @@ -48,9 +48,9 @@ func ExportRecords(dir, prefix, ext string, records []logfile.Record) (string, e for i, name := range cols { i := i channels[i] = Channel{ - Name: name, - read: func() float64 { return values[i] }, - format: sysvarFormat(name), + Name: name, + read: func() float64 { return values[i] }, + appendFmt: sysvarFormat(name), } } diff --git a/pkg/datalogger/log_txl.go b/pkg/datalogger/log_txl.go index e323cf3c..b10019ad 100644 --- a/pkg/datalogger/log_txl.go +++ b/pkg/datalogger/log_txl.go @@ -13,19 +13,29 @@ func NewTXLWriter(f *os.File) *TXWriter { type TXWriter struct { file *os.File + buf []byte // reusable per-line buffer } func (t *TXWriter) Write(ts time.Time, channels []Channel) error { - _, err := t.file.Write([]byte(ts.Format("02-01-2006 15:04:05.999") + "|")) - if err != nil { - return err - } + t.buf = ts.AppendFormat(t.buf[:0], "02-01-2006 15:04:05.999") + t.buf = append(t.buf, '|') for i := range channels { - if _, err := t.file.Write([]byte(channels[i].Name + "=" + replaceDot(channels[i].String()) + "|")); err != nil { - return err + t.buf = append(t.buf, channels[i].Name...) + t.buf = append(t.buf, '=') + // Format the value straight into buf, then swap the first decimal '.' + // for ',' in place (the TXL/European separator). No per-sample string. + start := len(t.buf) + t.buf = channels[i].Append(t.buf) + for j := start; j < len(t.buf); j++ { + if t.buf[j] == '.' { + t.buf[j] = ',' + break + } } + t.buf = append(t.buf, '|') } - _, err = t.file.Write([]byte("IMPORTANTLINE=0|\n")) + t.buf = append(t.buf, "IMPORTANTLINE=0|\n"...) + _, err := t.file.Write(t.buf) return err } diff --git a/pkg/datalogger/log_txl_test.go b/pkg/datalogger/log_txl_test.go new file mode 100644 index 00000000..f53becdf --- /dev/null +++ b/pkg/datalogger/log_txl_test.go @@ -0,0 +1,41 @@ +package datalogger + +import ( + "os" + "strings" + "testing" + "time" +) + +// Verifies the append-style formatting still produces the TXL line format: +// "ts|Name=value|...|IMPORTANTLINE=0|" with the first decimal '.' swapped for ','. +func TestTXWriterLineFormat(t *testing.T) { + sv := NewThreadSafeMap() + sv.Set("Rpm", 3000) // whole number -> no decimals + sv.Set("Lambda", 0.987) // decimal -> comma separator + sv.Set("Lambda.External", 0.987) + chans := []Channel{newSysvarChannel(sv, "Rpm"), newSysvarChannel(sv, "Lambda"), newSysvarChannel(sv, "Lambda.External")} + + f, err := os.CreateTemp(t.TempDir(), "*.t7l") + if err != nil { + t.Fatal(err) + } + w := NewTXLWriter(f) + ts := time.Date(2026, 6, 25, 12, 30, 0, 0, time.UTC) + if err := w.Write(ts, chans); err != nil { + t.Fatal(err) + } + if err := w.Close(); err != nil { + t.Fatal(err) + } + + got, err := os.ReadFile(f.Name()) + if err != nil { + t.Fatal(err) + } + line := strings.TrimRight(string(got), "\n") + want := "25-06-2026 12:30:00|Rpm=3000|Lambda=0,99|Lambda.External=0,987|IMPORTANTLINE=0|" + if line != want { + t.Fatalf("got %q\nwant %q", line, want) + } +} diff --git a/pkg/datalogger/t7logger.go b/pkg/datalogger/t7logger.go index ab536d2b..f5ae34b5 100644 --- a/pkg/datalogger/t7logger.go +++ b/pkg/datalogger/t7logger.go @@ -25,7 +25,7 @@ func NewT7(cfg Config, lw LogWriter) (IClient, error) { } func t7broadcastListener(ctx context.Context, cl *gocan.Client, sysvars *ThreadSafeMap) { - broadcast := cl.Subscribe(ctx, 0x1A0, 0x280, 0x3A0) + broadcast := cl.Subscribe(ctx, 0x1A0, 0x280, 0x3A0 /*, 0x5C0*/) defer broadcast.Close() var speed uint16 var rpm uint16 @@ -50,7 +50,6 @@ func t7broadcastListener(ctx context.Context, cl *gocan.Client, sysvars *ThreadS limp = msg.Data[3] & 0x01 cel = msg.Data[4] & 0x80 >> 7 cruise = msg.Data[4] & 0x20 >> 5 - gear = msg.Data[1] sysvars.Set("Out.X_ActualGear", float64(gear)) brakeLight = msg.Data[2] & 0x02 >> 1 @@ -61,11 +60,16 @@ func t7broadcastListener(ctx context.Context, cl *gocan.Client, sysvars *ThreadS ebus.Publish("LIMP", float64(limp)) ebus.Publish("CRUISE", float64(cruise)) ebus.Publish("CEL", float64(cel)) - case 0x3A0: speed = uint16(msg.Data[4]) | uint16(msg.Data[3])<<8 realSpeed = float64(speed) * 0.1 sysvars.Set("In.v_Vehicle", realSpeed) + case 0x5C0: + // 0x5C0 COTE_ECS: Data[1]=coolant. byte=(u8)(V+40), + coolant := float64(msg.Data[1]) - 40 + sysvars.Set("ActualIn.T_Engine", coolant) + // log.Printf("0x5C0: % X valid=%t coolant=%v", msg.Data, msg.Data[0]&0x10 != 0, coolant) + } } } @@ -109,7 +113,7 @@ func (c *T7Client) Start() error { c.OnMessage("Watching for broadcast messages") <-time.After(1550 * time.Millisecond) if found := c.sysvars.Keys(); len(found) > 0 { - c.OnMessage(fmt.Sprintf("Found %s", found)) + c.OnMessage(fmt.Sprintf("Found: %s", strings.Join(found, ", "))) } else { c.OnMessage("No broadcast messages found, stopping broadcast listener") bcancel() @@ -354,7 +358,7 @@ func (c *T7Client) Start() error { func initT7logging(ctx context.Context, kwp *kwp2000.Client, symbols []*symbol.Symbol, onMessage func(string)) error { if err := kwp.StartSession(ctx, kwp2000.INIT_MSG_ID, kwp2000.INIT_RESP_ID); err != nil { - return errors.New("failed to start session") + return fmt.Errorf("failed to start session: %w", err) } onMessage("Connected to ECU") diff --git a/pkg/datalogger/txbridgelogger_t7.go b/pkg/datalogger/txbridgelogger_t7.go index cbf52f84..f3220461 100644 --- a/pkg/datalogger/txbridgelogger_t7.go +++ b/pkg/datalogger/txbridgelogger_t7.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "fmt" "log" + "strings" "time" symbol "github.com/roffe/ecusymbol" @@ -25,10 +26,10 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { c.OnMessage("Watching for broadcast messages") <-time.After(1550 * time.Millisecond) - found := c.sysvars.Keys() - c.OnMessage(fmt.Sprintf("Found %s", found)) - - if len(found) == 0 { + if found := c.sysvars.Keys(); len(found) > 0 { + c.OnMessage(fmt.Sprintf("Found: %s", strings.Join(found, ", "))) + } else { + c.OnMessage("No broadcast messages found, stopping broadcast listener") bcancel() } diff --git a/pkg/ecu/t7/erase.go b/pkg/ecu/t7/erase.go index 34013380..d32a06f2 100644 --- a/pkg/ecu/t7/erase.go +++ b/pkg/ecu/t7/erase.go @@ -7,11 +7,12 @@ import ( "time" "github.com/roffe/gocan" + "github.com/roffe/txlogger/pkg/kwp2000" ) func (t *Client) EraseECU(ctx context.Context) error { data := make([]byte, 8) - eraseMsg := []byte{0x40, 0xA1, 0x02, 0x31, 0x52, 0x00, 0x00, 0x00} + eraseMsg := []byte{0x40, 0xA1, 0x02, kwp2000.START_ROUTINE_BY_IDENTIFIER, kwp2000.RLI_EOL_START, 0x00, 0x00, 0x00} confirmMsg := []byte{0x40, 0xA1, 0x01, 0x3E, 0x00, 0x00, 0x00, 0x00} t.cfg.OnProgress(-float64(17)) @@ -41,7 +42,7 @@ func (t *Client) EraseECU(ctx context.Context) error { // Start erase routine data[3] = 0 i = 0 - eraseMsg[4] = 0x53 + eraseMsg[4] = kwp2000.RLI_ERASE for data[3] != 0x71 && i < 200 { f, err := t.c.SendAndWait(ctx, gocan.NewFrame(0x240, eraseMsg, gocan.ResponseRequired), t.defaultTimeout, 0x258) if err != nil { diff --git a/pkg/widgets/cbar/cbar.go b/pkg/widgets/cbar/cbar.go index 774530f1..31958ba2 100644 --- a/pkg/widgets/cbar/cbar.go +++ b/pkg/widgets/cbar/cbar.go @@ -193,10 +193,6 @@ func (s *CBar) updateDisplayTextPosition() { s.displayText.Move(fyne.Position{X: x, Y: s.displayY}) } -func (s *CBar) SetValue2(value float64) { - s.SetValue(value) -} - func (s *CBar) CreateRenderer() fyne.WidgetRenderer { // Initialize visual elements s.initializeVisualElements() diff --git a/pkg/widgets/dashboard/dashboard.go b/pkg/widgets/dashboard/dashboard.go index cc82947e..e11c4023 100644 --- a/pkg/widgets/dashboard/dashboard.go +++ b/pkg/widgets/dashboard/dashboard.go @@ -525,7 +525,7 @@ func (db *Dashboard) layoutIcons(dims *dims) { }) // Taz icon - tazMin := fyne.Min(dims.sixthWidth, dims.thirdHeight) + tazMin := min(dims.sixthWidth, dims.thirdHeight) tazSize := fyne.Size{Width: tazMin, Height: tazMin + 16} db.image.taz.Resize(tazSize) @@ -672,10 +672,6 @@ func (dr *DashboardRenderer) Destroy() { } func (dr *DashboardRenderer) Objects() []fyne.CanvasObject { - // The object set is fixed for the lifetime of the renderer, so build it - // once and reuse it. Fyne calls Objects() on every render/refresh pass, - // and during live logging this would otherwise allocate a new slice each - // time, creating needless GC pressure. if dr.objects == nil { dr.objects = []fyne.CanvasObject{ dr.db.image.wheelLeft, diff --git a/pkg/widgets/dial/dial.go b/pkg/widgets/dial/dial.go index ac03dabc..d06bdfc4 100644 --- a/pkg/widgets/dial/dial.go +++ b/pkg/widgets/dial/dial.go @@ -4,7 +4,6 @@ import ( "image/color" "math" "strconv" - "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" @@ -19,13 +18,9 @@ type Dial struct { cfg *widgets.GaugeConfig - factor float64 - value float64 - highestObserved float64 + value float64 - needle *canvas.Line - highestObservedMarker *canvas.Line - lastHighestObserved time.Time + needle *canvas.Line pips []*canvas.Line pipLabels []*canvas.Text @@ -93,12 +88,9 @@ func New(cfg *widgets.GaugeConfig) *Dial { totalRange = 1 } - c.factor = c.cfg.Max / steps - c.face = canvas.NewArc(-135.73, 135.8, 0.985, color.RGBA{0x80, 0x80, 0x80, 0xFF}) c.center = &canvas.Circle{FillColor: color.RGBA{R: 0x01, G: 0x0B, B: 0x13, A: 0xFF}} c.needle = &canvas.Line{StrokeColor: color.RGBA{R: 0xFF, G: 0x67, B: 0, A: 0xFF}, StrokeWidth: 3} - c.highestObservedMarker = &canvas.Line{StrokeColor: color.RGBA{R: 216, G: 250, B: 8, A: 0xFF}, StrokeWidth: 6} c.titleText = &canvas.Text{Text: c.cfg.Title, Color: color.RGBA{R: 0xF0, G: 0xF0, B: 0xF0, A: 0xFF}, TextSize: 25} c.titleText.TextStyle.Monospace = true @@ -137,7 +129,6 @@ func New(cfg *widgets.GaugeConfig) *Dial { Color: color.RGBA{0xE0, 0xE0, 0xE0, 0xFF}, Alignment: fyne.TextAlignCenter, } - // lbl.TextStyle.Monospace = true if n := len(txt); n > c.maxLabelChars { c.maxLabelChars = n } @@ -200,20 +191,6 @@ func (c *Dial) SetValue(value float64) { // Update needle position (no immediate refresh) c.rotateNeedleNoRefresh(c.needle, value, c.needleOffset, c.needleLength) - // Highest observed marker with lazy reset; only refresh when it actually moves - markerMoved := false - if value > c.highestObserved { - c.highestObserved = value - c.lastHighestObserved = time.Now() - c.rotateNeedleNoRefresh(c.highestObservedMarker, value, c.radius-2, 6) - markerMoved = true - } else if time.Since(c.lastHighestObserved) > 10*time.Second { - c.highestObserved = value - c.lastHighestObserved = time.Now() - c.rotateNeedleNoRefresh(c.highestObservedMarker, value, c.radius-2, 6) - markerMoved = true - } - // Update text with minimal allocs; skip refresh if formatted output is unchanged c.buf = c.buf[:0] if c.fmtPrec >= 0 { @@ -227,30 +204,26 @@ func (c *Dial) SetValue(value float64) { } canvas.Refresh(c.needle) - if markerMoved { - canvas.Refresh(c.highestObservedMarker) - } if textChanged { canvas.Refresh(c.displayText) } } -func (c *Dial) SetValue2(value float64) { c.SetValue(value) } - -func (c *Dial) CreateRenderer() fyne.WidgetRenderer { return &DialRenderer{Dial: c} } +func (c *Dial) CreateRenderer() fyne.WidgetRenderer { return &DialRenderer{d: c} } type DialRenderer struct { - *Dial + d *Dial objects []fyne.CanvasObject } -func (c *DialRenderer) Layout(space fyne.Size) { +func (r *DialRenderer) Layout(space fyne.Size) { + c := r.d if c.size == space { return } c.size = space - c.diameter = fyne.Min(space.Width, space.Height) + c.diameter = min(space.Width, space.Height) c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) c.needleOffset = -c.radius * .15 @@ -329,18 +302,16 @@ func (c *DialRenderer) Layout(space fyne.Size) { c.applySinCos(p, c.pipSin[i], c.pipCos[i], radius87, eightRadius-1) } } - - c.highestObservedMarker.StrokeWidth = max(2.0, midStroke) - c.rotateNeedleNoRefresh(c.highestObservedMarker, c.highestObserved, c.radius-2, 6) } -func (c *DialRenderer) MinSize() fyne.Size { return c.minsize } -func (c *DialRenderer) Refresh() {} -func (c *DialRenderer) Destroy() {} +func (r *DialRenderer) MinSize() fyne.Size { return r.d.minsize } +func (r *DialRenderer) Refresh() {} +func (r *DialRenderer) Destroy() {} -func (c *DialRenderer) Objects() []fyne.CanvasObject { - if c.objects == nil { - objs := make([]fyne.CanvasObject, 0, len(c.pips)+len(c.pipLabels)+7) +func (r *DialRenderer) Objects() []fyne.CanvasObject { + if r.objects == nil { + c := r.d + objs := make([]fyne.CanvasObject, 0, len(c.pips)+len(c.pipLabels)+6) for _, v := range c.pips { objs = append(objs, v) } @@ -350,8 +321,8 @@ func (c *DialRenderer) Objects() []fyne.CanvasObject { } } objs = append(objs, c.face, c.titleText, c.center, - c.highestObservedMarker, c.needle, c.displayText) - c.objects = objs + c.needle, c.displayText) + r.objects = objs } - return c.objects + return r.objects } diff --git a/pkg/widgets/dial/dial.old b/pkg/widgets/dial/dial.old index 4eaf3858..f3a76afe 100644 --- a/pkg/widgets/dial/dial.old +++ b/pkg/widgets/dial/dial.old @@ -233,7 +233,7 @@ func (c *DialRenderer) Layout(space fyne.Size) { } c.size = space - c.diameter = fyne.Min(space.Width, space.Height) + c.diameter = fmin(space.Width, space.Height) c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) c.needleOffset = -c.radius * .15 diff --git a/pkg/widgets/dial/shader/dial.go b/pkg/widgets/dial/shader/dial.go index e9697cb3..d7a36011 100644 --- a/pkg/widgets/dial/shader/dial.go +++ b/pkg/widgets/dial/shader/dial.go @@ -205,7 +205,7 @@ func (c *DialRenderer) Layout(space fyne.Size) { } c.size = space - c.diameter = fyne.Min(space.Width, space.Height) + c.diameter = min(space.Width, space.Height) c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) diff --git a/pkg/widgets/dualdial/dual_dial.go b/pkg/widgets/dualdial/dual_dial.go index 5bd1f64c..6e7db05e 100644 --- a/pkg/widgets/dualdial/dual_dial.go +++ b/pkg/widgets/dualdial/dual_dial.go @@ -251,7 +251,7 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { } c.size = space - c.diameter = fyne.Min(space.Width, space.Height) + c.diameter = min(space.Width, space.Height) c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) diff --git a/pkg/widgets/dualdial/dual_dial.old b/pkg/widgets/dualdial/dual_dial.old index 8a25f9b3..ea3a3719 100644 --- a/pkg/widgets/dualdial/dual_dial.old +++ b/pkg/widgets/dualdial/dual_dial.old @@ -231,7 +231,7 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { } c.size = space - c.diameter = fyne.Min(space.Width, space.Height) + c.diameter = min(space.Width, space.Height) c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) diff --git a/pkg/widgets/dualdial/shader/dual_dial.go b/pkg/widgets/dualdial/shader/dual_dial.go index 42afe371..9bec975e 100644 --- a/pkg/widgets/dualdial/shader/dual_dial.go +++ b/pkg/widgets/dualdial/shader/dual_dial.go @@ -219,7 +219,7 @@ func (c *DualDialRenderer) Layout(space fyne.Size) { } c.size = space - c.diameter = fyne.Min(space.Width, space.Height) + c.diameter = min(space.Width, space.Height) c.radius = c.diameter * common.OneHalf c.middle = fyne.NewPos(space.Width*common.OneHalf, space.Height*common.OneHalf) diff --git a/pkg/widgets/gauge/gauge.go b/pkg/widgets/gauge/gauge.go index d93ab59d..03f8dc23 100644 --- a/pkg/widgets/gauge/gauge.go +++ b/pkg/widgets/gauge/gauge.go @@ -3,6 +3,7 @@ package gauge import ( "errors" + "fyne.io/fyne/v2" "github.com/roffe/txlogger/pkg/ebus" "github.com/roffe/txlogger/pkg/widgets" "github.com/roffe/txlogger/pkg/widgets/cbar" @@ -12,29 +13,33 @@ import ( "github.com/roffe/txlogger/pkg/widgets/vbar" ) -func New(cfg *widgets.GaugeConfig) (widgets.IGauge, []func(), error) { +func New(cfg *widgets.GaugeConfig) (fyne.CanvasObject, func(), error) { switch cfg.Type { case "Dial": dial := dial.New(cfg) cancel := ebus.SubscribeFunc(cfg.SymbolName, dial.SetValue) - return dial, []func(){cancel}, nil + return dial, cancel, nil case "DualDial": ddial := dualdial.New(cfg) cancel1 := ebus.SubscribeFunc(cfg.SymbolName, ddial.SetValue) cancel2 := ebus.SubscribeFunc(cfg.SymbolNameSecondary, ddial.SetValue2) - return ddial, []func(){cancel1, cancel2}, nil + cancelFn := func() { + cancel1() + cancel2() + } + return ddial, cancelFn, nil case "VBar": vb := vbar.New(cfg) cancel := ebus.SubscribeFunc(cfg.SymbolName, vb.SetValue) - return vb, []func(){cancel}, nil + return vb, cancel, nil case "HBar": hb := hbar.New(cfg) cancel := ebus.SubscribeFunc(cfg.SymbolName, hb.SetValue) - return hb, []func(){cancel}, nil + return hb, cancel, nil case "CBar": cb := cbar.New(cfg) cancel := ebus.SubscribeFunc(cfg.SymbolName, cb.SetValue) - return cb, []func(){cancel}, nil + return cb, cancel, nil } return nil, nil, errors.New("unknown gauge type") } diff --git a/pkg/widgets/hbar/hbar.go b/pkg/widgets/hbar/hbar.go index 633f733e..de0fa3ab 100644 --- a/pkg/widgets/hbar/hbar.go +++ b/pkg/widgets/hbar/hbar.go @@ -130,10 +130,6 @@ func (s *HBar) SetValue(value float64) { } } -func (s *HBar) SetValue2(value float64) { - s.SetValue(value) -} - func (s *HBar) Value() float64 { return s.value } diff --git a/pkg/widgets/icon/icon.go b/pkg/widgets/icon/icon.go index 81704f88..8e6ee8de 100644 --- a/pkg/widgets/icon/icon.go +++ b/pkg/widgets/icon/icon.go @@ -46,7 +46,10 @@ func (ic *Icon) SetText(text string) { } func (ic *Icon) CreateRenderer() fyne.WidgetRenderer { - return &IconRenderer{IC: ic} + return &IconRenderer{ + IC: ic, + objects: []fyne.CanvasObject{ic.cfg.Image, ic.text}, + } } type IconRenderer struct { @@ -72,8 +75,5 @@ func (ic *IconRenderer) Destroy() { } func (ic *IconRenderer) Objects() []fyne.CanvasObject { - if ic.objects == nil { - ic.objects = []fyne.CanvasObject{ic.IC.cfg.Image, ic.IC.text} - } return ic.objects } diff --git a/pkg/widgets/igauge.go b/pkg/widgets/igauge.go deleted file mode 100644 index b7368da4..00000000 --- a/pkg/widgets/igauge.go +++ /dev/null @@ -1,12 +0,0 @@ -package widgets - -import ( - "fyne.io/fyne/v2" -) - -type IGauge interface { - fyne.Widget - SetValue(float64) - SetValue2(float64) - GetConfig() *GaugeConfig -} diff --git a/pkg/widgets/ledicon/ledicon.go b/pkg/widgets/ledicon/ledicon.go index f3de64a9..6d74049f 100644 --- a/pkg/widgets/ledicon/ledicon.go +++ b/pkg/widgets/ledicon/ledicon.go @@ -25,7 +25,7 @@ func New(text string) *Widget { ColorOff: color.RGBA{0x80, 0x80, 0x80, 0xFF}, } w.ExtendBaseWidget(w) - w.ledicon = &canvas.Circle{FillColor: color.RGBA{0x80, 0x80, 0x80, 0xFF}} + w.ledicon = &canvas.Circle{FillColor: w.ColorOff} w.label = widget.NewLabel(text) return w } @@ -52,7 +52,10 @@ func (w *Widget) SetState(state bool) { } func (w *Widget) CreateRenderer() fyne.WidgetRenderer { - return &iconRenderer{w: w} + return &iconRenderer{ + w: w, + objects: []fyne.CanvasObject{w.ledicon, w.label}, + } } var _ fyne.WidgetRenderer = (*iconRenderer)(nil) @@ -82,8 +85,5 @@ func (r *iconRenderer) Destroy() { } func (r *iconRenderer) Objects() []fyne.CanvasObject { - if r.objects == nil { - r.objects = []fyne.CanvasObject{r.w.ledicon, r.w.label} - } return r.objects } diff --git a/pkg/widgets/logplayer/logplayer.go b/pkg/widgets/logplayer/logplayer.go index 7b88e19f..4e83b618 100644 --- a/pkg/widgets/logplayer/logplayer.go +++ b/pkg/widgets/logplayer/logplayer.go @@ -261,6 +261,7 @@ func (l *Logplayer) render() { l.objs.selectionLabel = widget.NewLabel("") l.updateSelectionLabel() + n := l.logFile.Len() values := make(map[string][]float64) for { if rec := l.logFile.Next(); !rec.EOF { @@ -268,7 +269,11 @@ func (l *Logplayer) render() { if k == "Pgm_status" { continue } - values[k] = append(values[k], v) + s, ok := values[k] + if !ok { + s = make([]float64, 0, n) // ponytail: cap once, kills append regrowth churn + } + values[k] = append(s, v) } } else { break @@ -492,12 +497,40 @@ func (l *Logplayer) exportSelection() { l.cfg.OnExport(records) } +// frameTarget returns the wall-clock instant a record should play at, given an +// anchor (anchorWall pinned to anchorRec) and a speed multiplier where larger +// means slower (1 = real time). Because the target is computed from the record's +// own timestamp relative to the fixed anchor, scheduling jitter on earlier +// frames does not shift it — error stays bounded instead of accumulating. +func frameTarget(anchorWall, anchorRec, recTime time.Time, speed float64) time.Time { + return anchorWall.Add(time.Duration(float64(recTime.Sub(anchorRec)) * speed)) +} + func (l *Logplayer) playLog() { speedMultiplier := 1.0 timer := time.NewTimer(0) defer timer.Stop() - var nextDelay time.Duration + // Absolute scheduling: anchorWall maps to anchorRec (the wall-clock instant + // the current record's timeline position was pinned). Every frame is + // scheduled against its record's true timestamp relative to this anchor, so + // per-frame scheduling jitter cannot accumulate into drift — a late frame + // fires immediately and re-syncs instead of pushing the whole timeline back. + var anchorWall, anchorRec time.Time + + // reanchor pins the timeline to the current record at the current instant. + // Called whenever playback (re)starts or jumps: play, seek, step, speed change. + reanchor := func() { + anchorWall = time.Now() + anchorRec = l.logFile.Get().Time + } + + // schedule arms the timer for the next record using its real timestamp. + // time.Until goes negative when we're behind, firing immediately to catch up. + schedule := func() { + next := l.logFile.RecordAt(l.logFile.Pos() + 1) + timer.Reset(time.Until(frameTarget(anchorWall, anchorRec, next.Time, speedMultiplier))) + } timeSetter := func(t time.Time) { timeText := t.Format("15:04:05.00") @@ -515,6 +548,10 @@ func (l *Logplayer) playLog() { switch op.Op { case OpPlaybackSpeed: speedMultiplier = op.Rate + if l.state == statePlaying { + reanchor() + schedule() + } case OpSeek: l.logFile.Seek(op.Pos) if rec := l.logFile.Get(); !rec.EOF { @@ -524,7 +561,8 @@ func (l *Logplayer) playLog() { f(rec.Time) } if l.state == statePlaying { - timer.Reset(0) + reanchor() + schedule() } else { for k, v := range rec.Values { l.cfg.EBus.Publish(k, v) @@ -545,7 +583,8 @@ func (l *Logplayer) playLog() { }) if l.state == statePlaying { - timer.Reset(0) + reanchor() + schedule() } else { for k, v := range rec.Values { l.cfg.EBus.Publish(k, v) @@ -565,7 +604,8 @@ func (l *Logplayer) playLog() { l.objs.positionSlider.Value = float64(pos) timeSetter(rec.Time) if l.state == statePlaying { - timer.Reset(0) + reanchor() + schedule() } else { for k, v := range rec.Values { l.cfg.EBus.Publish(k, v) @@ -579,7 +619,8 @@ func (l *Logplayer) playLog() { } case OpPlay: l.state = statePlaying - timer.Reset(0) // Start playback immediately + reanchor() + schedule() case OpPause: l.state = statePaused timer.Stop() @@ -591,8 +632,7 @@ func (l *Logplayer) playLog() { } if rec := l.logFile.Next(); !rec.EOF { currentPos := l.logFile.Pos() - nextDelay = time.Duration(float64(rec.DelayTillNext)*speedMultiplier) * time.Millisecond - timer.Reset(nextDelay) + schedule() l.objs.positionSlider.Value = float64(currentPos) timeText := rec.Time.Format("15:04:05.00") diff --git a/pkg/widgets/logplayer/playback_timing_test.go b/pkg/widgets/logplayer/playback_timing_test.go new file mode 100644 index 00000000..d69f2c0d --- /dev/null +++ b/pkg/widgets/logplayer/playback_timing_test.go @@ -0,0 +1,61 @@ +package logplayer + +import ( + "testing" + "time" +) + +// TestFrameTargetNoDrift simulates a jittery scheduler (every wake-up is a few ms +// late, as on a loaded Windows box) and checks that absolute scheduling keeps the +// per-frame timing error bounded, while the old relative-reset scheme accumulates +// it into unbounded drift. Same jitter, two schemes. +func TestFrameTargetNoDrift(t *testing.T) { + const ( + frames = 5000 + gap = 10 * time.Millisecond // recording cadence + jitter = 2 * time.Millisecond // how late each wake-up fires + speed = 1.0 // real time + ) + + start := time.Unix(0, 0) + recTime := func(i int) time.Time { return start.Add(time.Duration(i) * gap) } + + // Absolute: target is recomputed from the fixed anchor every frame. + anchorWall, anchorRec := start, recTime(0) + now := start + var absMax time.Duration + + // Relative: the old scheme reset the next delay from the late firing instant. + relNow := start + var relMax time.Duration + + for i := 1; i <= frames; i++ { + // absolute + now = frameTarget(anchorWall, anchorRec, recTime(i), speed).Add(jitter) + if e := absErr(now.Sub(start), recTime(i).Sub(start)); e > absMax { + absMax = e + } + // relative: fire at previous-fire + gap, then add the same jitter + relNow = relNow.Add(gap).Add(jitter) + if e := absErr(relNow.Sub(start), recTime(i).Sub(start)); e > relMax { + relMax = e + } + } + + // Absolute error never exceeds a single wake-up's jitter, no matter how long. + if absMax > jitter { + t.Fatalf("absolute scheme drifted: max error %v > jitter %v", absMax, jitter) + } + // Relative error grows ~jitter per frame: proves the bug the rewrite fixes. + if relMax < time.Duration(frames/2)*jitter { + t.Fatalf("relative scheme should accumulate drift, got only %v", relMax) + } + t.Logf("after %d frames: absolute max error %v, relative drift %v", frames, absMax, relMax) +} + +func absErr(a, b time.Duration) time.Duration { + if a < b { + return b - a + } + return a - b +} diff --git a/pkg/widgets/mapviewer/mapviewer.go b/pkg/widgets/mapviewer/mapviewer.go index ad736c9e..0720f521 100644 --- a/pkg/widgets/mapviewer/mapviewer.go +++ b/pkg/widgets/mapviewer/mapviewer.go @@ -710,7 +710,7 @@ func (r *mapViewerRenderer) Destroy() { */ func calculateTextSize(widthFactor, heightFactor float32) float32 { - cellSize := fyne.Min(widthFactor, heightFactor) + cellSize := min(widthFactor, heightFactor) // Scale text size relative to cell size, but with a more conservative ratio // Reduced from 0.6 to 0.4 to prevent overflow diff --git a/pkg/widgets/msglist/msglist.go b/pkg/widgets/msglist/msglist.go index 658cd7df..befdeccb 100644 --- a/pkg/widgets/msglist/msglist.go +++ b/pkg/widgets/msglist/msglist.go @@ -2,7 +2,6 @@ package msglist import ( "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/widget" ) @@ -12,73 +11,50 @@ var _ fyne.Widget = (*MsgList)(nil) type MsgList struct { widget.BaseWidget msgs binding.StringList - output *widget.List + list *widget.List listener binding.DataListener } func New(data binding.StringList) *MsgList { - m := &MsgList{ - msgs: data, - } + m := &MsgList{msgs: data} m.ExtendBaseWidget(m) - m.output = widget.NewListWithData( - m.msgs, + m.list = widget.NewListWithData( + data, func() fyne.CanvasObject { - w := widget.NewLabel("") - w.Alignment = fyne.TextAlignLeading - w.Selectable = true - return w + l := widget.NewLabel("") + l.Selectable = true + return l }, func(item binding.DataItem, obj fyne.CanvasObject) { - i := item.(binding.String) - txt, err := i.Get() + txt, err := item.(binding.String).Get() if err != nil { - fyne.LogError("Failed to get string", err) + fyne.LogError("msglist: get string", err) return } obj.(*widget.Label).SetText(txt) }, ) - m.listener = binding.NewDataListener(func() { - m.output.ScrollToBottom() - }) + m.listener = binding.NewDataListener(m.list.ScrollToBottom) return m } func (m *MsgList) CreateRenderer() fyne.WidgetRenderer { m.msgs.AddListener(m.listener) - return &msgListRenderer{ - m: m, - container: container.NewScroll(m.output), - } + return &msgListRenderer{m: m, objects: []fyne.CanvasObject{m.list}} } var _ fyne.WidgetRenderer = (*msgListRenderer)(nil) type msgListRenderer struct { - m *MsgList - container *container.Scroll + m *MsgList + objects []fyne.CanvasObject } -func (r *msgListRenderer) MinSize() fyne.Size { - return fyne.NewSize(300, 100) -} - -func (r *msgListRenderer) Layout(size fyne.Size) { - r.container.Resize(size) -} - -func (r *msgListRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{r.container} -} - -func (r *msgListRenderer) Refresh() { - -} - -func (r *msgListRenderer) Destroy() { - r.m.msgs.RemoveListener(r.m.listener) -} +func (r *msgListRenderer) MinSize() fyne.Size { return fyne.NewSize(300, 200) } +func (r *msgListRenderer) Layout(size fyne.Size) { r.m.list.Resize(size) } +func (r *msgListRenderer) Objects() []fyne.CanvasObject { return r.objects } +func (r *msgListRenderer) Refresh() { r.m.list.Refresh() } +func (r *msgListRenderer) Destroy() { r.m.msgs.RemoveListener(r.m.listener) } diff --git a/pkg/widgets/multiwindow/arrange.go b/pkg/widgets/multiwindow/arrange.go index 9e6d517f..f43565f7 100644 --- a/pkg/widgets/multiwindow/arrange.go +++ b/pkg/widgets/multiwindow/arrange.go @@ -71,7 +71,7 @@ func (f *FloatingArranger) Layout(maxSize fyne.Size, confined bool, windows []*I return } - maxSteps := int(fyne.Min( + maxSteps := int(min( (maxSize.Width-initOffset)/20, (maxSize.Height-initOffset)/20, )) @@ -90,14 +90,14 @@ func (f *FloatingArranger) Layout(maxSize fyne.Size, confined bool, windows []*I // Clamp positions to keep windows within bounds maxX := maxSize.Width - window.MinSize().Width maxY := maxSize.Height - window.MinSize().Height - posX = fyne.Min(posX, maxX) - posY = fyne.Min(posY, maxY) + posX = min(posX, maxX) + posY = min(posY, maxY) } pos := fyne.NewPos(posX, posY) size := fyne.NewSize( - fyne.Max(maxSize.Width/2, window.MinSize().Width), - fyne.Max(maxSize.Height/2, window.MinSize().Height), + max(maxSize.Width/2, window.MinSize().Width), + max(maxSize.Height/2, window.MinSize().Height), ) f.setWindowState(window, pos, size, false) @@ -177,7 +177,7 @@ func (p *PackArranger) expandWindows(spaces []windowSpace, maxSize fyne.Size) { if node.pos.Y < otherNode.pos.Y+otherNode.size.Height && node.pos.Y+node.size.Height > otherNode.pos.Y { if otherNode.pos.X > node.pos.X { - maxWidth = fyne.Min(maxWidth, otherNode.pos.X-node.pos.X-padding) + maxWidth = min(maxWidth, otherNode.pos.X-node.pos.X-padding) } } @@ -185,14 +185,14 @@ func (p *PackArranger) expandWindows(spaces []windowSpace, maxSize fyne.Size) { if node.pos.X < otherNode.pos.X+otherNode.size.Width && node.pos.X+node.size.Width > otherNode.pos.X { if otherNode.pos.Y > node.pos.Y { - maxHeight = fyne.Min(maxHeight, otherNode.pos.Y-node.pos.Y-padding) + maxHeight = min(maxHeight, otherNode.pos.Y-node.pos.Y-padding) } } } // Ensure we don't exceed the container bounds - maxWidth = fyne.Min(maxWidth, maxSize.Width-node.pos.X-padding) - maxHeight = fyne.Min(maxHeight, maxSize.Height-node.pos.Y-padding) + maxWidth = min(maxWidth, maxSize.Width-node.pos.X-padding) + maxHeight = min(maxHeight, maxSize.Height-node.pos.Y-padding) // Calculate expanded size while maintaining aspect ratio minSize := window.MinSize() @@ -302,8 +302,8 @@ func (p *PreservingArranger) Layout(maxSize fyne.Size, confined bool, windows [] // Ensure window stays within bounds maxWidth := maxSize.Width - r.pos.X maxHeight := maxSize.Height - r.pos.Y - newSize.Width = fyne.Min(newSize.Width, maxWidth) - newSize.Height = fyne.Min(newSize.Height, maxHeight) + newSize.Width = min(newSize.Width, maxWidth) + newSize.Height = min(newSize.Height, maxHeight) } p.setWindowState(r.window, r.pos, newSize, false) } diff --git a/pkg/widgets/multiwindow/innerwindow.go b/pkg/widgets/multiwindow/innerwindow.go index 559c53c4..93c68414 100644 --- a/pkg/widgets/multiwindow/innerwindow.go +++ b/pkg/widgets/multiwindow/innerwindow.go @@ -67,7 +67,7 @@ type InnerWindow struct { Persist bool // Persist through layout changes IgnoreSave bool // Ignore saving to layout - //minBtn, maxBtn, closeBtn *borderButton + // minBtn, maxBtn, closeBtn *borderButton title string bg *canvas.Rectangle @@ -225,12 +225,12 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { if isLeading { // Left side (darwin default or explicit left alignment) buttons = container.NewHBox(close, min, max) - //bar = container.NewBorder(nil, nil, buttons, borderIcon, title) + // bar = container.NewBorder(nil, nil, buttons, borderIcon, title) bar = container.NewBorder(nil, nil, buttons, borderIcon, container.New(layout.NewCustomPaddedLayout(topPad, 0, 0, 0), title)) } else { // Right side (Windows/Linux default and explicit right alignment) buttons = container.NewHBox(min, max, close) - //bar = container.NewBorder(nil, nil, borderIcon, buttons, title) + // bar = container.NewBorder(nil, nil, borderIcon, buttons, title) bar = container.NewBorder(nil, nil, borderIcon, buttons, container.New(layout.NewCustomPaddedLayout(topPad, 0, 0, 0), title)) } @@ -275,7 +275,8 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { leftBottomCorner: leftBottomCorner, rightBottomCorner: rightBottomCorner, borders: borders, - contentBG: contentBG} + contentBG: contentBG, + } r.Layout(w.Size()) return r } @@ -420,7 +421,7 @@ func (i *innerWindowRenderer) MinSize() fyne.Size { return fyne.NewSize(minimizedWidth, barHeight+pad) } contentMin := i.win.content.MinSize() - innerWidth := fyne.Max(i.bar.MinSize().Width, contentMin.Width) + innerWidth := max(i.bar.MinSize().Width, contentMin.Width) return fyne.NewSize(innerWidth+pad*2, contentMin.Height+pad+barHeight) } @@ -442,9 +443,11 @@ func (i *innerWindowRenderer) Refresh() { i.ShadowingRenderer.RefreshShadow() } -var _ desktop.Mouseable = (*draggableLabel)(nil) -var _ fyne.Draggable = (*draggableLabel)(nil) -var _ fyne.Focusable = (*draggableLabel)(nil) +var ( + _ desktop.Mouseable = (*draggableLabel)(nil) + _ fyne.Draggable = (*draggableLabel)(nil) + _ fyne.Focusable = (*draggableLabel)(nil) +) type draggableLabel struct { widget.Label @@ -633,7 +636,7 @@ func (b *buttonTheme) Size(n fyne.ThemeSizeName) float32 { //n = theme.SizeNameWindowButtonRadius return 4 case theme.SizeNameInlineIcon: - //n = theme.SizeNameWindowButtonIcon + // n = theme.SizeNameWindowButtonIcon return 20 } diff --git a/pkg/widgets/multiwindow/multiplewindows.go b/pkg/widgets/multiwindow/multiplewindows.go index fcbcd803..109d97fd 100644 --- a/pkg/widgets/multiwindow/multiplewindows.go +++ b/pkg/widgets/multiwindow/multiplewindows.go @@ -260,7 +260,7 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { case resizeUp: actualDY := ev.Dragged.DY if actualDY > 0 { - actualDY = fyne.Min(actualDY, currentSize.Height-minSize.Height) + actualDY = min(actualDY, currentSize.Height-minSize.Height) } else if w.Position().Y+actualDY < 0 { actualDY = -w.Position().Y } @@ -272,7 +272,7 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { actualDX := ev.Dragged.DX if actualDX > 0 { // When shrinking (dragging right), limit by remaining width - actualDX = fyne.Min(actualDX, currentSize.Width-minSize.Width) + actualDX = min(actualDX, currentSize.Width-minSize.Width) } else if w.Position().X+actualDX < 0 { // Prevent dragging past left edge actualDX = -w.Position().X @@ -284,14 +284,14 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { case resizeUpLeft: actualDY := ev.Dragged.DY if actualDY > 0 { - actualDY = fyne.Min(actualDY, currentSize.Height-minSize.Height) + actualDY = min(actualDY, currentSize.Height-minSize.Height) } else if w.Position().Y+actualDY < 0 { actualDY = -w.Position().Y } actualDX := ev.Dragged.DX if actualDX > 0 { // When shrinking (dragging right), limit by remaining width - actualDX = fyne.Min(actualDX, currentSize.Width-minSize.Width) + actualDX = min(actualDX, currentSize.Width-minSize.Width) } else if w.Position().X+actualDX < 0 { // Prevent dragging past left edge actualDX = -w.Position().X @@ -301,7 +301,7 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { case resizeUpRight: actualDY := ev.Dragged.DY if actualDY > 0 { - actualDY = fyne.Min(actualDY, currentSize.Height-minSize.Height) + actualDY = min(actualDY, currentSize.Height-minSize.Height) } else if w.Position().Y+actualDY < 0 { actualDY = -w.Position().Y } @@ -311,7 +311,7 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { actualDX := ev.Dragged.DX if actualDX > 0 { // When shrinking (dragging right), limit by remaining width - actualDX = fyne.Min(actualDX, currentSize.Width-minSize.Width) + actualDX = min(actualDX, currentSize.Width-minSize.Width) } else if w.Position().X+actualDX < 0 { // Prevent dragging past left edge actualDX = -w.Position().X @@ -328,8 +328,8 @@ func (m *MultipleWindows) setupChild(w *InnerWindow) { pos := w.Position() maxWidth := contentSize.Width - pos.X maxHeight := contentSize.Height - pos.Y - newSize.Width = fyne.Min(newSize.Width, maxWidth) - newSize.Height = fyne.Min(newSize.Height, maxHeight) + newSize.Width = min(newSize.Width, maxWidth) + newSize.Height = min(newSize.Height, maxHeight) } w.Resize(newSize.Max(minSize)) diff --git a/pkg/widgets/symbollist/symbollist.go b/pkg/widgets/symbollist/symbollist.go index a29b849d..071358ec 100644 --- a/pkg/widgets/symbollist/symbollist.go +++ b/pkg/widgets/symbollist/symbollist.go @@ -386,7 +386,10 @@ func (sw *SymbolWidgetEntry) SetCorrectionFactor(f float64) { */ func (sw *SymbolWidgetEntry) CreateRenderer() fyne.WidgetRenderer { - return &symbolWidgetEntryRenderer{e: sw} + return &symbolWidgetEntryRenderer{ + e: sw, + objects: []fyne.CanvasObject{sw.container}, + } // return widget.NewSimpleRenderer(sw.container) } @@ -416,13 +419,9 @@ func (s *symbolWidgetEntryRenderer) Refresh() { col.A = barAlpha s.e.valueBar.FillColor = col s.e.valueBar.StrokeColor = col - // cascades to the labels, delete button and value bar s.e.container.Refresh() } func (s *symbolWidgetEntryRenderer) Objects() []fyne.CanvasObject { - if s.objects == nil { - s.objects = []fyne.CanvasObject{s.e.container} - } return s.objects } diff --git a/pkg/widgets/secrettext/secrettext.go b/pkg/widgets/tappabletext/tappabletext.go similarity index 65% rename from pkg/widgets/secrettext/secrettext.go rename to pkg/widgets/tappabletext/tappabletext.go index 40ef47b9..b6a4c923 100644 --- a/pkg/widgets/secrettext/secrettext.go +++ b/pkg/widgets/tappabletext/tappabletext.go @@ -1,12 +1,10 @@ -package secrettext +package tappabletext import ( "bytes" - "sync" "fyne.io/fyne/v2" "fyne.io/fyne/v2/dialog" - "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/widget" "github.com/hajimehoshi/go-mp3" "github.com/roffe/txlogger/pkg/assets" @@ -19,32 +17,26 @@ var _ fyne.Tappable = (*SecretText)(nil) type SecretText struct { *widget.Label tappedTimes int - SecretFunc func() - initOnce sync.Once + Taps int + Func func() } -func New(text string) *SecretText { +func New(text string, taps int, fn func()) *SecretText { label := widget.NewLabel(text) return &SecretText{ Label: label, + Taps: taps, + Func: fn, } } func (s *SecretText) Tapped(*fyne.PointEvent) { s.tappedTimes++ // log.Println("tapped", s.tappedTimes) - if s.tappedTimes >= 10 { - - /* - t := fyne.NewStaticResource("taz.png", assets.Taz) - cv := canvas.NewImageFromResource(t) - cv.ScaleMode = canvas.ImageScaleFastest - cv.SetMinSize(fyne.NewSize(0, 0)) - */ + if s.tappedTimes >= s.Taps { + s.tappedTimes = 0 fileBytesReader := bytes.NewReader(assets.Korvring) - - // Decode file decodedMp3, err := mp3.NewDecoder(fileBytesReader) if err != nil { panic("mp3.NewDecoder failed: " + err.Error()) @@ -54,8 +46,7 @@ func (s *SecretText) Tapped(*fyne.PointEvent) { player.Play() - s.tappedTimes = 0 - if f := s.SecretFunc; f != nil { + if f := s.Func; f != nil { f() } @@ -82,16 +73,9 @@ func (s *SecretText) Tapped(*fyne.PointEvent) { player.Pause() }) d.Show() - /* - an := canvas.NewSizeAnimation(fyne.NewSize(0, 0), fyne.NewSize(370, 386), time.Second, func(size fyne.Size) { - cv.Resize(size) - }) - - an.Start() - */ } } -func (s *SecretText) Cursor() desktop.Cursor { - return desktop.CrosshairCursor -} +//func (s *SecretText) Cursor() desktop.Cursor { +// return desktop.CrosshairCursor +//} diff --git a/pkg/widgets/vbar/vbar.go b/pkg/widgets/vbar/vbar.go index 93bb2057..d2fd3827 100644 --- a/pkg/widgets/vbar/vbar.go +++ b/pkg/widgets/vbar/vbar.go @@ -134,10 +134,6 @@ func (s *VBar) SetValue(value float64) { } } -func (s *VBar) SetValue2(value float64) { - s.SetValue(value) -} - func (s *VBar) Value() float64 { return s.value } diff --git a/pkg/windows/help.go b/pkg/windows/help.go index 30ec9712..d555109b 100644 --- a/pkg/windows/help.go +++ b/pkg/windows/help.go @@ -13,6 +13,7 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/roffe/txlogger/pkg/assets" + "github.com/roffe/txlogger/pkg/widgets/tappabletext" ) func Help() *container.AppTabs { @@ -117,7 +118,7 @@ func Help() *container.AppTabs { return tabs } -func About() *fyne.Container { +func (mw *MainWindow) about() *fyne.Container { kvaserLogo := canvas.NewImageFromResource(fyne.NewStaticResource("kvaser_logo.png", assets.KvaserLogoBytes)) kvaserLogo.SetMinSize(fyne.NewSize(190, 117)) kvaserLogo.FillMode = canvas.ImageFillContain @@ -149,6 +150,12 @@ func About() *fyne.Container { thnx := widget.NewLabel("Special thanks to") thnx.TextStyle.Bold = true + versionString := tappabletext.New("Version: "+fyne.CurrentApp().Metadata().Version+" Build: "+strconv.Itoa(fyne.CurrentApp().Metadata().Build), 10, func() { + mw.app.Preferences().SetBool("enable_preview_features1337", true) + mw.previewFeatures = true + mw.SetMainMenu(mw.GetMenu(mw.selects.ecuSelect.Selected)) + }) + return container.NewBorder( nil, nil, @@ -160,7 +167,7 @@ func About() *fyne.Container { nil, container.NewVBox( widget.NewHyperlink("txlogger.com", tx), - widget.NewLabel("Version: "+fyne.CurrentApp().Metadata().Version+" Build: "+strconv.Itoa(fyne.CurrentApp().Metadata().Build)), + versionString, widget.NewLabel("Author: Joakim \"Roffe\" Karlsson"), ), ), diff --git a/pkg/windows/mainWindow.go b/pkg/windows/mainWindow.go index 91ca4c2f..aa58a35d 100644 --- a/pkg/windows/mainWindow.go +++ b/pkg/windows/mainWindow.go @@ -35,7 +35,6 @@ import ( "github.com/roffe/txlogger/pkg/widgets/ledicon" "github.com/roffe/txlogger/pkg/widgets/logplayer" "github.com/roffe/txlogger/pkg/widgets/multiwindow" - "github.com/roffe/txlogger/pkg/widgets/secrettext" "github.com/roffe/txlogger/pkg/widgets/settings" "github.com/roffe/txlogger/pkg/widgets/symbollist" "google.golang.org/protobuf/types/known/emptypb" @@ -80,7 +79,7 @@ type MainWindow struct { gwclient proto.GocanClient buttonsDisabled bool settings *settings.Widget - statusText *secrettext.SecretText + statusText *widget.Label wm *multiwindow.MultipleWindows content *fyne.Container startup bool @@ -136,12 +135,8 @@ func NewMainWindow(app fyne.App) *MainWindow { gocanGatewayLED: ledicon.New("Gateway"), canLED: ledicon.New("CAN"), - statusText: secrettext.New("Harder, Better, Faster, Stronger"), - previewFeatures: app.Preferences().BoolWithFallback("enable_preview_features", false), - } - - mw.statusText.SecretFunc = func() { - mw.app.Preferences().SetBool("enable_preview_features", true) + statusText: widget.NewLabel("Harder, Better, Faster, Stronger"), + previewFeatures: app.Preferences().BoolWithFallback("enable_preview_features1337", false), } ebus.SubscribeFunc(ebus.TOPIC_COLORBLINDMODE, func(v float64) { diff --git a/pkg/windows/mainWindow_menu.go b/pkg/windows/mainWindow_menu.go index 4a9bb9b3..d0761522 100644 --- a/pkg/windows/mainWindow_menu.go +++ b/pkg/windows/mainWindow_menu.go @@ -100,7 +100,7 @@ func (mw *MainWindow) setupMenu() { mw.wm.Raise(w) return } - inner := multiwindow.NewInnerWindow("About", About()) + inner := multiwindow.NewInnerWindow("About", mw.about()) inner.Icon = theme.HelpIcon() mw.wm.Add(inner) }), diff --git a/pkg/windows/mainwindow_layout.go b/pkg/windows/mainwindow_layout.go index ff43213d..8841057e 100644 --- a/pkg/windows/mainwindow_layout.go +++ b/pkg/windows/mainwindow_layout.go @@ -58,12 +58,13 @@ func (mw *MainWindow) SaveLayout() error { return nil } + func writeLayout(name string, data []byte) error { layoutPath, err := common.GetLayoutPath() if err != nil { return err } - if err := os.WriteFile(filepath.Join(layoutPath, name+".json"), data, 0644); err != nil { + if err := os.WriteFile(filepath.Join(layoutPath, name+".json"), data, 0o644); err != nil { return err } return nil @@ -117,7 +118,6 @@ func (mw *MainWindow) jsonLayout() ([]byte, error) { Preset: mw.selects.presetSelect.Selected, Windows: history, }) - if err != nil { return nil, err } @@ -144,7 +144,7 @@ func (mw *MainWindow) LoadLayout(name string) error { mw.wm.CloseAll() if mw.dlc == nil { - //mw.selects.ecuSelect.SetSelected(layout.ECU) + // mw.selects.ecuSelect.SetSelected(layout.ECU) mw.selects.ecuSelect.Selected = layout.ECU mw.selects.presetSelect.SetSelected(layout.Preset) } @@ -163,16 +163,14 @@ func (mw *MainWindow) LoadLayout(name string) error { } if h.GaugeConfig != nil { - gauge, cancelFuncs, err := gauge.New(h.GaugeConfig) + gauge, cancelFn, err := gauge.New(h.GaugeConfig) if err != nil { mw.Error(fmt.Errorf("failed to create gauge: %w", err)) continue } iw := multiwindow.NewInnerWindow(h.Title, gauge) iw.OnClose = func() { - for _, cancel := range cancelFuncs { - cancel() - } + cancelFn() } mw.wm.Add(iw) continue From 790b36f3cf0a4a5164a5e6908cbb37819a56e579 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 25 Jun 2026 23:17:28 +0200 Subject: [PATCH 089/102] update gocan --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8291d2a1..bc5e146c 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 github.com/roffe/ecusymbol v1.2.4 - github.com/roffe/gocan v1.4.2 + github.com/roffe/gocan v1.4.3 go.bug.st/serial v1.7.1 golang.org/x/image v0.40.0 golang.org/x/mod v0.36.0 diff --git a/go.sum b/go.sum index c7da6d40..823e7e8e 100644 --- a/go.sum +++ b/go.sum @@ -134,8 +134,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/roffe/ecusymbol v1.2.4 h1:5MGAs7e6djTAaa4y8bpByATMXjgq7/khLJqff3t3Zx0= github.com/roffe/ecusymbol v1.2.4/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= -github.com/roffe/gocan v1.4.2 h1:8kx7UjE+akgs3OwRo/t3jaCIl6Bvr3GMqDPuzosc6oE= -github.com/roffe/gocan v1.4.2/go.mod h1:8TjfD7TGUNpAZSPwdtnYRI58ICd2NPAo/gTWZUsne+k= +github.com/roffe/gocan v1.4.3 h1:G7DBUK2tAHDtqRR9KJ3OCJiWoi28gb/jPYtsAFUUa9k= +github.com/roffe/gocan v1.4.3/go.mod h1:8TjfD7TGUNpAZSPwdtnYRI58ICd2NPAo/gTWZUsne+k= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= From 6e6c045f0df44ca9b5ccb3a54bfa545463cd877e Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 25 Jun 2026 23:36:06 +0200 Subject: [PATCH 090/102] work now --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index bc5e146c..c28caf99 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pion/mdns/v2 v2.1.0 github.com/roffe/ecusymbol v1.2.4 - github.com/roffe/gocan v1.4.3 + github.com/roffe/gocan v1.4.4 go.bug.st/serial v1.7.1 golang.org/x/image v0.40.0 golang.org/x/mod v0.36.0 diff --git a/go.sum b/go.sum index 823e7e8e..e2d2ba6b 100644 --- a/go.sum +++ b/go.sum @@ -136,6 +136,8 @@ github.com/roffe/ecusymbol v1.2.4 h1:5MGAs7e6djTAaa4y8bpByATMXjgq7/khLJqff3t3Zx0 github.com/roffe/ecusymbol v1.2.4/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= github.com/roffe/gocan v1.4.3 h1:G7DBUK2tAHDtqRR9KJ3OCJiWoi28gb/jPYtsAFUUa9k= github.com/roffe/gocan v1.4.3/go.mod h1:8TjfD7TGUNpAZSPwdtnYRI58ICd2NPAo/gTWZUsne+k= +github.com/roffe/gocan v1.4.4 h1:8CxnuAzqUyOWqTOk/FZLf20J+DNFyHo9Iar9DdUm7uY= +github.com/roffe/gocan v1.4.4/go.mod h1:8TjfD7TGUNpAZSPwdtnYRI58ICd2NPAo/gTWZUsne+k= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= From 0a2c30e476be4d3496fc162de294ab774428f8c5 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 26 Jun 2026 15:53:52 +0200 Subject: [PATCH 091/102] =?UTF-8?q?varf=C3=B6r=20e=20du=20arg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/windows-release.yml | 6 +++--- .github/workflows/windows.yml | 6 +++--- Makefile | 3 +++ build.ps1 | 21 +++++++++++++++++---- setup_build_env.ps1 | 27 +++++++++++++++++++-------- 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index 48245c7f..af898af0 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -38,12 +38,12 @@ jobs: run: | go get . go install fyne.io/tools/cmd/fyne@latest - .\setup_build_env.ps1 + .\setup_build_env.ps1 -gcc - name: Build run: | - $env:PATH += ";D:\a\txlogger\txlogger\llvm-mingw\bin" - .\build.ps1 -cangateway -usegitsrc -txlogger -setup + $env:PATH = "C:\msys64\mingw64\bin;C:\msys64\mingw32\bin;" + $env:PATH + .\build.ps1 -gcc -cangateway -usegitsrc -txlogger -setup - name: Creating Zip run: 7z a txlogger.zip txlogger.exe cangateway.exe .\debug.bat C:\Progra~2\Kvaser\Canlib\Bin\canlib32.dll .\vcpkg\packages\libusb_x64-windows\bin\libusb-1.0.dll .\canusb\dll64\canusbdrv64.dll diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0ccd16c9..d5d98ed6 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -41,12 +41,12 @@ jobs: run: | go get . go install fyne.io/tools/cmd/fyne@latest - .\setup_build_env.ps1 + .\setup_build_env.ps1 -gcc - name: Build run: | - $env:PATH += ";D:\a\txlogger\txlogger\llvm-mingw\bin" - .\build.ps1 -cangateway -usegitsrc -txlogger -setup + $env:PATH = "C:\msys64\mingw64\bin;C:\msys64\mingw32\bin;" + $env:PATH + .\build.ps1 -gcc -cangateway -usegitsrc -txlogger -setup - name: Creating Zip run: 7z a txlogger.zip txlogger.exe cangateway.exe .\debug.bat C:\Progra~2\Kvaser\Canlib\Bin\canlib32.dll .\vcpkg\packages\libusb_x64-windows\bin\libusb-1.0.dll .\canusb\dll64\canusbdrv64.dll diff --git a/Makefile b/Makefile index c00e5dd2..fbee460e 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,9 @@ debug: clean cangateway @echo Using compiler "$(CC)" -go run -tags=$(BUILDTAGS),debug . 2>&1 | tee run.log +windows: + CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOARCH=386 GOOS=windows go build -tags="j2534" -ldflags '-s -w' -o cangateway.exe ../gocangateway + PKG_CONFIG_PATH="./vcpkg/packages/libusb_x64-windows/lib/pkgconfig" CGO_CFLAGS="-I/vcpkg/packages/libusb_x64-windows/include/libusb-1.0" CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOARCH=amd64 GOOS=windows go build -tags=$(BUILDTAGS) -ldflags '-s -w' -o txlogger.exe . run: clean cangateway @echo Using compiler "$(CC)" diff --git a/build.ps1 b/build.ps1 index 1479b5e0..db017d41 100644 --- a/build.ps1 +++ b/build.ps1 @@ -3,7 +3,8 @@ param( [switch]$txlogger, [switch]$setup, [switch]$release, - [switch]$usegitsrc + [switch]$usegitsrc, + [switch]$gcc ) if (-not ($cangateway -or $txlogger -or $setup -or $release)) { @@ -19,10 +20,14 @@ if ($release) { $setup = $true } -$env:CGO_ENABLED = "1" +$env:CGO_ENABLED = "1" $env:GOGC = "100" -$env:CC = "x86_64-w64-mingw32-clang.exe" -$env:CXX = "x86_64-w64-mingw32-clang++.exe" +# Default: llvm-mingw clang (much faster, multi-target so one compiler handles 386 + amd64). +# -gcc: GCC-based mingw-w64 (slower, but avoids AV false positives; needs separate per-arch compilers, set below). +if (-not $gcc) { + $env:CC = "x86_64-w64-mingw32-clang.exe" + $env:CXX = "x86_64-w64-mingw32-clang++.exe" +} $current_path = Get-Location @@ -39,6 +44,10 @@ if ($cangateway) { # $env:CGO_CFLAGS = ($includes | ForEach-Object { '-I' + $_ }) -join ' ' # $env:CGO_LDFLAGS = ($libs | ForEach-Object { '-L' + $_ }) -join ' ' $env:GOARCH = "386" + if ($gcc) { + $env:CC = "i686-w64-mingw32-gcc.exe" + $env:CXX = "i686-w64-mingw32-g++.exe" + } if ($usegitsrc) { # git clone https://github.com/roffe/gocangateway.git # Set-Location -Path ".\gocangateway" @@ -81,6 +90,10 @@ if ($txlogger) { $env:CGO_CFLAGS = ($includes | ForEach-Object { '-I' + $_ }) -join ' ' # $env:CGO_LDFLAGS = ($libs | ForEach-Object { '-L' + $_ }) -join ' ' $env:GOARCH = "amd64" + if ($gcc) { + $env:CC = "x86_64-w64-mingw32-gcc.exe" + $env:CXX = "x86_64-w64-mingw32-g++.exe" + } fyne package -tags="canlib,canusb,combi,ftdi,j2534,pcan,rcan" --release } diff --git a/setup_build_env.ps1 b/setup_build_env.ps1 index e7bf741f..195ee1b3 100644 --- a/setup_build_env.ps1 +++ b/setup_build_env.ps1 @@ -1,3 +1,7 @@ +param( + [switch]$gcc +) + $temp_dir = ".\setup_temp" $canusb = "https://www.canusb.com/files/canusb_dll_driver.zip" $canlib = "https://pim.kvaser.com/var/assets/Product_Resources/7330130980150/5.51.461/canlib_5_51_461.exe" @@ -27,16 +31,23 @@ Invoke-WebRequest -Uri $canlib -OutFile "$temp_dir\canlib.exe" Write-Output "Extracting CANUSB" Expand-Archive -Path "$temp_dir\canusb_dll_driver.zip" -DestinationPath ".\canusb" -Force -# download llvm-mingw -Write-Output "Downloading llvm-MinGW" -Invoke-WebRequest -Uri $llvm -OutFile "$temp_dir\llvm-mingw.zip" +if ($gcc) { + # GCC-based mingw-w64 (32-bit for cangateway, 64-bit for txlogger). + # Slower than llvm but avoids AV false positives; used by the CI workflows. + Write-Output "Installing mingw-w64 GCC toolchains" + C:\msys64\usr\bin\pacman.exe -S --noconfirm --needed mingw-w64-i686-gcc mingw-w64-x86_64-gcc +} +else { + # llvm-mingw clang: default for local dev, builds an order of magnitude faster + Write-Output "Downloading llvm-MinGW" + Invoke-WebRequest -Uri $llvm -OutFile "$temp_dir\llvm-mingw.zip" -# Write-Output "Extracting llvm-MinGW" -Expand-Archive -Path "$temp_dir\llvm-mingw.zip" -DestinationPath ".\" -Force + Write-Output "Extracting llvm-MinGW" + Expand-Archive -Path "$temp_dir\llvm-mingw.zip" -DestinationPath ".\" -Force -# rename folder llvm-mingw-20251007-ucrt-x86_64 to llvm-mingw -Write-Output "Renaming llvm-MinGW folder" -Rename-Item -Path ".\llvm-mingw-20251216-ucrt-x86_64" -NewName "llvm-mingw" + Write-Output "Renaming llvm-MinGW folder" + Rename-Item -Path ".\llvm-mingw-20251216-ucrt-x86_64" -NewName "llvm-mingw" +} Write-Output "Installing CANLIB" Start-Process -FilePath "$temp_dir\canlib.exe" -ArgumentList "/S" -Wait From 8ab02b19ccb86477f3fae032fb4be8ed7112f203 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 26 Jun 2026 17:11:16 +0200 Subject: [PATCH 092/102] kan du inte cykla eller? --- .github/workflows/windows-release.yml | 6 +++--- .github/workflows/windows.yml | 6 +++--- FyneApp.toml | 2 +- Makefile | 9 ++++++++- build.ps1 | 25 +++++++------------------ pkg/cangw/cangw.go | 5 +++-- pkg/cangw/hidewindow_other.go | 7 +++++++ pkg/cangw/hidewindow_windows.go | 16 ++++++++++++++++ setup_build_env.ps1 | 26 +++++++------------------- 9 files changed, 55 insertions(+), 47 deletions(-) create mode 100644 pkg/cangw/hidewindow_other.go create mode 100644 pkg/cangw/hidewindow_windows.go diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index af898af0..48245c7f 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -38,12 +38,12 @@ jobs: run: | go get . go install fyne.io/tools/cmd/fyne@latest - .\setup_build_env.ps1 -gcc + .\setup_build_env.ps1 - name: Build run: | - $env:PATH = "C:\msys64\mingw64\bin;C:\msys64\mingw32\bin;" + $env:PATH - .\build.ps1 -gcc -cangateway -usegitsrc -txlogger -setup + $env:PATH += ";D:\a\txlogger\txlogger\llvm-mingw\bin" + .\build.ps1 -cangateway -usegitsrc -txlogger -setup - name: Creating Zip run: 7z a txlogger.zip txlogger.exe cangateway.exe .\debug.bat C:\Progra~2\Kvaser\Canlib\Bin\canlib32.dll .\vcpkg\packages\libusb_x64-windows\bin\libusb-1.0.dll .\canusb\dll64\canusbdrv64.dll diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d5d98ed6..0ccd16c9 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -41,12 +41,12 @@ jobs: run: | go get . go install fyne.io/tools/cmd/fyne@latest - .\setup_build_env.ps1 -gcc + .\setup_build_env.ps1 - name: Build run: | - $env:PATH = "C:\msys64\mingw64\bin;C:\msys64\mingw32\bin;" + $env:PATH - .\build.ps1 -gcc -cangateway -usegitsrc -txlogger -setup + $env:PATH += ";D:\a\txlogger\txlogger\llvm-mingw\bin" + .\build.ps1 -cangateway -usegitsrc -txlogger -setup - name: Creating Zip run: 7z a txlogger.zip txlogger.exe cangateway.exe .\debug.bat C:\Progra~2\Kvaser\Canlib\Bin\canlib32.dll .\vcpkg\packages\libusb_x64-windows\bin\libusb-1.0.dll .\canusb\dll64\canusbdrv64.dll diff --git a/FyneApp.toml b/FyneApp.toml index 88569b1a..6eac2e8e 100644 --- a/FyneApp.toml +++ b/FyneApp.toml @@ -5,7 +5,7 @@ Icon = "Icon.png" Name = "txlogger" ID = "com.roffe.txlogger" Version = "2.1.10" -Build = 689 +Build = 690 [Migrations] fyneDo = true diff --git a/Makefile b/Makefile index fbee460e..bd4038db 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,14 @@ debug: clean cangateway windows: CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOARCH=386 GOOS=windows go build -tags="j2534" -ldflags '-s -w' -o cangateway.exe ../gocangateway - PKG_CONFIG_PATH="./vcpkg/packages/libusb_x64-windows/lib/pkgconfig" CGO_CFLAGS="-I/vcpkg/packages/libusb_x64-windows/include/libusb-1.0" CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOARCH=amd64 GOOS=windows go build -tags=$(BUILDTAGS) -ldflags '-s -w' -o txlogger.exe . + CGO_CFLAGS="-Ivcpkg/packages/libusb_x64-windows/include/libusb-1.0" \ + CGO_LDFLAGS="-Lvcpkg/packages/libusb_x64-windows/lib" \ + CGO_ENABLED=1 \ + CC=x86_64-w64-mingw32-gcc \ + GOARCH=amd64 \ + GOOS=windows \ + fyne package -os windows -tags=$(BUILDTAGS) --release +# go build -tags=$(BUILDTAGS) -ldflags '-s -w' -o txlogger.exe . run: clean cangateway @echo Using compiler "$(CC)" diff --git a/build.ps1 b/build.ps1 index db017d41..1b5794ee 100644 --- a/build.ps1 +++ b/build.ps1 @@ -3,8 +3,7 @@ param( [switch]$txlogger, [switch]$setup, [switch]$release, - [switch]$usegitsrc, - [switch]$gcc + [switch]$usegitsrc ) if (-not ($cangateway -or $txlogger -or $setup -or $release)) { @@ -22,12 +21,8 @@ if ($release) { $env:CGO_ENABLED = "1" $env:GOGC = "100" -# Default: llvm-mingw clang (much faster, multi-target so one compiler handles 386 + amd64). -# -gcc: GCC-based mingw-w64 (slower, but avoids AV false positives; needs separate per-arch compilers, set below). -if (-not $gcc) { - $env:CC = "x86_64-w64-mingw32-clang.exe" - $env:CXX = "x86_64-w64-mingw32-clang++.exe" -} +$env:CC = "x86_64-w64-mingw32-clang.exe" +$env:CXX = "x86_64-w64-mingw32-clang++.exe" $current_path = Get-Location @@ -44,22 +39,20 @@ if ($cangateway) { # $env:CGO_CFLAGS = ($includes | ForEach-Object { '-I' + $_ }) -join ' ' # $env:CGO_LDFLAGS = ($libs | ForEach-Object { '-L' + $_ }) -join ' ' $env:GOARCH = "386" - if ($gcc) { - $env:CC = "i686-w64-mingw32-gcc.exe" - $env:CXX = "i686-w64-mingw32-g++.exe" - } if ($usegitsrc) { # git clone https://github.com/roffe/gocangateway.git # Set-Location -Path ".\gocangateway" # go build -tags="canlib,j2534" -ldflags '-s -w -H=windowsgui' -o cangateway.exe . # Move-Item -Path ".\cangateway.exe" -Destination "$current_path\cangateway.exe" -Force # Set-Location -Path $current_path - go install -tags="j2534" -ldflags '-s -w -H=windowsgui' github.com/roffe/gocangateway@latest + # console subsystem (no -H=windowsgui): GUI-subsystem console-less exes trip AV heuristics. + # txlogger spawns it with CREATE_NO_WINDOW so no console window shows. See pkg/cangw. + go install -tags="j2534" -ldflags '-s -w' github.com/roffe/gocangateway@latest Move-Item -Path "$Env:USERPROFILE\go\bin\windows_386\gocangateway.exe" -Destination "$current_path\cangateway.exe" -Force } else { #Set-Location -Path "..\gocangateway" - go build -tags="j2534" -ldflags '-s -w -H=windowsgui' -o cangateway.exe ..\gocangateway + go build -tags="j2534" -ldflags '-s -w' -o cangateway.exe ..\gocangateway #Move-Item -Path ".\cangateway.exe" -Destination "$current_path\cangateway.exe" -Force #Set-Location -Path $current_path } @@ -90,10 +83,6 @@ if ($txlogger) { $env:CGO_CFLAGS = ($includes | ForEach-Object { '-I' + $_ }) -join ' ' # $env:CGO_LDFLAGS = ($libs | ForEach-Object { '-L' + $_ }) -join ' ' $env:GOARCH = "amd64" - if ($gcc) { - $env:CC = "x86_64-w64-mingw32-gcc.exe" - $env:CXX = "x86_64-w64-mingw32-g++.exe" - } fyne package -tags="canlib,canusb,combi,ftdi,j2534,pcan,rcan" --release } diff --git a/pkg/cangw/cangw.go b/pkg/cangw/cangw.go index b0e86577..a6107ba5 100644 --- a/pkg/cangw/cangw.go +++ b/pkg/cangw/cangw.go @@ -32,8 +32,9 @@ func Start() (*os.Process, error) { } cmd := exec.Command(filepath.Join(wd, exeName)) - // Uncomment on Windows if you want to hide the console window: - // cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + // cangateway is built as a console exe (GUI-subsystem console-less binaries + // trip AV heuristics); hide the console window at launch instead. + hideWindow(cmd) stderr, err := cmd.StderrPipe() if err != nil { diff --git a/pkg/cangw/hidewindow_other.go b/pkg/cangw/hidewindow_other.go new file mode 100644 index 00000000..4cec078a --- /dev/null +++ b/pkg/cangw/hidewindow_other.go @@ -0,0 +1,7 @@ +//go:build !windows + +package cangw + +import "os/exec" + +func hideWindow(*exec.Cmd) {} diff --git a/pkg/cangw/hidewindow_windows.go b/pkg/cangw/hidewindow_windows.go new file mode 100644 index 00000000..e4f44dc1 --- /dev/null +++ b/pkg/cangw/hidewindow_windows.go @@ -0,0 +1,16 @@ +//go:build windows + +package cangw + +import ( + "os/exec" + "syscall" +) + +// hideWindow runs the console child without spawning a console window, so +// cangateway can be a console-subsystem exe (no -H=windowsgui, which trips AV +// heuristics) while staying invisible to the user. +func hideWindow(cmd *exec.Cmd) { + const createNoWindow = 0x08000000 // CREATE_NO_WINDOW + cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNoWindow} +} diff --git a/setup_build_env.ps1 b/setup_build_env.ps1 index 195ee1b3..add4034f 100644 --- a/setup_build_env.ps1 +++ b/setup_build_env.ps1 @@ -1,7 +1,3 @@ -param( - [switch]$gcc -) - $temp_dir = ".\setup_temp" $canusb = "https://www.canusb.com/files/canusb_dll_driver.zip" $canlib = "https://pim.kvaser.com/var/assets/Product_Resources/7330130980150/5.51.461/canlib_5_51_461.exe" @@ -31,23 +27,15 @@ Invoke-WebRequest -Uri $canlib -OutFile "$temp_dir\canlib.exe" Write-Output "Extracting CANUSB" Expand-Archive -Path "$temp_dir\canusb_dll_driver.zip" -DestinationPath ".\canusb" -Force -if ($gcc) { - # GCC-based mingw-w64 (32-bit for cangateway, 64-bit for txlogger). - # Slower than llvm but avoids AV false positives; used by the CI workflows. - Write-Output "Installing mingw-w64 GCC toolchains" - C:\msys64\usr\bin\pacman.exe -S --noconfirm --needed mingw-w64-i686-gcc mingw-w64-x86_64-gcc -} -else { - # llvm-mingw clang: default for local dev, builds an order of magnitude faster - Write-Output "Downloading llvm-MinGW" - Invoke-WebRequest -Uri $llvm -OutFile "$temp_dir\llvm-mingw.zip" +# download llvm-mingw +Write-Output "Downloading llvm-MinGW" +Invoke-WebRequest -Uri $llvm -OutFile "$temp_dir\llvm-mingw.zip" - Write-Output "Extracting llvm-MinGW" - Expand-Archive -Path "$temp_dir\llvm-mingw.zip" -DestinationPath ".\" -Force +Write-Output "Extracting llvm-MinGW" +Expand-Archive -Path "$temp_dir\llvm-mingw.zip" -DestinationPath ".\" -Force - Write-Output "Renaming llvm-MinGW folder" - Rename-Item -Path ".\llvm-mingw-20251216-ucrt-x86_64" -NewName "llvm-mingw" -} +Write-Output "Renaming llvm-MinGW folder" +Rename-Item -Path ".\llvm-mingw-20251216-ucrt-x86_64" -NewName "llvm-mingw" Write-Output "Installing CANLIB" Start-Process -FilePath "$temp_dir\canlib.exe" -ArgumentList "/S" -Wait From 59009afc649483019d2f4fd28547a3ad5a27a9e2 Mon Sep 17 00:00:00 2001 From: roffe Date: Sun, 28 Jun 2026 20:58:00 +0200 Subject: [PATCH 093/102] tons of stuff again --- FyneApp.toml | 2 +- Makefile | 5 +- pkg/mdns/mdns.go => experiments/mdns/mdns.old | 0 go.mod | 15 ++-- go.sum | 30 +++---- pkg/datalogger/t7logger.go | 3 + pkg/datalogger/txbridgelogger.go | 27 +++++++ pkg/datalogger/txbridgelogger_t5.go | 10 +++ pkg/datalogger/txbridgelogger_t7.go | 7 ++ pkg/datalogger/txbridgelogger_t8.go | 7 ++ pkg/ota/firmware.bin | Bin 845376 -> 1009984 bytes pkg/ota/ota.go | 16 +--- pkg/txbridge/client.go | 76 +++++++++++------- pkg/txbridge/client_test.go | 19 +++++ pkg/widgets/plotter/plotter.go | 4 +- pkg/widgets/settings/adapter.go | 4 +- pkg/widgets/settings/controls.go | 12 +-- pkg/widgets/settings/settings.go | 5 +- pkg/widgets/txconfigurator/txconfigurator.go | 30 ++----- pkg/windows/mainmenu_t7.go | 1 + txconfigurator/main.go | 6 +- 21 files changed, 170 insertions(+), 109 deletions(-) rename pkg/mdns/mdns.go => experiments/mdns/mdns.old (100%) create mode 100644 pkg/txbridge/client_test.go diff --git a/FyneApp.toml b/FyneApp.toml index 6eac2e8e..2c0705fe 100644 --- a/FyneApp.toml +++ b/FyneApp.toml @@ -5,7 +5,7 @@ Icon = "Icon.png" Name = "txlogger" ID = "com.roffe.txlogger" Version = "2.1.10" -Build = 690 +Build = 691 [Migrations] fyneDo = true diff --git a/Makefile b/Makefile index bd4038db..f6eb5f34 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,9 @@ endif default: txlogger +pkg/ota/firmware.bin: /home/roffe/Documents/PlatformIO/Projects/txbridge/.pio/build/esp32dev/firmware.bin + @cp $< $@ + cangateway: go build -tags="j2534" -ldflags '-s -w' -o cangateway ../gocangateway @@ -35,7 +38,7 @@ windows: fyne package -os windows -tags=$(BUILDTAGS) --release # go build -tags=$(BUILDTAGS) -ldflags '-s -w' -o txlogger.exe . -run: clean cangateway +run: clean cangateway pkg/ota/firmware.bin @echo Using compiler "$(CC)" -GOEXPERIMENT=simd go run -tags=$(BUILDTAGS) . 2>&1 | tee run.log diff --git a/pkg/mdns/mdns.go b/experiments/mdns/mdns.old similarity index 100% rename from pkg/mdns/mdns.go rename to experiments/mdns/mdns.old diff --git a/go.mod b/go.mod index c28caf99..2f211f9b 100644 --- a/go.mod +++ b/go.mod @@ -10,18 +10,17 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.7.5-0.20260622115008-9d9a9461ca01 + fyne.io/fyne/v2 v2.7.5-0.20260627204512-898abc2d3d41 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/pion/mdns/v2 v2.1.0 - github.com/roffe/ecusymbol v1.2.4 - github.com/roffe/gocan v1.4.4 + github.com/roffe/ecusymbol v1.2.5 + github.com/roffe/gocan v1.4.5 go.bug.st/serial v1.7.1 golang.org/x/image v0.40.0 golang.org/x/mod v0.36.0 - golang.org/x/net v0.54.0 + golang.org/x/net v0.54.0 // indirect golang.org/x/sync v0.20.0 google.golang.org/grpc v1.79.1 // indirect google.golang.org/protobuf v1.36.11 @@ -48,16 +47,17 @@ require ( github.com/fredbi/uri v1.1.1 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fyne-io/gl-js v0.2.1-0.20260315212741-029c47fd27e8 // indirect - github.com/fyne-io/glfw-js v0.3.0 // indirect + github.com/fyne-io/glfw-js v0.4.0 // indirect github.com/fyne-io/image v0.1.1 // indirect github.com/fyne-io/oksvg v0.2.0 // indirect github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect + github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260627172858-eb9c312d9d47 // indirect github.com/go-text/render v0.2.1 // indirect github.com/go-text/typesetting v0.3.4 // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/gousb v1.1.3 // indirect + github.com/gotmc/libusb/v2 v2.6.0 // indirect github.com/hack-pad/go-indexeddb v0.3.2 // indirect github.com/hack-pad/safejs v0.1.1 // indirect github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect @@ -69,7 +69,6 @@ require ( github.com/mdlayher/netlink v1.8.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect - github.com/pion/logging v0.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect diff --git a/go.sum b/go.sum index e2d2ba6b..ce31fc14 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -fyne.io/fyne/v2 v2.7.5-0.20260622115008-9d9a9461ca01 h1:G0GqajRHUPTm6LTMvLdklI1EEqY7c6Vh6YLNWiMEUCQ= -fyne.io/fyne/v2 v2.7.5-0.20260622115008-9d9a9461ca01/go.mod h1:+QHmxyt889RWLBt6HjSY04BmnO+IUQClMPkRVKltTyY= +fyne.io/fyne/v2 v2.7.5-0.20260627204512-898abc2d3d41 h1:cLKNpbITUjaxNU8RFIVWzlUCRwO3s6PD8i1o4sUuM1Y= +fyne.io/fyne/v2 v2.7.5-0.20260627204512-898abc2d3d41/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= @@ -43,16 +43,16 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fyne-io/gl-js v0.2.1-0.20260315212741-029c47fd27e8 h1:0kdPD/GEntpWmZEK5Zu/xE6Tr37jYCVDf9QP8lA/QK8= github.com/fyne-io/gl-js v0.2.1-0.20260315212741-029c47fd27e8/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= -github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk= -github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= +github.com/fyne-io/glfw-js v0.4.0 h1:I9hREBeFyI10cNIqbMKYb1PRidyPDgwob8o2la9SfQo= +github.com/fyne-io/glfw-js v0.4.0/go.mod h1:SDchsFZh4n7nVuBoiowOhOgIBdz+qUQVeC1w9fe2yVU= github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8= github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc= +github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260627172858-eb9c312d9d47 h1:8gV6hg2D33yhLkJQ7E4eHNLMLw/+SmJItBBjkHVikfo= +github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260627172858-eb9c312d9d47/go.mod h1:T5Dn0JwIJOX1euPZ/iT4tq6nFYtmukjcYa7937HuYK8= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -77,6 +77,8 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8 github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gotmc/libusb/v2 v2.6.0 h1:5HRTO1EqchxnWvUIc7l3YtZN8NewWIiQtpSYvUvKu6w= +github.com/gotmc/libusb/v2 v2.6.0/go.mod h1:rU0mvps+snf/CHvEkISbxrwUlWpt6VluOkjqpKo6TFw= github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8= @@ -117,12 +119,6 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ= github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= -github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= -github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= -github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A= -github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= -github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= @@ -132,12 +128,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/roffe/ecusymbol v1.2.4 h1:5MGAs7e6djTAaa4y8bpByATMXjgq7/khLJqff3t3Zx0= -github.com/roffe/ecusymbol v1.2.4/go.mod h1:exejs9+FhPTHhUe+ZKAezRIzjZWFyvrANzF6zZ8h7Y0= -github.com/roffe/gocan v1.4.3 h1:G7DBUK2tAHDtqRR9KJ3OCJiWoi28gb/jPYtsAFUUa9k= -github.com/roffe/gocan v1.4.3/go.mod h1:8TjfD7TGUNpAZSPwdtnYRI58ICd2NPAo/gTWZUsne+k= -github.com/roffe/gocan v1.4.4 h1:8CxnuAzqUyOWqTOk/FZLf20J+DNFyHo9Iar9DdUm7uY= -github.com/roffe/gocan v1.4.4/go.mod h1:8TjfD7TGUNpAZSPwdtnYRI58ICd2NPAo/gTWZUsne+k= +github.com/roffe/ecusymbol v1.2.5 h1:h1ghjJZcm85+n5P+UjJWCiJDXMgy5BUhaFeKgwRJSss= +github.com/roffe/ecusymbol v1.2.5/go.mod h1:Y6vMPbT3P6nVXfUetMZBJKc6N4jPuzpNJfk8bHAfx5Q= +github.com/roffe/gocan v1.4.5 h1:4dLsm9ulGWmpteyEcKWmebnilhaXn3r740DIKAkN/Wg= +github.com/roffe/gocan v1.4.5/go.mod h1:vayI3roc38RKMq5B/yeG+hQt7HUog5Izx8EvpQRrmtg= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= diff --git a/pkg/datalogger/t7logger.go b/pkg/datalogger/t7logger.go index f5ae34b5..c5e3fec4 100644 --- a/pkg/datalogger/t7logger.go +++ b/pkg/datalogger/t7logger.go @@ -386,6 +386,9 @@ func initT7logging(ctx context.Context, kwp *kwp2000.Client, symbols []*symbol.S continue } onMessage("Defining " + sym.Name) + + // log.Println("Define:", sym.String()) + if err := kwp.DynamicallyDefineLocalIdBySymbolNumber(ctx, index, sym.Number); err != nil { return errors.New("failed to define dynamic register") } diff --git a/pkg/datalogger/txbridgelogger.go b/pkg/datalogger/txbridgelogger.go index b3eca75f..cf26dbc0 100644 --- a/pkg/datalogger/txbridgelogger.go +++ b/pkg/datalogger/txbridgelogger.go @@ -8,6 +8,11 @@ import ( "github.com/roffe/gocan" ) +// dataTimeout aborts a txbridge logging session if no log frame arrives for +// this long. The txbridge loggers wait passively on the autonomous stream, so +// without this a dead stream would hang the session forever instead of erroring. +const dataTimeout = 5 * time.Second + var _ IClient = (*TxBridge)(nil) type TxBridge struct { @@ -76,9 +81,31 @@ func (c *TxBridge) setECU(cl *gocan.Client, ecuType string) error { return err } time.Sleep(75 * time.Millisecond) + // Setting the ECU above applies a per-ECU default delay. Override it with the + // configured rate now: delayTime is the firmware's ms between reads = 1000/Hz. + // Framed command: 'D' . + if c.Config.Rate > 0 { + delay := 1000 / c.Config.Rate + if delay < 1 { + delay = 1 + } else if delay > 255 { + delay = 255 + } + if err := cl.Send(gocan.SystemMsg, []byte{'D', 0x01, byte(delay), byte(delay)}, gocan.Outgoing); err != nil { + return err + } + time.Sleep(10 * time.Millisecond) + } return nil } func (c *TxBridge) startLogging(cl *gocan.Client) error { return cl.Send(gocan.SystemMsg, []byte("r"), gocan.Outgoing) } + +// stopLogging tells the dongle to stop its autonomous read loop. Send this before +// ending the ECU session (StopSession / ReturnToNormalMode): otherwise the dongle +// keeps issuing reads against an ended session and work() logs a spurious timeout. +func (c *TxBridge) stopLogging(cl *gocan.Client) error { + return cl.Send(gocan.SystemMsg, []byte("s"), gocan.Outgoing) +} diff --git a/pkg/datalogger/txbridgelogger_t5.go b/pkg/datalogger/txbridgelogger_t5.go index eb936084..4b518588 100644 --- a/pkg/datalogger/txbridgelogger_t5.go +++ b/pkg/datalogger/txbridgelogger_t5.go @@ -49,6 +49,11 @@ func (c *TxBridge) t5(pctx context.Context, cl *gocan.Client) error { go func() { defer cl.Close() + defer func() { + _ = c.stopLogging(cl) // stop the dongle's read loop before closing the connection + time.Sleep(50 * time.Millisecond) + }() + lastData := time.Now() for { select { case <-ctx.Done(): @@ -62,6 +67,10 @@ func (c *TxBridge) t5(pctx context.Context, cl *gocan.Client) error { c.OnMessage("too many errors, aborting logging") return } + if time.Since(lastData) > dataTimeout { + c.OnMessage("no data for 5s, aborting logging") + return + } c.resetPerSecond() case read := <-c.readChan: toRead := min(234, read.Length) @@ -148,6 +157,7 @@ func (c *TxBridge) t5(pctx context.Context, cl *gocan.Client) error { c.OnMessage("txbridge sub closed") return } + lastData = time.Now() if msg.DLC() != (expectedPayloadSize + 4) { c.onError() diff --git a/pkg/datalogger/txbridgelogger_t7.go b/pkg/datalogger/txbridgelogger_t7.go index f3220461..4f8a28f4 100644 --- a/pkg/datalogger/txbridgelogger_t7.go +++ b/pkg/datalogger/txbridgelogger_t7.go @@ -101,9 +101,11 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { go func() { defer cl.Close() defer func() { + _ = c.stopLogging(cl) // stop the dongle's read loop before ending the session _ = kwp.StopSession(ctx) time.Sleep(75 * time.Millisecond) }() + lastData := time.Now() for { select { case <-ctx.Done(): @@ -117,6 +119,10 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { c.OnMessage("too many errors, aborting logging") return } + if time.Since(lastData) > dataTimeout { + c.OnMessage("no data for 5s, aborting logging") + return + } c.resetPerSecond() case read := <-c.readChan: toRead := min(245, read.Length) @@ -203,6 +209,7 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { c.OnMessage("txbridge recv channel closed") return } + lastData = time.Now() if msg.DLC() != int(expectedPayloadSize+4) { c.onError() c.OnMessage(fmt.Sprintf("expected %d bytes, got %d", expectedPayloadSize+4, msg.DLC())) diff --git a/pkg/datalogger/txbridgelogger_t8.go b/pkg/datalogger/txbridgelogger_t8.go index f705be11..7df62416 100644 --- a/pkg/datalogger/txbridgelogger_t8.go +++ b/pkg/datalogger/txbridgelogger_t8.go @@ -62,10 +62,12 @@ func (c *TxBridge) t8(pctx context.Context, cl *gocan.Client) error { adConverter := NewWBLInterpolator(c.WidebandConfig) defer func() { + _ = c.stopLogging(cl) // stop the dongle's read loop before ending the session _ = gm.ReturnToNormalMode(ctx) time.Sleep(100 * time.Millisecond) }() + lastData := time.Now() for { select { case <-ctx.Done(): @@ -79,6 +81,10 @@ func (c *TxBridge) t8(pctx context.Context, cl *gocan.Client) error { c.OnMessage("too many errors, aborting logging") return } + if time.Since(lastData) > dataTimeout { + c.OnMessage("no data for 5s, aborting logging") + return + } c.resetPerSecond() case read := <-c.readChan: if err := c.handleReadTxbridge(ctx, cl, read); err != nil { @@ -94,6 +100,7 @@ func (c *TxBridge) t8(pctx context.Context, cl *gocan.Client) error { c.OnMessage("txbridge recv channel closed") return } + lastData = time.Now() if msg.DLC() != int(expectedPayloadSize+4) { c.OnMessage(fmt.Sprintf("expected %d bytes, got %d", expectedPayloadSize+4, msg.DLC())) return diff --git a/pkg/ota/firmware.bin b/pkg/ota/firmware.bin index a23cca3a1fc0de00e45d133080a509385d1dc411..8710acd47b340c84d4b477bea95fc65d725ccf50 100644 GIT binary patch literal 1009984 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

4EQ|wSMXKv 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:N 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∓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

rhKKg1r>=9?2F5e{1eIL8EV(0y( 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

    v2ri-2kjQ;n~a%drc8~TQt)P?8z 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 meTDtwu4WWhl%_5s3Q(4iUD`oVrQr^~W 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 |uokRg;D?{*XFu<+^xE(5; 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%0Aw7ehR))yvkXxX&62G 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[Y)|PZUNyU;%D%K3F;0-D~rs#S(@o<%)uz+CwHGL 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≷X65MQcIT}LXka}^rD7$Izo@fPeqUy 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 diff --git a/pkg/ota/ota.go b/pkg/ota/ota.go index d666be6b..5c1c7510 100644 --- a/pkg/ota/ota.go +++ b/pkg/ota/ota.go @@ -8,7 +8,6 @@ import ( "io" "log" "net" - "os" "strings" "time" @@ -24,7 +23,7 @@ var firmware []byte const ( COM_SPEED = 1000000 - MinimumtxbridgeVersion = "1.1.3" + MinimumtxbridgeVersion = "1.1.4" ) type Config struct { @@ -57,7 +56,6 @@ func UpdateOTA(cfg Config) error { } else { sp, err = openSerialPort(cfg.Port) } - if err != nil { if sp != nil { sp.Close() @@ -71,7 +69,7 @@ func UpdateOTA(cfg Config) error { // return err //} - //cfg.Logfunc("Firmware size: ", len(firmware)) + // cfg.Logfunc("Firmware size: ", len(firmware)) cmd := serialcommand.NewSerialCommand('v', []byte{0x10}) buf, err := cmd.MarshalBinary() @@ -204,15 +202,6 @@ func openSerialPort(port string) (io.ReadWriteCloser, error) { func openTcpPort(address string) (io.ReadWriteCloser, error) { d := net.Dialer{Timeout: 2 * time.Second} - if address == "" { - address = "192.168.4.1:1337" - } - if value := os.Getenv("TXBRIDGE_ADDRESS"); value != "" { - address = value - } - if !strings.HasSuffix(address, ":1337") { - address += ":1337" // Ensure the port is always set - } p, err := d.Dial("tcp", address) if err != nil { return nil, err @@ -226,7 +215,6 @@ func openTcpPort(address string) (io.ReadWriteCloser, error) { // readSerialCommand reads a single command from the serial port with timeout func readSerialCommand(port io.ReadWriteCloser, timeout time.Duration) (*serialcommand.SerialCommand, error) { - var ( parsingCommand bool command byte diff --git a/pkg/txbridge/client.go b/pkg/txbridge/client.go index e679ff59..9052e74b 100644 --- a/pkg/txbridge/client.go +++ b/pkg/txbridge/client.go @@ -1,28 +1,51 @@ package txbridge import ( - "context" "errors" - "fmt" - "log" "net" - "os" "strings" "time" "github.com/roffe/gocan/pkg/serialcommand" - "github.com/roffe/txlogger/pkg/mdns" ) -var ErrNotConnected = errors.New("not connected") -var ErrNoData = errors.New("no data read") +const DefaultAddress = "192.168.4.1:1337" -func NewClient() *Client { - return &Client{} +var ( + ErrNotConnected = errors.New("not connected") + ErrNoData = errors.New("no data read") +) + +// ResolveAddress returns the host:port to dial for a txbridge connection. +// The host comes from a "tcp://host[:port]" port string; the port is always +// forced to 1337 (appended if missing, overwritten if wrong). Anything else +// falls back to DefaultAddress. +func ResolveAddress(port string) string { + addr, ok := strings.CutPrefix(port, "tcp://") + if !ok || addr == "" { + return DefaultAddress + } + host, _, err := net.SplitHostPort(addr) + if err != nil { + host = addr // no port present + } + return net.JoinHostPort(host, "1337") +} + +func NewClient(address string) *Client { + return &Client{ + address: address, + } +} + +// SetAddress updates the address used by the next Connect call. +func (c *Client) SetAddress(address string) { + c.address = address } type Client struct { - conn net.Conn + conn net.Conn + address string } func (c *Client) Connect() error { @@ -30,28 +53,19 @@ func (c *Client) Connect() error { return nil // Already connected } dialer := net.Dialer{Timeout: 2 * time.Second} + //ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + //defer cancel() + //if src, err := mdns.Query(ctx, "txbridge.local"); err != nil { + // log.Printf("failed to query mDNS: %v", err) + //} else { + // if src.IsValid() { + // address = fmt.Sprintf("%s:%d", src.String(), 1337) + // } else { + // log.Printf("No mDNS response, using address: %s", address) + // } + //} - address := "192.168.4.1:1337" - if value := os.Getenv("TXBRIDGE_ADDRESS"); value != "" { - address = value - } - if !strings.HasSuffix(address, ":1337") { - address += ":1337" // Ensure the port is always set - } - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - if src, err := mdns.Query(ctx, "txbridge.local"); err != nil { - log.Printf("failed to query mDNS: %v", err) - } else { - if src.IsValid() { - address = fmt.Sprintf("%s:%d", src.String(), 1337) - } else { - log.Printf("No mDNS response, using address: %s", address) - } - } - - conn, err := dialer.Dial("tcp", address) + conn, err := dialer.Dial("tcp", c.address) if err != nil { return err } diff --git a/pkg/txbridge/client_test.go b/pkg/txbridge/client_test.go new file mode 100644 index 00000000..f3969f81 --- /dev/null +++ b/pkg/txbridge/client_test.go @@ -0,0 +1,19 @@ +package txbridge + +import "testing" + +func TestResolveAddress(t *testing.T) { + cases := map[string]string{ + "": DefaultAddress, + "COM3": DefaultAddress, // serial port, not tcp + "tcp://": DefaultAddress, + "tcp://10.0.0.5": "10.0.0.5:1337", // port appended + "tcp://10.0.0.5:8080": "10.0.0.5:1337", // wrong port overwritten + "tcp://192.168.4.1:1337": "192.168.4.1:1337", + } + for in, want := range cases { + if got := ResolveAddress(in); got != want { + t.Errorf("ResolveAddress(%q) = %q, want %q", in, got, want) + } + } +} diff --git a/pkg/widgets/plotter/plotter.go b/pkg/widgets/plotter/plotter.go index a4d7dff6..bf0ebf4c 100644 --- a/pkg/widgets/plotter/plotter.go +++ b/pkg/widgets/plotter/plotter.go @@ -412,7 +412,7 @@ func defaultRange(name string) (min, max float64, ok bool) { return 0, 2200, true case "ActualIn.p_AirInlet", "In.p_AirInlet", "ActualIn.p_AirBefThrottle", "In.p_AirBefThrottle": return -1.0, 3.0, true - case "DisplProt.LambdaScanner", "Lambda.ADScanner", "LambdaScan.LambdaScanner", "LambdaScan.LambdaScanner2": + case "DisplProt.LambdaScanner", "Lambda.ADScanner", "LambdaScan.LambdaScanner", "LambdaScan.LambdaScanner2", "Lambda.External": return 0.5, 1.5, true case "IgnProt.fi_Offset": return -30, 10, true @@ -420,8 +420,6 @@ func defaultRange(name string) (min, max float64, ok bool) { return -25, 25, true case "ECMStat.p_Diff": return -1, 2, true - case "Lambda.External": - return 0.5, 1.5, true case "P_medel", "Max_tryck", "Regl_tryck": return -1, 3, true } diff --git a/pkg/widgets/settings/adapter.go b/pkg/widgets/settings/adapter.go index dbf4545f..4e738ddf 100644 --- a/pkg/widgets/settings/adapter.go +++ b/pkg/widgets/settings/adapter.go @@ -2,7 +2,6 @@ package settings import ( "errors" - "fmt" "strconv" "strings" @@ -26,7 +25,7 @@ func (sw *Widget) GetAdapterWithExtraFilters(ecuType string, filters []uint32) ( } port := prefPort.get() - if ad, found := sw.adapters[adapterName]; found && ad.RequiresSerialPort && port == "" { + if ad, found := sw.adapters[adapterName]; found && ad.RequiresSerialPort && port == "" && !ad.SerialPortOptional { return nil, errors.New("Select port in setings") //lint:ignore ST1005 This is ok } @@ -47,7 +46,6 @@ func (sw *Widget) GetAdapterWithExtraFilters(ecuType string, filters []uint32) ( if adapterName == "txbridge wifi" { cfg.AdditionalConfig = map[string]string{ - "address": fmt.Sprintf("%s:%d", "192.168.4.1", 1337), "minversion": ota.MinimumtxbridgeVersion, } } diff --git a/pkg/widgets/settings/controls.go b/pkg/widgets/settings/controls.go index 3be2f7a9..d467c88a 100644 --- a/pkg/widgets/settings/controls.go +++ b/pkg/widgets/settings/controls.go @@ -150,15 +150,17 @@ func (sw *Widget) newAdapterSelector() *widget.Select { }) } -func (sw *Widget) newPortSelector() *widget.Select { - return widget.NewSelect(sw.ListPorts(), func(s string) { +func (sw *Widget) newPortSelector() *widget.SelectEntry { + sel := widget.NewSelectEntry(sw.ListPorts()) + sel.OnChanged = func(s string) { prefPort.set(s) if itm, ok := portCache[s]; ok { sw.portDescription.SetText(itm.SerialNumber) } else { sw.portDescription.SetText("") } - }) + } + return sel } func (sw *Widget) newSpeedSelector() *widget.Select { @@ -167,7 +169,7 @@ func (sw *Widget) newSpeedSelector() *widget.Select { func (sw *Widget) newPortRefreshButton() *widget.Button { return widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { - sw.portSelector.Options = sw.ListPorts() + sw.portSelector.SetOptions(sw.ListPorts()) sw.portSelector.Refresh() }) } @@ -202,7 +204,7 @@ func (sw *Widget) loadPreferences() { sw.colorBlindMode.SetSelected(prefColorBlindMode.get()) sw.adapterSelector.SetSelected(prefAdapter.get()) - sw.portSelector.SetSelected(prefPort.get()) + sw.portSelector.SetText(prefPort.get()) sw.speedSelector.SetSelected(prefSpeed.get()) sw.debugCheckbox.SetChecked(prefDebug.get()) diff --git a/pkg/widgets/settings/settings.go b/pkg/widgets/settings/settings.go index a6536ca8..c3572cd8 100644 --- a/pkg/widgets/settings/settings.go +++ b/pkg/widgets/settings/settings.go @@ -58,7 +58,7 @@ type Widget struct { debugCheckbox *widget.Check adapterSelector *widget.Select refreshBtn *widget.Button - portSelector *widget.Select + portSelector *widget.SelectEntry portDescription *widget.Label speedSelector *widget.Select adapters map[string]*gocan.AdapterInfo @@ -143,7 +143,8 @@ func (sw *Widget) CreateRenderer() fyne.WidgetRenderer { tabs.Append(sw.loggingTab()) tabs.Append(sw.wblTab()) tabs.Append(sw.adScannerTab()) - tabs.Append(container.NewTabItemWithIcon("txbridge", theme.DownloadIcon(), txconfigurator.NewConfigurator())) + + tabs.Append(container.NewTabItemWithIcon("txbridge", theme.DownloadIcon(), txconfigurator.NewConfigurator(prefPort.get))) sw.loadPreferences() return widget.NewSimpleRenderer(tabs) diff --git a/pkg/widgets/txconfigurator/txconfigurator.go b/pkg/widgets/txconfigurator/txconfigurator.go index c5c07c24..09cf5e2c 100644 --- a/pkg/widgets/txconfigurator/txconfigurator.go +++ b/pkg/widgets/txconfigurator/txconfigurator.go @@ -1,10 +1,8 @@ package txconfigurator import ( - "context" "errors" "fmt" - "log" "time" "fyne.io/fyne/v2" @@ -13,7 +11,6 @@ import ( "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - "github.com/roffe/txlogger/pkg/mdns" "github.com/roffe/txlogger/pkg/ota" "github.com/roffe/txlogger/pkg/txbridge" ) @@ -23,7 +20,8 @@ var _ desktop.Mouseable = (*ConfiguratorWidget)(nil) type ConfiguratorWidget struct { widget.BaseWidget - client *txbridge.Client + client *txbridge.Client + getPort func() string apSSIDEntry *widget.Entry apPasswordEntry *widget.Entry @@ -40,9 +38,10 @@ type ConfiguratorWidget struct { container *fyne.Container } -func NewConfigurator() *ConfiguratorWidget { +func NewConfigurator(getPort func() string) *ConfiguratorWidget { t := &ConfiguratorWidget{ - client: txbridge.NewClient(), + client: txbridge.NewClient(""), + getPort: getPort, } t.apChannelSelect = widget.NewSelect([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"}, func(s string) { @@ -66,21 +65,9 @@ func NewConfigurator() *ConfiguratorWidget { t.updateButton.Enable() t.connectButton.Enable() }) - address := "tcp://192.168.4.1:1337" - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - if src, err := mdns.Query(ctx, "txbridge.local"); err != nil { - log.Printf("failed to query mDNS: %v", err) - } else { - if src.IsValid() { - address = fmt.Sprintf("tcp://%s:%d", src.String(), 1337) - } else { - log.Printf("No mDNS response, using address: %s", address) - } - } err := ota.UpdateOTA(ota.Config{ - Port: address, + Port: "tcp://" + txbridge.ResolveAddress(t.getPort()), Logfunc: func(a ...any) { t.statusLabel.SetText(fmt.Sprint(a...)) }, @@ -90,9 +77,8 @@ func NewConfigurator() *ConfiguratorWidget { }) }, }) - if err != nil { - //dialog.ShowError(err, fyne.CurrentApp().Driver().AllWindows()[0]) + // dialog.ShowError(err, fyne.CurrentApp().Driver().AllWindows()[0]) t.statusLabel.SetText("Status: " + err.Error()) return } @@ -167,6 +153,7 @@ func NewConfigurator() *ConfiguratorWidget { } func (t *ConfiguratorWidget) connect() { + t.client.SetAddress(txbridge.ResolveAddress(t.getPort())) err := t.client.Connect() if err != nil { dialog.ShowError(err, fyne.CurrentApp().Driver().AllWindows()[0]) @@ -410,7 +397,6 @@ func (tr *ConfiguratorWidgetRenderer) MinSize() fyne.Size { } func (tr *ConfiguratorWidgetRenderer) Refresh() { - } func (tr *ConfiguratorWidgetRenderer) Objects() []fyne.CanvasObject { diff --git a/pkg/windows/mainmenu_t7.go b/pkg/windows/mainmenu_t7.go index f17e18e8..3e967555 100644 --- a/pkg/windows/mainmenu_t7.go +++ b/pkg/windows/mainmenu_t7.go @@ -71,6 +71,7 @@ func (mw *MainWindow) t7Menu() []MenuItem { {Name: "Enrichment factor during starting E85", Data: "StartCal.EnrFacE85Tab"}, {Name: "Enable cloosed loop regulation", Data: "LambdaCal.ST_Enable"}, {Name: "Common for tuning", Data: "AdpFuelCal.T_AdaptLim|E85Cal.ST_Enable|FCutCal.ST_Enable|LambdaCal.ST_Enable|PurgeCal.ST_PurgeEnable|TorqueCal.M_BrakeLimit"}, + {Name: "Injection end angles", Data: "InjAnglCal.Map"}, }}, {Name: "Ignition", Children: []MenuItem{ {Name: "Ignition map", Data: "IgnNormCal.Map", Region: "LambdaCal.MaxLoadNormTab"}, diff --git a/txconfigurator/main.go b/txconfigurator/main.go index 7c831cca..679e2f8c 100644 --- a/txconfigurator/main.go +++ b/txconfigurator/main.go @@ -15,7 +15,11 @@ func init() { func main() { myApp := app.New() myWindow := myApp.NewWindow("txbridge configurator") - cfg := txconfigurator.NewConfigurator() + + p := func() string { + return "192.168.4.1:1337" + } + cfg := txconfigurator.NewConfigurator(p) myWindow.SetContent(cfg) myWindow.Resize(fyne.NewSize(480, 200)) myWindow.ShowAndRun() From 1d91b452d8e46fa4a2bc292844bcfb2593b6eab4 Mon Sep 17 00:00:00 2001 From: roffe Date: Wed, 1 Jul 2026 11:21:24 +0200 Subject: [PATCH 094/102] save --- go.mod | 2 +- go.sum | 6 +- pkg/datalogger/datalogger.go | 35 ++-- pkg/datalogger/t5fastlogger.go | 241 ++++++++++++++++++++++ pkg/datalogger/txbridgelogger.go | 5 + pkg/datalogger/txbridgelogger_t5.go | 296 ++++++++++++++++++++++++++++ pkg/ota/firmware.bin | Bin 1009984 -> 1011568 bytes pkg/t5can/t5can.go | 3 + pkg/widgets/dtcreader/dtcreader.go | 4 +- pkg/widgets/settings/controls.go | 3 + pkg/widgets/settings/getters.go | 2 + pkg/widgets/settings/prefs.go | 5 +- pkg/widgets/settings/settings.go | 7 + pkg/widgets/settings/tabs.go | 3 + pkg/windows/mainWindow_buttons.go | 9 +- txlogger.code-workspace | 12 +- 16 files changed, 608 insertions(+), 25 deletions(-) create mode 100644 pkg/datalogger/t5fastlogger.go diff --git a/go.mod b/go.mod index 2f211f9b..db327449 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.7.5-0.20260627204512-898abc2d3d41 + fyne.io/fyne/v2 v2.8.0-rc1.0.20260630205042-248c2ef8b9d7 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 diff --git a/go.sum b/go.sum index ce31fc14..9e145a20 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -fyne.io/fyne/v2 v2.7.5-0.20260627204512-898abc2d3d41 h1:cLKNpbITUjaxNU8RFIVWzlUCRwO3s6PD8i1o4sUuM1Y= -fyne.io/fyne/v2 v2.7.5-0.20260627204512-898abc2d3d41/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260629184356-6445a38924b2 h1:yGGQ0eH0TPS/pQJtagPoJQ39wBd5+utJ4cq6VZ4Efi8= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260629184356-6445a38924b2/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260630205042-248c2ef8b9d7 h1:3qwqxIRpR6OAjELfw9Y3dCwn+bCRgouKIIOG1oZryEA= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260630205042-248c2ef8b9d7/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= diff --git a/pkg/datalogger/datalogger.go b/pkg/datalogger/datalogger.go index 402aaf37..edfae974 100644 --- a/pkg/datalogger/datalogger.go +++ b/pkg/datalogger/datalogger.go @@ -7,6 +7,7 @@ import ( symbol "github.com/roffe/ecusymbol" "github.com/roffe/gocan" + "github.com/roffe/txlogger/pkg/debug" ) var ErrToManyErrors = fmt.Errorf("too many errors, aborting logging") @@ -29,19 +30,20 @@ type IClient interface { } type Config struct { - FilenamePrefix string - ECU string - Device gocan.Adapter - Symbols []*symbol.Symbol - Rate int - OnMessage func(string) - CaptureCounter func(int) - ErrorCounter func(int) - FpsCounter func(int) - LogFormat string - LogPath string - WidebandConfig WidebandConfig - RemoteMode int + FilenamePrefix string + ECU string + Device gocan.Adapter + Symbols []*symbol.Symbol + Rate int + OnMessage func(string) + CaptureCounter func(int) + ErrorCounter func(int) + FpsCounter func(int) + LogFormat string + LogPath string + WidebandConfig WidebandConfig + RemoteMode int + ExperimentalT5FastLogging bool } type Client struct { @@ -89,7 +91,12 @@ func New(cfg Config) (IClient, string, error) { switch cfg.ECU { case "T5": - datalogger.IClient, err = NewT5(cfg, lw) + if cfg.ExperimentalT5FastLogging { + debug.Log("Using experimental T5 fast logger") + datalogger.IClient, err = NewT5Fast(cfg, lw) + } else { + datalogger.IClient, err = NewT5(cfg, lw) + } if err != nil { return nil, "", err } diff --git a/pkg/datalogger/t5fastlogger.go b/pkg/datalogger/t5fastlogger.go new file mode 100644 index 00000000..34f52aa0 --- /dev/null +++ b/pkg/datalogger/t5fastlogger.go @@ -0,0 +1,241 @@ +package datalogger + +import ( + "bytes" + "context" + "fmt" + "time" + + symbol "github.com/roffe/ecusymbol" + "github.com/roffe/gocan" + "github.com/roffe/txlogger/pkg/ebus" + "github.com/roffe/txlogger/pkg/t5can" +) + +// --------------------------------------------------------------------------- +// T5 gather fast-logger, generic-CAN-adapter variant. +// +// Same trick as the txbridge gather path (see txbridgelogger_t5.go and +// txbridge/t5_gather.s): upload a tiny CPU32 stub + descriptor table into the +// certified-free SRAM tail, then per cycle call the stub (C1) so it packs all +// logged symbols into one contiguous buffer at 0x7800, and read that back with +// packed 6-byte C7 reads instead of one round-trip per symbol. +// +// On a plain CAN adapter we drive the raw T5 commands ourselves rather than +// letting a dongle do it. +// --------------------------------------------------------------------------- + +// Verified 52-byte CPU32 gather routine (see txbridge/t5_gather.s). Reads the +// descriptor table at 0x7740 and packs the listed symbols into 0x7800. +var t5GatherStub = []byte{ + 0x48, 0xE7, 0xC0, 0xE0, 0x30, 0x7C, 0x77, 0x40, 0x32, 0x7C, 0x78, 0x00, + 0x70, 0x00, 0x10, 0x18, 0x67, 0x1C, 0x53, 0x40, 0x52, 0x88, 0x34, 0x58, + 0x72, 0x00, 0x12, 0x18, 0x52, 0x88, 0x4A, 0x01, 0x67, 0x08, 0x53, 0x41, + 0x12, 0xDA, 0x51, 0xC9, 0xFF, 0xFC, 0x51, 0xC8, 0xFF, 0xEA, 0x4C, 0xDF, + 0x07, 0x03, 0x4E, 0x75, +} + +const ( + t5GatherStubAddr = 0x7700 // stub entry (called via C1 each cycle) + t5GatherBufAddr = 0x7800 // packed output buffer + t5GatherTableOff = 0x40 // table base = 0x7740 = stub + 0x40 +) + +// buildT5GatherImage builds the SRAM image uploaded at t5GatherStubAddr: +// stub (padded to 0x40) + descriptor table { count, pad, N*{addr.w, len.b, pad.b} }. +// Shared by the txbridge gather path and the generic fast-logger. +func buildT5GatherImage(symbols []*symbol.Symbol) ([]byte, error) { + image := make([]byte, t5GatherTableOff) + copy(image, t5GatherStub) + image = append(image, byte(len(symbols)), 0x00) + for _, sym := range symbols { + if sym.SramOffset >= 0x8000 || sym.Length == 0 || sym.Length > 255 { + return nil, fmt.Errorf("symbol %s not gather-representable (addr %X len %d)", sym.Name, sym.SramOffset, sym.Length) + } + image = append(image, byte(sym.SramOffset>>8), byte(sym.SramOffset), byte(sym.Length), 0x00) + } + if len(image) > 255 { + return nil, fmt.Errorf("gather image too large: %d bytes", len(image)) + } + return image, nil +} + +type T5FastClient struct { + *BaseLogger +} + +func NewT5Fast(cfg Config, lw LogWriter) (IClient, error) { + return &T5FastClient{BaseLogger: NewBaseLogger(cfg, lw)}, nil +} + +func (c *T5FastClient) Start() error { + defer c.secondTicker.Stop() + defer c.lw.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + eventHandler := func(e gocan.Event) { + c.OnMessage(e.String()) + if e.Type == gocan.EventTypeError { + c.onError() + } + } + + cl, err := gocan.NewWithOpts(ctx, c.Device, gocan.WithEventFunc(eventHandler)) + if err != nil { + return err + } + defer cl.Close() + ctx = cl.Context() + + t := time.NewTicker(time.Second / time.Duration(c.Rate)) + defer t.Stop() + t5 := t5can.NewClient(cl) + + // T5 decodes every value into sysvars (see newT5Converter), so all columns + // are sysvar channels. + channels := make([]Channel, 0, len(c.Symbols)+2) + gatherLen := uint32(0) + for _, s := range c.Symbols { + s.Correctionfactor = 0.1 + gatherLen += uint32(s.Length) + channels = append(channels, newSysvarChannel(c.sysvars, s.Name)) + } + + if err := c.setupWBL(ctx, cl); err != nil { + return err + } + + if c.lamb != nil { + defer c.lamb.Stop() + } + for _, name := range c.appendExtraSysvars(nil) { + channels = append(channels, newSysvarChannel(c.sysvars, name)) + } + + // Upload the gather stub + descriptor table and arm the stream-call once. + image, err := buildT5GatherImage(c.Symbols) + if err != nil { + return err + } + if err := t5.WriteRam(ctx, t5GatherStubAddr, image); err != nil { + return fmt.Errorf("gather upload: %w", err) + } + if err := armT5Gather(ctx, cl); err != nil { + return fmt.Errorf("gather arm: %w", err) + } + c.OnMessage(fmt.Sprintf("T5 fast-logger enabled (%d symbols, %d byte payload)", len(c.Symbols), len(image))) + + tx := cl.Subscribe(ctx, gocan.SystemMsgDataResponse) + defer tx.Close() + + converto := newT5Converter() + adscannerConverter := NewWBLInterpolator(c.WidebandConfig) + + go func() { + defer cl.Close() + for { + select { + case <-ctx.Done(): + return + case <-c.quitChan: + c.OnMessage("Stopped logging..") + return + case <-c.secondTicker.C: + c.FpsCounter(c.capturePerSecond) + if c.errPerSecond > 5 { + c.OnMessage("too many errors, aborting logging") + return + } + c.resetPerSecond() + case read := <-c.readChan: + data, err := t5.ReadRam(ctx, read.Address, read.Length) + if err != nil { + c.onError() + c.OnMessage(err.Error()) + continue + } + read.Data = data + read.Complete(nil) + case write := <-c.writeChan: + if err := t5.WriteRam2(ctx, write.Address, write.Data); err != nil { + write.Complete(err) + break + } + write.Complete(nil) + case <-t.C: + ts := time.Now() + + // Call the stub (C1, no reply) to pack symbols into 0x7800, then + // read the whole packed buffer in one go. + if err := callT5Gather(cl); err != nil { + c.onError() + c.OnMessage(err.Error()) + continue + } + // C1 runs the stub with interrupts masked; the ECU only frees its + // command mailbox at the end of that ISR, so a C7 sent right behind + // C1 is dropped. Wait a touch before reading. + time.Sleep(2 * time.Millisecond) // ponytail: fixed gap, tune if reads come back stale + packed, err := t5.ReadRam(ctx, t5GatherBufAddr, gatherLen) + if err != nil { + c.onError() + c.OnMessage(err.Error()) + continue + } + + off := 0 + for _, sym := range c.Symbols { + end := off + int(sym.Length) + if err := sym.Read(bytes.NewReader(packed[off:end])); err != nil { + c.OnMessage("failed to read symbol " + sym.Name + ": " + err.Error()) + return + } + off = end + val := converto(sym.Name, sym.Bytes()) + if c.WidebandConfig.ADScanner && sym.Name == c.WidebandConfig.ADScannerSymbol { + lambda := adscannerConverter(int(val)) + c.sysvars.Set(LAMBDAADSCANNER, lambda) + ebus.Publish(LAMBDAADSCANNER, lambda) + } + c.sysvars.Set(sym.Name, val) + ebus.Publish(sym.Name, val) + } + + if c.lamb != nil { + lambda := c.lamb.GetLambda() + c.sysvars.Set(EXTERNALWBLSYM, lambda) + ebus.Publish(EXTERNALWBLSYM, lambda) + } + + if err := c.lw.Write(ts, channels); err != nil { + c.OnMessage("failed to write log: " + err.Error()) + return + } + c.onCapture(ts) + } + } + }() + return cl.Wait(ctx) +} + +// armT5Gather sets the ECU's stream-call flag (A5 @ stub addr, len 0). The flag +// persists, so we only arm once; C1 then calls the stub each cycle. +func armT5Gather(ctx context.Context, cl *gocan.Client) error { + arm := []byte{0xA5, t5GatherStubAddr >> 24, t5GatherStubAddr >> 16, t5GatherStubAddr >> 8, t5GatherStubAddr & 0xFF, 0x00, 0x00, 0x00} + resp, err := cl.SendAndWait(ctx, gocan.NewFrame(0x5, arm, gocan.ResponseRequired), 500*time.Millisecond, 0xC) + if err != nil { + return err + } + if resp.DLC() < 2 || resp.Data[0] != 0xA5 || resp.Data[1] != 0x00 { + return fmt.Errorf("arm rejected: % 02X", resp.Data) + } + return nil +} + +// callT5Gather issues the C1 "call address" command for the stub. C1 has no reply. +func callT5Gather(cl *gocan.Client) error { + c1 := []byte{0xC1, t5GatherStubAddr >> 24, t5GatherStubAddr >> 16, t5GatherStubAddr >> 8, t5GatherStubAddr & 0xFF, 0x00, 0x00, 0x00} + return cl.Send(0x5, c1, gocan.Outgoing) +} diff --git a/pkg/datalogger/txbridgelogger.go b/pkg/datalogger/txbridgelogger.go index cf26dbc0..002f6dd3 100644 --- a/pkg/datalogger/txbridgelogger.go +++ b/pkg/datalogger/txbridgelogger.go @@ -6,6 +6,7 @@ import ( "time" "github.com/roffe/gocan" + "github.com/roffe/txlogger/pkg/debug" ) // dataTimeout aborts a txbridge logging session if no log frame arrives for @@ -60,6 +61,10 @@ func (c *TxBridge) Start() error { if err := c.setECU(cl, "5"); err != nil { return err } + if c.Config.ExperimentalT5FastLogging { + debug.Log("Using experimental T5 fast logger") + return c.t5new(ctx, cl) + } return c.t5(ctx, cl) case "T7": if err := c.setECU(cl, "7"); err != nil { diff --git a/pkg/datalogger/txbridgelogger_t5.go b/pkg/datalogger/txbridgelogger_t5.go index 4b518588..319b785d 100644 --- a/pkg/datalogger/txbridgelogger_t5.go +++ b/pkg/datalogger/txbridgelogger_t5.go @@ -8,6 +8,7 @@ import ( "log" "time" + "github.com/avast/retry-go/v4" "github.com/roffe/gocan" "github.com/roffe/gocan/pkg/serialcommand" "github.com/roffe/txlogger/pkg/ebus" @@ -211,6 +212,293 @@ func (c *TxBridge) t5(pctx context.Context, cl *gocan.Client) error { return cl.Wait(ctx) } +// enableT5Gather uploads the gather stub + descriptor table to ECU SRAM (using the +// same A5 address-command + 7-byte index-frame write the bootloader uses) and then +// enables gather mode on the dongle. The table is built from c.Symbols (the same +// list sent via 'd'); the C4 'W' write can't be used here (it needs an ECU +// write-enable we never set), so we drive the raw CAN write ourselves while the +// dongle is idle and forwards the 0xC replies back to us. +// +// The stub/table layout (t5GatherStub, buildT5GatherImage) is shared with the +// generic-adapter fast logger in t5fastlogger.go. +func (c *TxBridge) enableT5Gather(ctx context.Context, cl *gocan.Client) error { + image, err := buildT5GatherImage(c.Symbols) + if err != nil { + return err + } + + if err := c.uploadT5SRAM(ctx, cl, t5GatherStubAddr, image); err != nil { + return fmt.Errorf("gather upload: %w", err) + } + if err := cl.Send(gocan.SystemMsg, []byte("g"), gocan.Outgoing); err != nil { + return err + } + c.OnMessage(fmt.Sprintf("T5 gather enabled (%d symbols, %d byte image)", len(c.Symbols), len(image))) + return nil +} + +// uploadT5SRAM writes data into ECU SRAM via the stock A5 arm + 7-byte index frames +// (cf. pkg/ecu/t5/bootloader.go). Each index frame is acked on 0xC with +// [offset, 0x00]. Must run while the dongle is idle (pre-logging) so the 0xC +// replies are forwarded to the host. +// +// The index byte is the write offset within the armed block and MUST stay <= 0x7F +// (the ECU routes a first byte > 0x7F to the command table, not the write path), +// so chunk into blocks, re-arming at each block's base so the offset restarts at 0. +func (c *TxBridge) uploadT5SRAM(ctx context.Context, cl *gocan.Client, address uint32, data []byte) error { + const maxBlock = 112 // 16 frames, max offset 105 (0x69) — well under 0x7F + for blkStart := 0; blkStart < len(data); blkStart += maxBlock { + blkEnd := min(blkStart+maxBlock, len(data)) + block := data[blkStart:blkEnd] + blkAddr := address + uint32(blkStart) + + // Retry only on a lost 0xC ack (timeout): index writes are absolute + // (base+index) and re-arming engine-off is idempotent, so re-sending the + // same frame is safe. A received-but-wrong reply is NOT retried — for A5 + // it means the arm was *rejected* (RPM-gated: engine running, or bad addr), + // which also de-arms (CLR.B 0x3724); re-sending A5 can't beat the gate and + // is exactly what must never happen on a live engine (see Trionic5.md). + arm := []byte{0xA5, byte(blkAddr >> 24), byte(blkAddr >> 16), byte(blkAddr >> 8), byte(blkAddr), byte(len(block)), 0x00, 0x00} + if err := retry.Do(func() error { + resp, err := cl.SendAndWait(ctx, gocan.NewFrame(0x5, arm, gocan.ResponseRequired), 500*time.Millisecond, 0xC) + if err != nil { + return err + } + if resp.DLC() < 2 || resp.Data[0] != 0xA5 || resp.Data[1] != 0x00 { + return retry.Unrecoverable(fmt.Errorf("rejected (engine must be off to arm): % 02X", resp.Data)) + } + return nil + }, retry.Context(ctx), retry.LastErrorOnly(true), retry.Attempts(3)); err != nil { + return fmt.Errorf("arm @%X: %w", blkAddr, err) + } + + for off := 0; off < len(block); off += 7 { + frame := make([]byte, 8) + frame[0] = byte(off) + for i := 0; i < 7 && off+i < len(block); i++ { + frame[1+i] = block[off+i] + } + if err := retry.Do(func() error { + resp, err := cl.SendAndWait(ctx, gocan.NewFrame(0x5, frame, gocan.ResponseRequired), 250*time.Millisecond, 0xC) + if err != nil { + return err + } + if resp.DLC() < 2 || resp.Data[0] != byte(off) || resp.Data[1] != 0x00 { + return retry.Unrecoverable(fmt.Errorf("rejected: % 02X", resp.Data)) + } + return nil + }, retry.Context(ctx), retry.LastErrorOnly(true), retry.Attempts(3)); err != nil { + return fmt.Errorf("data @%X+%d: %w", blkAddr, off, err) + } + } + } + return nil +} + +// t5new is a test variant of t5 that enables the gather fast-logger. Identical to +// t5 except for the enableT5Gather call before startLogging. Flip the dispatch in +// txbridgelogger.go (c.t5 -> c.t5new) to try it; merge into t5 once validated. +func (c *TxBridge) t5new(pctx context.Context, cl *gocan.Client) error { + ctx, cancel := context.WithCancel(pctx) + defer cancel() + + channels := make([]Channel, 0, len(c.Symbols)+2) + for _, s := range c.Symbols { + s.Correctionfactor = 0.1 + channels = append(channels, newSysvarChannel(c.sysvars, s.Name)) + } + + if c.lamb != nil { + defer c.lamb.Stop() + } + for _, name := range c.appendExtraSysvars(nil) { + channels = append(channels, newSysvarChannel(c.sysvars, name)) + } + + expectedPayloadSize, err := c.configureT5Symbols(cl) // configure the symbol list in the dongle and get the expected payload size + if err != nil { + return fmt.Errorf("error configuring symbols: %w", err) + } + + // --- the only difference from t5(): build+upload the gather stub/table and enable. + if err := c.enableT5Gather(ctx, cl); err != nil { + return fmt.Errorf("error enabling gather: %w", err) + } + + tx := cl.Subscribe(ctx, gocan.SystemMsgDataResponse) + defer tx.Close() + + if err := c.startLogging(cl); err != nil { + return fmt.Errorf("error starting logging: %w", err) + } + + converto := newT5Converter() + adscannerConverter := NewWBLInterpolator(c.WidebandConfig) + + go func() { + defer cl.Close() + defer func() { + _ = c.stopLogging(cl) + time.Sleep(50 * time.Millisecond) + }() + lastData := time.Now() + for { + select { + case <-ctx.Done(): + return + case <-c.quitChan: + c.OnMessage("Stopped logging..") + return + case <-c.secondTicker.C: + c.FpsCounter(c.capturePerSecond) + if c.errPerSecond > 5 { + c.OnMessage("too many errors, aborting logging") + return + } + if time.Since(lastData) > dataTimeout { + c.OnMessage("no data for 5s, aborting logging") + return + } + c.resetPerSecond() + case read := <-c.readChan: + toRead := min(234, read.Length) + read.Length -= toRead + cmd := serialcommand.SerialCommand{ + Command: 'R', + Data: []byte{ + byte(read.Address), + byte(read.Address >> 8), + byte(read.Address >> 16), + byte(read.Address >> 24), + byte(toRead), + }, + } + read.Address += uint32(toRead) + payload, err := cmd.MarshalBinary() + if err != nil { + c.onError() + c.OnMessage(err.Error()) + continue + } + frame := gocan.NewFrame(gocan.SystemMsg, payload, gocan.Outgoing) + resp, err := cl.SendAndWait(ctx, frame, 3*time.Second, gocan.SystemMsgDataRequest) + if err != nil { + read.Complete(err) + continue + } + read.Data = append(read.Data, resp.Data...) + if read.Length > 0 { + c.readChan <- read + } else { + read.Complete(nil) + } + continue + case write := <-c.writeChan: + toWrite := min(128, write.Length) + cmd := serialcommand.SerialCommand{ + Command: 'W', + Data: []byte{ + byte(write.Address), + byte(write.Address >> 8), + byte(write.Address >> 16), + byte(write.Address >> 24), + byte(toWrite), + }, + } + cmd.Data = append(cmd.Data, write.Data[:toWrite]...) + + write.Data = write.Data[toWrite:] + write.Address += uint32(toWrite) + write.Length -= toWrite + + payload, err := cmd.MarshalBinary() + if err != nil { + write.Complete(err) + continue + } + + frame := gocan.NewFrame(gocan.SystemMsg, payload, gocan.Outgoing) + + resp, err := cl.SendAndWait(ctx, frame, 5*time.Second, gocan.SystemMsgWriteResponse) + if err != nil { + write.Complete(err) + continue + } + + if resp.Identifier == gocan.SystemMsgError { + write.Complete(fmt.Errorf("error response: % 02X", resp.Data)) + continue + } + + if write.Length > 0 { + select { + case c.writeChan <- write: + default: + log.Println("writeChan full") + } + continue + } + write.Complete(nil) + continue + case msg, ok := <-tx.Chan(): + if !ok { + c.OnMessage("txbridge sub closed") + return + } + lastData = time.Now() + + if msg.DLC() != (expectedPayloadSize + 4) { + c.onError() + c.OnMessage(fmt.Sprintf("expected %d bytes, got %d", expectedPayloadSize+4, msg.DLC())) + continue + } + + r := bytes.NewReader(msg.Data) + if err := binary.Read(r, binary.LittleEndian, &c.currtimestamp); err != nil { + c.onError() + c.OnMessage("failed to read timestamp: " + err.Error()) + continue + } + + if c.firstTime.IsZero() { + c.firstTime = time.Now() + c.firstTimestamp = c.currtimestamp + } + + timeStamp := c.calculateCompensatedTimestamp() + + for _, sym := range c.Symbols { + if err := sym.Read(r); err != nil { + c.OnMessage("failed to read symbol " + sym.Name + ": " + err.Error()) + return + } + val := converto(sym.Name, sym.Bytes()) + if c.WidebandConfig.ADScanner && sym.Name == c.WidebandConfig.ADScannerSymbol { + lambda := adscannerConverter(int(val)) + c.sysvars.Set(LAMBDAADSCANNER, lambda) + ebus.Publish(LAMBDAADSCANNER, lambda) + } + c.sysvars.Set(sym.Name, val) + ebus.Publish(sym.Name, val) + } + + if c.lamb != nil { + lambda := c.lamb.GetLambda() + c.sysvars.Set(EXTERNALWBLSYM, lambda) + ebus.Publish(EXTERNALWBLSYM, lambda) + } + + if err := c.lw.Write(timeStamp, channels); err != nil { + c.OnMessage("failed to write log: " + err.Error()) + return + } + c.onCapture(timeStamp) + } + } + }() + return cl.Wait(ctx) +} + func (c *TxBridge) configureT5Symbols(cl *gocan.Client) (int, error) { var expectedPayloadSize uint16 var symbollist []byte @@ -234,3 +522,11 @@ func (c *TxBridge) configureT5Symbols(cl *gocan.Client) (int, error) { c.OnMessage("Symbol list configured") return int(expectedPayloadSize), nil } + +func (c *TxBridge) calculateExpectedPayloadSize() int { + var expectedPayloadSize uint16 + for _, sym := range c.Symbols { + expectedPayloadSize += sym.Length + } + return int(expectedPayloadSize) +} diff --git a/pkg/ota/firmware.bin b/pkg/ota/firmware.bin index 8710acd47b340c84d4b477bea95fc65d725ccf50..3c1465df866e140daa06a955e4d67a5d15033cb6 100644 GIT binary patch delta 226003 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 z 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%0DPx2zs;uu>qyl9)* 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�mb?BKpJk+M_KRC`3?nefa@S3%X})3;6oo(d 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

    9*wWt>Zki~M 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 ext.x || p_dev.y > ext.y) { diff --git a/pkg/widgets/dualdial/shader/dual_dial_shader.go b/pkg/widgets/dualdial/shader/dual_dial_shader.go index 54b784b7..1d942374 100644 --- a/pkg/widgets/dualdial/shader/dual_dial_shader.go +++ b/pkg/widgets/dualdial/shader/dual_dial_shader.go @@ -25,8 +25,8 @@ precision mediump float; ` const dualDialShaderBody = ` -uniform vec2 frame_size; -uniform vec4 rect_coords; +uniform vec2 frame; +uniform vec4 bounds; uniform float size_d; // dial diameter, logical px uniform float steps; // pip intervals; steps+1 pips are drawn @@ -61,8 +61,8 @@ void over(inout vec3 col, inout float alpha, vec3 c, float a) { } void main() { - vec2 ext = vec2(rect_coords.y - rect_coords.x, rect_coords.w - rect_coords.z); - vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; + vec2 ext = vec2(bounds.z - bounds.x, bounds.w - bounds.y); + vec2 p_dev = vec2(gl_FragCoord.x, frame.y - gl_FragCoord.y) - bounds.xy; // the painter expands the quad slightly for edge softness; stay inside if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > ext.x || p_dev.y > ext.y) { diff --git a/pkg/widgets/meshgrid/meshgrid_shader.go b/pkg/widgets/meshgrid/meshgrid_shader.go index 071ee970..13ae048e 100644 --- a/pkg/widgets/meshgrid/meshgrid_shader.go +++ b/pkg/widgets/meshgrid/meshgrid_shader.go @@ -57,8 +57,8 @@ precision mediump float; const meshShaderBody = ` #define MAX_STEPS 160 -uniform vec2 frame_size; -uniform vec4 rect_coords; +uniform vec2 frame; +uniform vec4 bounds; uniform sampler2D mesh_tex; // cols x rows cell values (or corner grid), 16 bit in RG uniform sampler2D colormap_tex; // 256x1 value -> base color lookup @@ -253,11 +253,11 @@ vec3 shade_surface(vec3 base, vec3 n, vec3 light, vec3 view_dir, float ao) { void main() { mat3 rot = mat3(r0, r3, r6, r1, r4, r7, r2, r5, r8); - float pix_scale = (rect_coords.y - rect_coords.x) / max(size_w, 1.0); - vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; + float pix_scale = (bounds.z - bounds.x) / max(size_w, 1.0); + vec2 p_dev = vec2(gl_FragCoord.x, frame.y - gl_FragCoord.y) - bounds.xy; // the painter expands the quad slightly for edge softness; stay inside - if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > rect_coords.y - rect_coords.x || p_dev.y > rect_coords.w - rect_coords.z) { + if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > bounds.z - bounds.x || p_dev.y > bounds.w - bounds.y) { discard; } diff --git a/pkg/widgets/plotter/plotter_shader.go b/pkg/widgets/plotter/plotter_shader.go index 5b828140..4cce5285 100644 --- a/pkg/widgets/plotter/plotter_shader.go +++ b/pkg/widgets/plotter/plotter_shader.go @@ -65,8 +65,8 @@ const plotShaderBody = ` #define MAX_RAW 16 #define MAX_GROUPS 24 -uniform vec2 frame_size; -uniform vec4 rect_coords; +uniform vec2 frame; +uniform vec4 bounds; uniform sampler2D data_tex; // one texel per sample, 16-bit value in RG uniform sampler2D mm_tex; // min/max per 16 samples: RG=min, BA=max @@ -130,10 +130,10 @@ float seg_dist(vec2 p, vec2 a, vec2 b) { } void main() { - float pix_scale = (rect_coords.y - rect_coords.x) / max(size_w, 1.0); - vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; - float w_dev = rect_coords.y - rect_coords.x; - float h_dev = rect_coords.w - rect_coords.z; + float pix_scale = (bounds.z - bounds.x) / max(size_w, 1.0); + vec2 p_dev = vec2(gl_FragCoord.x, frame.y - gl_FragCoord.y) - bounds.xy; + float w_dev = bounds.z - bounds.x; + float h_dev = bounds.w - bounds.y; // the painter expands the quad slightly for edge softness; stay inside if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > w_dev || p_dev.y > h_dev) { diff --git a/pkg/widgets/tunnel/tunnel_shader.go b/pkg/widgets/tunnel/tunnel_shader.go index 0a8818a4..e36a276a 100644 --- a/pkg/widgets/tunnel/tunnel_shader.go +++ b/pkg/widgets/tunnel/tunnel_shader.go @@ -41,8 +41,8 @@ precision mediump float; const tunnelBody = ` #define PI 3.14159265359 -uniform vec2 frame_size; // output frame size, device px -uniform vec4 rect_coords; // this object's bounds (x1, x2, y1, y2), device px +uniform vec2 frame; // output frame size, device px +uniform vec4 bounds; // this object's bounds (x1, y1, x2, y2), device px uniform float time; // elapsed animation seconds // optional tuning knobs (default sensibly if left at 0 by the Go side) @@ -78,8 +78,8 @@ float tri(float x) { } void main() { - vec2 size = vec2(rect_coords.y - rect_coords.x, rect_coords.w - rect_coords.z); - vec2 p_dev = vec2(gl_FragCoord.x, frame_size.y - gl_FragCoord.y) - rect_coords.xz; + vec2 size = vec2(bounds.z - bounds.x, bounds.w - bounds.y); + vec2 p_dev = vec2(gl_FragCoord.x, frame.y - gl_FragCoord.y) - bounds.xy; // the painter expands the quad slightly for edge softness; stay inside if (p_dev.x < 0.0 || p_dev.y < 0.0 || p_dev.x > size.x || p_dev.y > size.y) { From e3731f330a4c241d3f1a01e108f57632763d64d5 Mon Sep 17 00:00:00 2001 From: roffe Date: Fri, 3 Jul 2026 23:06:47 +0200 Subject: [PATCH 096/102] cleanup --- .../dial_shader}/dial.go | 0 .../dial_shader}/dial_shader.go | 0 .../dial_shader}/dial_shader_test.go | 0 .../dual_dial_shader}/dual_dial.go | 0 .../dual_dial_shader}/dual_dial_shader.go | 0 .../dual_dial_shader_test.go | 0 go.mod | 10 +- go.sum | 8 +- pkg/bus/aggregators.go | 13 +- pkg/layout/portion.go | 158 ++++++++ pkg/layout/portion_test.go | 71 ++++ pkg/ota/firmware.bin | Bin 1012544 -> 1259024 bytes pkg/widgets/completionentry.go | 260 ++++++++++++ pkg/widgets/completionentry_test.go | 262 ++++++++++++ pkg/widgets/dial/dial.go | 37 +- pkg/widgets/dial/dial.old | 377 ------------------ pkg/widgets/dualdial/dual_dial.go | 43 +- pkg/widgets/dualdial/dual_dial.old | 363 ----------------- pkg/widgets/logplayer/logplayer.go | 29 +- pkg/widgets/maps/mapcache.go | 40 -- pkg/widgets/maps/maps.go | 370 ----------------- pkg/widgets/maps/mapsbutton.go | 49 --- pkg/widgets/multiwindow/innerwindow.go | 21 +- pkg/widgets/plotter/plotter.go | 3 +- pkg/widgets/symbolbrowser/symbolbrowser.go | 2 +- pkg/widgets/symbollist/symbollist.go | 2 +- pkg/widgets/txconfigurator/txconfigurator.go | 17 +- pkg/windows/mainWindow.go | 4 +- pkg/windows/mainWindow_internal.go | 4 +- 29 files changed, 858 insertions(+), 1285 deletions(-) rename {pkg/widgets/dial/shader => experiments/dial_shader}/dial.go (100%) rename {pkg/widgets/dial/shader => experiments/dial_shader}/dial_shader.go (100%) rename {pkg/widgets/dial/shader => experiments/dial_shader}/dial_shader_test.go (100%) rename {pkg/widgets/dualdial/shader => experiments/dual_dial_shader}/dual_dial.go (100%) rename {pkg/widgets/dualdial/shader => experiments/dual_dial_shader}/dual_dial_shader.go (100%) rename {pkg/widgets/dualdial/shader => experiments/dual_dial_shader}/dual_dial_shader_test.go (100%) create mode 100644 pkg/layout/portion.go create mode 100644 pkg/layout/portion_test.go create mode 100644 pkg/widgets/completionentry.go create mode 100644 pkg/widgets/completionentry_test.go delete mode 100644 pkg/widgets/dial/dial.old delete mode 100644 pkg/widgets/dualdial/dual_dial.old delete mode 100644 pkg/widgets/maps/mapcache.go delete mode 100644 pkg/widgets/maps/maps.go delete mode 100644 pkg/widgets/maps/mapsbutton.go diff --git a/pkg/widgets/dial/shader/dial.go b/experiments/dial_shader/dial.go similarity index 100% rename from pkg/widgets/dial/shader/dial.go rename to experiments/dial_shader/dial.go diff --git a/pkg/widgets/dial/shader/dial_shader.go b/experiments/dial_shader/dial_shader.go similarity index 100% rename from pkg/widgets/dial/shader/dial_shader.go rename to experiments/dial_shader/dial_shader.go diff --git a/pkg/widgets/dial/shader/dial_shader_test.go b/experiments/dial_shader/dial_shader_test.go similarity index 100% rename from pkg/widgets/dial/shader/dial_shader_test.go rename to experiments/dial_shader/dial_shader_test.go diff --git a/pkg/widgets/dualdial/shader/dual_dial.go b/experiments/dual_dial_shader/dual_dial.go similarity index 100% rename from pkg/widgets/dualdial/shader/dual_dial.go rename to experiments/dual_dial_shader/dual_dial.go diff --git a/pkg/widgets/dualdial/shader/dual_dial_shader.go b/experiments/dual_dial_shader/dual_dial_shader.go similarity index 100% rename from pkg/widgets/dualdial/shader/dual_dial_shader.go rename to experiments/dual_dial_shader/dual_dial_shader.go diff --git a/pkg/widgets/dualdial/shader/dual_dial_shader_test.go b/experiments/dual_dial_shader/dual_dial_shader_test.go similarity index 100% rename from pkg/widgets/dualdial/shader/dual_dial_shader_test.go rename to experiments/dual_dial_shader/dual_dial_shader_test.go diff --git a/go.mod b/go.mod index 813ec373..04ad99de 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,16 @@ go 1.26.0 //replace github.com/roffe/gocan => ..\gocan //replace github.com/roffe/ecusymbol => ..\ecusymbol -//replace fyne.io/fyne/v2 => ..\..\fyne-io\fyne +//replace fyne.io/fyne/v2 => ../fyne + //replace go.bug.st/serial => ../../../go.bug.st/serial replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.8.0-rc1.0.20260701153857-4a089fc3dab4 - fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e + fyne.io/fyne/v2 v2.8.0-rc1.0.20260703173000-4a52a9fe8ec0 github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 - github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/roffe/ecusymbol v1.2.5 github.com/roffe/gocan v1.4.6 go.bug.st/serial v1.7.1 @@ -31,6 +30,7 @@ require ( github.com/ebitengine/oto/v3 v3.4.0 github.com/godbus/dbus/v5 v5.2.2 github.com/hajimehoshi/go-mp3 v0.3.4 + github.com/stretchr/testify v1.11.1 golang.org/x/sys v0.46.0 kernel.org/pub/linux/libs/security/libcap/cap v1.2.78 ) @@ -68,6 +68,7 @@ require ( github.com/mattn/go-runewidth v0.0.17 // indirect github.com/mdlayher/netlink v1.8.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect @@ -75,7 +76,6 @@ require ( github.com/rymdport/portal v0.4.2 // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect - github.com/stretchr/testify v1.11.1 // indirect github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9 // indirect github.com/yuin/goldmark v1.7.16 // indirect go.einride.tech/can v0.16.1 // indirect diff --git a/go.sum b/go.sum index 97deb48d..739d97f1 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ -fyne.io/fyne/v2 v2.8.0-rc1.0.20260701153857-4a089fc3dab4 h1:J9sHdnKvuI4wT8JnTH+vcTPlVtJmSS8EGOiKjYBA/Zk= -fyne.io/fyne/v2 v2.8.0-rc1.0.20260701153857-4a089fc3dab4/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260703154118-128e217711eb h1:jc6YOUFxAwtiiMNetr9u5bku3+GIkAVzpsy1VJIzY8I= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260703154118-128e217711eb/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260703173000-4a52a9fe8ec0 h1:nhP5BXtOU6MtXJaSBXBynmOHmWvezpL0N0u4CYZs1SU= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260703173000-4a52a9fe8ec0/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= -fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= -fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e/go.mod h1:TyPwb4pDTB8+btHM20AJpPUNAF8FqEq136+vcGQhcI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= diff --git a/pkg/bus/aggregators.go b/pkg/bus/aggregators.go index bbebbf2c..6d9b5b0d 100644 --- a/pkg/bus/aggregators.go +++ b/pkg/bus/aggregators.go @@ -1,13 +1,10 @@ package bus -import "sync" +import ( + "sync" -// Number is the set of value types DIFFAggregator can subtract. -type Number interface { - ~int | ~int8 | ~int16 | ~int32 | ~int64 | - ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | - ~float32 | ~float64 -} + "github.com/roffe/txlogger/pkg/common" +) // DIFFAggregator subscribes to the first and second topics and, once both have // produced a value, publishes their difference (second - first) to the output @@ -21,7 +18,7 @@ type Number interface { // // The returned unsubscribe function removes both input subscriptions; calling // it more than once is safe and has no further effect. -func DIFFAggregator[K comparable, V Number](c *Controller[K, V], first, second, output K) (unsubscribe func()) { +func DIFFAggregator[K comparable, V common.Number](c *Controller[K, V], first, second, output K) (unsubscribe func()) { var ( mu sync.Mutex firstUpdated bool diff --git a/pkg/layout/portion.go b/pkg/layout/portion.go new file mode 100644 index 00000000..695a316a --- /dev/null +++ b/pkg/layout/portion.go @@ -0,0 +1,158 @@ +package layout + +import ( + "log" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" +) + +var _ fyne.Layout = (*HPortion)(nil) + +// HPortion allows the canvas objects to be divided into portions of the width. +// The length of the Portions slice needs to be equal to the amount of canvas objects. +type HPortion struct { + Portions []float64 +} + +// Layout sets the size and position of the canvas objects. +func (p *HPortion) Layout(objects []fyne.CanvasObject, size fyne.Size) { + if len(p.Portions) != len(objects) { + log.Println("Mismatch between partitions and objects") + return + } + + sum := float64(0) + for _, child := range p.Portions { + sum += float64(child) + } + + padding := theme.Padding() + width := size.Width - padding*float32(len(objects)-1) + xpos := float32(0) + + for i, child := range objects { + width := float32(p.Portions[i]/sum) * width + child.Resize(fyne.NewSize(width, size.Height)) + child.Move(fyne.NewPos(xpos, 0)) + + xpos += width + padding + } +} + +// MinSize calculates the minimum required size to fit all objects. +// It is equal to the largest width MinSize divided by the corresponding portion. +func (p *HPortion) MinSize(objects []fyne.CanvasObject) fyne.Size { + if len(p.Portions) != len(objects) { + log.Println("Mismatch between partitions and objects") + return fyne.NewSize(0, 0) + } + + if len(objects) == 0 { + return fyne.NewSize(0, 0) + } + + sum := float64(0) + for _, child := range p.Portions { + sum += float64(child) + } + + maxMinWidth := float32(0) + maxIndex := -1 + height := float32(0) + + for i := 0; i < len(objects); i++ { + min := objects[i].MinSize() + height = fyne.Max(height, min.Height) + + if min.Width > maxMinWidth { + maxMinWidth = min.Width + maxIndex = i + } + } + + totalPadding := float32(len(objects)-1) * theme.Padding() + return fyne.NewSize(maxMinWidth/float32(p.Portions[maxIndex]/sum)+totalPadding, height) +} + +// NewHPortion creates a layout that partitions objects horizontally taking up +// as large of a portion of the space as defined by the given slice. +// The length of the Portions slice needs to be equal to the amount of objects. +func NewHPortion(Portions []float64) *HPortion { + return &HPortion{Portions: Portions} +} + +var _ fyne.Layout = (*VPortion)(nil) + +// VPortion allows the canvas objects to be divided into portions of the height. +// The length of the Portions slice needs to be equal to the amount of canvas objects. +type VPortion struct { + Portions []float64 +} + +// Layout sets the size and position of the canvas objects. +func (p *VPortion) Layout(objects []fyne.CanvasObject, size fyne.Size) { + if len(p.Portions) != len(objects) { + log.Println("Mismatch between partitions and objects") + return + } + + sum := float64(0) + for _, child := range p.Portions { + sum += float64(child) + } + + padding := theme.Padding() + height := size.Width - padding*float32(len(objects)-1) + ypos := float32(0) + + for i, child := range objects { + height := float32(p.Portions[i]/sum) * height + child.Resize(fyne.NewSize(size.Width, height)) + child.Move(fyne.NewPos(0, ypos)) + + ypos += height + padding + } +} + +// MinSize calculates the minimum required size to fit all objects. +// It is equal to the largest height MinSize divided by the corresponding portion. +func (p *VPortion) MinSize(objects []fyne.CanvasObject) fyne.Size { + if len(p.Portions) != len(objects) { + log.Println("Mismatch between partitions and objects") + return fyne.NewSize(0, 0) + } + + if len(objects) == 0 { + return fyne.NewSize(0, 0) + } + + sum := float64(0) + for _, child := range p.Portions { + sum += float64(child) + } + + maxMinHeight := float32(0) + maxIndex := -1 + width := float32(0) + + for i := 0; i < len(objects); i++ { + min := objects[i].MinSize() + width = fyne.Max(width, min.Width) + + if min.Height > maxMinHeight { + maxMinHeight = min.Height + maxIndex = i + } + } + + totalPadding := float32(len(objects)-1) * theme.Padding() + return fyne.NewSize(width, maxMinHeight/float32(p.Portions[maxIndex]/sum)+totalPadding) +} + +// NewVPortion creates a layout that partitions objects verticaly taking up +// as large of a portion of the space as defined by the given slice. +// The length of the Portions slice needs to be equal to the amount of objects. +func NewVPortion(portion []float64) *VPortion { + return &VPortion{Portions: portion} +} diff --git a/pkg/layout/portion_test.go b/pkg/layout/portion_test.go new file mode 100644 index 00000000..815cbfdd --- /dev/null +++ b/pkg/layout/portion_test.go @@ -0,0 +1,71 @@ +package layout + +import ( + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/stretchr/testify/assert" +) + +func TestHPortion(t *testing.T) { + cont := container.New(&HPortion{Portions: []float64{50, 50}}, widget.NewEntry(), widget.NewEntry()) + cont.Resize(fyne.NewSize(100, 100)) + + assert.Equal(t, (100-theme.Padding())/2, cont.Objects[0].Size().Width) + assert.Equal(t, (100-theme.Padding())/2, cont.Objects[1].Size().Width) + + assert.Equal(t, cont.Objects[0].MinSize().Height, cont.MinSize().Height) + assert.Equal(t, cont.Objects[1].MinSize().Height, cont.MinSize().Height) + + // Using 0.5 and 0.5 should be the same as 50 and 50. + cont.Layout = NewHPortion([]float64{0.5, 0.5}) + + assert.Equal(t, (100-theme.Padding())/2, cont.Objects[0].Size().Width) + assert.Equal(t, (100-theme.Padding())/2, cont.Objects[1].Size().Width) + + assert.Equal(t, cont.Objects[0].MinSize().Height, cont.MinSize().Height) + assert.Equal(t, cont.Objects[1].MinSize().Height, cont.MinSize().Height) + + // Mismatch in length should error out. + cont.Layout = NewHPortion([]float64{}) + cont.Resize(fyne.NewSize(50, 50)) + assert.Equal(t, fyne.NewSize(0, 0), cont.MinSize()) + + // Having no objects should result in zero size. + cont.Objects = nil + cont.Resize(fyne.NewSize(100, 100)) + assert.Equal(t, fyne.NewSize(0, 0), cont.MinSize()) +} + +func TestVPortion(t *testing.T) { + cont := container.New(&VPortion{Portions: []float64{50, 50}}, widget.NewEntry(), widget.NewEntry()) + cont.Resize(fyne.NewSize(100, 100)) + + assert.Equal(t, (100-theme.Padding())/2, cont.Objects[0].Size().Height) + assert.Equal(t, (100-theme.Padding())/2, cont.Objects[1].Size().Height) + + assert.Equal(t, cont.Objects[0].MinSize().Width, cont.MinSize().Width) + assert.Equal(t, cont.Objects[1].MinSize().Width, cont.MinSize().Width) + + // Using 0.5 and 0.5 should be the same as 50 and 50. + cont.Layout = NewVPortion([]float64{0.5, 0.5}) + + assert.Equal(t, (100-theme.Padding())/2, cont.Objects[0].Size().Height) + assert.Equal(t, (100-theme.Padding())/2, cont.Objects[1].Size().Height) + + assert.Equal(t, cont.Objects[0].MinSize().Width, cont.MinSize().Width) + assert.Equal(t, cont.Objects[1].MinSize().Width, cont.MinSize().Width) + + // Mismatch in length should error out. + cont.Layout = NewVPortion([]float64{}) + cont.Resize(fyne.NewSize(50, 50)) + assert.Equal(t, fyne.NewSize(0, 0), cont.MinSize()) + + // Having no objects should result in zero size. + cont.Objects = nil + cont.Resize(fyne.NewSize(100, 100)) + assert.Equal(t, fyne.NewSize(0, 0), cont.MinSize()) +} diff --git a/pkg/ota/firmware.bin b/pkg/ota/firmware.bin index a578e3c1992b9a7e440498660c198fe71833dace..f04980c5d1464f1d7f1c3bc6c48680c37f51695c 100644 GIT binary patch delta 765096 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

    uBInO0I3e;=KAV-=_SH3xN53hyk#GU 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

    1c$#`%O!D;ddUi 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 SJa?7JzHn)!yRSHMj{pESIH$b; diff --git a/pkg/t5can/t5can.go b/pkg/t5can/t5can.go index 0932abb3..5c0bce6d 100644 --- a/pkg/t5can/t5can.go +++ b/pkg/t5can/t5can.go @@ -62,6 +62,9 @@ func (c *Client) sendReadCommand(ctx context.Context, address uint32) ([]byte, e if resp.Data[0] != cmdByte { return nil, fmt.Errorf("invalid response: expected 0x%X, got 0x%X", cmdByte, resp.Data[0]) } + if resp.Data[1] != respOK { // D2 code: 4 = error (e.g. address out of range) + return nil, fmt.Errorf("read error at 0x%X: code 0x%02X", address, resp.Data[1]) + } data := append([]byte(nil), resp.Data[2:8]...) // copy slice slices.Reverse(data) diff --git a/pkg/widgets/dtcreader/dtcreader.go b/pkg/widgets/dtcreader/dtcreader.go index c04e8f5a..755699ce 100644 --- a/pkg/widgets/dtcreader/dtcreader.go +++ b/pkg/widgets/dtcreader/dtcreader.go @@ -137,7 +137,7 @@ func (d *DTCReader) ReadDTCS() error { } go func() { - defer d.Enable() + defer fyne.Do(d.Enable) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() @@ -181,7 +181,7 @@ func (d *DTCReader) ClearDTCS() error { return fmt.Errorf("DTC clearing not supported for ECU %s", ecu) } go func() { - defer d.Enable() + defer fyne.Do(d.Enable) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() diff --git a/pkg/widgets/settings/controls.go b/pkg/widgets/settings/controls.go index d467c88a..6faac78b 100644 --- a/pkg/widgets/settings/controls.go +++ b/pkg/widgets/settings/controls.go @@ -211,4 +211,7 @@ func (sw *Widget) loadPreferences() { // Graphics sw.plotRendererSelect.SetSelectedIndex(prefPlotterRenderer.get()) sw.meshRendererSelect.SetSelectedIndex(prefMeshRenderer.get()) + + // Logging + sw.experimentalT5FastLogger.SetChecked(prefExperimentalT5FastLogger.get()) } diff --git a/pkg/widgets/settings/getters.go b/pkg/widgets/settings/getters.go index c73667ad..4de32c3f 100644 --- a/pkg/widgets/settings/getters.go +++ b/pkg/widgets/settings/getters.go @@ -52,6 +52,8 @@ func (sw *Widget) GetLogPath() string { return p } +func (sw *Widget) GetExperimentalT5FastLogger() bool { return prefExperimentalT5FastLogger.get() } + // Dashboard func (sw *Widget) GetUseMPH() bool { return prefUseMPH.get() } func (sw *Widget) GetSwapRPMandSpeed() bool { return prefSwapRPMandSpeed.get() } diff --git a/pkg/widgets/settings/prefs.go b/pkg/widgets/settings/prefs.go index 5b996fb9..b1367e6d 100644 --- a/pkg/widgets/settings/prefs.go +++ b/pkg/widgets/settings/prefs.go @@ -84,8 +84,9 @@ var ( prefMeshRenderer = intPref{"meshRenderer", 2} // Logging - prefLogFormat = stringPref{"logFormat", "CSV"} - prefLogPath = stringPref{"logPath", ""} + prefLogFormat = stringPref{"logFormat", "CSV"} + prefLogPath = stringPref{"logPath", ""} + prefExperimentalT5FastLogger = boolPref{"experimentalT5FastLogger", false} // Dashboard prefUseMPH = boolPref{"useMPH", false} diff --git a/pkg/widgets/settings/settings.go b/pkg/widgets/settings/settings.go index c3572cd8..580c5970 100644 --- a/pkg/widgets/settings/settings.go +++ b/pkg/widgets/settings/settings.go @@ -63,6 +63,9 @@ type Widget struct { speedSelector *widget.Select adapters map[string]*gocan.AdapterInfo + // Loggging + experimentalT5FastLogger *widget.Check + // Wideband wbleditor *WBLEditor wblADscanner *widget.Check @@ -110,9 +113,13 @@ func (sw *Widget) CreateRenderer() fyne.WidgetRenderer { sw.livePreview = checkBox("Live preview values in symbollist (uncheck if you have a slow pc)", prefLivePreview) sw.meshView = checkBox("3D Mesh on map viewing", prefMeshView) sw.realtimeBars = checkBox("Bars on live preview values (uncheck if you have a slow pc)", prefRealtimeBars) + + // Logging sw.logFormat = sw.newLogFormat() sw.logPath = widget.NewLabel("") sw.logPath.Truncation = fyne.TextTruncateEllipsis + sw.experimentalT5FastLogger = checkBox("Experimental T5 fast logger", prefExperimentalT5FastLogger) + sw.useMPH = checkBox("Use mph instead of km/h", prefUseMPH) sw.swapRPMandSpeed = checkBox("Swap RPM and speed gauge position", prefSwapRPMandSpeed) sw.colorBlindMode = sw.newColorBlindMode() diff --git a/pkg/widgets/settings/tabs.go b/pkg/widgets/settings/tabs.go index 1bf538a8..dc9fe3f4 100644 --- a/pkg/widgets/settings/tabs.go +++ b/pkg/widgets/settings/tabs.go @@ -106,6 +106,9 @@ func (sw *Widget) loggingTab() *container.TabItem { section("Storage", container.NewBorder(nil, logFolderButtons, widget.NewLabel("Log folder"), nil, sw.logPath), ), + section("Experimental", + container.NewBorder(nil, nil, widget.NewIcon(theme.WarningIcon()), widget.NewLabel("MIGHT CORRUPT RAM!!"), sw.experimentalT5FastLogger), + ), ) } diff --git a/pkg/windows/mainWindow_buttons.go b/pkg/windows/mainWindow_buttons.go index 510b9c75..e11c905d 100644 --- a/pkg/windows/mainWindow_buttons.go +++ b/pkg/windows/mainWindow_buttons.go @@ -176,6 +176,10 @@ func (mw *MainWindow) addSymbolBtnFunc() *widget.Button { if mw.selects.symbolLookup.Text == "" { return } + if mw.fw == nil { + mw.Error(fmt.Errorf("Cannot add symbol, no binary loaded")) + return + } /* switch mw.selects.symbolLookup.Text { case "ADC1": @@ -218,7 +222,7 @@ func (mw *MainWindow) addSymbolBtnFunc() *widget.Button { sym := mw.fw.GetByName(mw.selects.symbolLookup.Text) if sym == nil { - mw.Error(fmt.Errorf("%q not found", mw.selects.symbolLookup.Text)) + mw.Error(fmt.Errorf("%q not found in binary", mw.selects.symbolLookup.Text)) return } mw.symbolList.Add(sym) @@ -451,6 +455,7 @@ func newDataLogger(mw *MainWindow, device gocan.Adapter) (datalogger.IClient, st LambdaValues: mw.settings.GetWBLLambdaValues(), }, // Remote: mw.selects.remoteSelect.Selected == "Remote", - RemoteMode: mw.selects.remoteSelect.SelectedIndex(), + RemoteMode: mw.selects.remoteSelect.SelectedIndex(), + ExperimentalT5FastLogging: mw.settings.GetExperimentalT5FastLogger(), }) } diff --git a/txlogger.code-workspace b/txlogger.code-workspace index 21f01fe7..780111bf 100644 --- a/txlogger.code-workspace +++ b/txlogger.code-workspace @@ -1,19 +1,27 @@ { "folders": [ { + "name": "txlogger", "path": "." }, { - "path": "../gocan" + "name": "ecusymbol", + "path": "../ecusymbol" }, { - "path": "../ecusymbol" + "name": "gocan", + "path": "../gocan" }, { + "name": "gocangateway", "path": "../gocangateway" }, { + "name": "txweb", "path": "../txweb" + }, + { + "path": "../../../../../Documents/PlatformIO/Projects/txbridge" } ], "settings": { From f22d00ef43cb2ea62025c02bb15835aa8c381714 Mon Sep 17 00:00:00 2001 From: roffe Date: Thu, 2 Jul 2026 00:07:42 +0200 Subject: [PATCH 095/102] save --- go.mod | 4 +- go.sum | 10 +- pkg/datalogger/t7logger.go | 101 +++++++++++------- pkg/datalogger/txbridgelogger.go | 17 +++ pkg/datalogger/txbridgelogger_t7.go | 64 +++++++---- pkg/ota/firmware.bin | Bin 1011568 -> 1012544 bytes pkg/ota/ota.go | 4 +- pkg/widgets/dial/shader/dial_shader.go | 8 +- .../dualdial/shader/dual_dial_shader.go | 8 +- pkg/widgets/meshgrid/meshgrid_shader.go | 10 +- pkg/widgets/plotter/plotter_shader.go | 12 +-- pkg/widgets/tunnel/tunnel_shader.go | 8 +- 12 files changed, 153 insertions(+), 93 deletions(-) diff --git a/go.mod b/go.mod index db327449..813ec373 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,13 @@ go 1.26.0 replace go.einride.tech/can => github.com/samuelbrian/can-go v0.0.2 require ( - fyne.io/fyne/v2 v2.8.0-rc1.0.20260630205042-248c2ef8b9d7 + fyne.io/fyne/v2 v2.8.0-rc1.0.20260701153857-4a089fc3dab4 fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e github.com/avast/retry-go/v4 v4.7.0 github.com/lusingander/colorpicker v0.7.5 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/roffe/ecusymbol v1.2.5 - github.com/roffe/gocan v1.4.5 + github.com/roffe/gocan v1.4.6 go.bug.st/serial v1.7.1 golang.org/x/image v0.40.0 golang.org/x/mod v0.36.0 diff --git a/go.sum b/go.sum index 9e145a20..97deb48d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -fyne.io/fyne/v2 v2.8.0-rc1.0.20260629184356-6445a38924b2 h1:yGGQ0eH0TPS/pQJtagPoJQ39wBd5+utJ4cq6VZ4Efi8= -fyne.io/fyne/v2 v2.8.0-rc1.0.20260629184356-6445a38924b2/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= -fyne.io/fyne/v2 v2.8.0-rc1.0.20260630205042-248c2ef8b9d7 h1:3qwqxIRpR6OAjELfw9Y3dCwn+bCRgouKIIOG1oZryEA= -fyne.io/fyne/v2 v2.8.0-rc1.0.20260630205042-248c2ef8b9d7/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260701153857-4a089fc3dab4 h1:J9sHdnKvuI4wT8JnTH+vcTPlVtJmSS8EGOiKjYBA/Zk= +fyne.io/fyne/v2 v2.8.0-rc1.0.20260701153857-4a089fc3dab4/go.mod h1:J1MHvPeMxAUF5zRWfGNP3rNRHAK6ZBJ/OiQl4BjzUtY= fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA= fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/x/fyne v0.0.0-20260404122735-cbbdf562353e h1:O6Bll+49ZD/09VbG8mon6saRTIm7aqzzR+7a3548t7E= @@ -132,8 +130,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/roffe/ecusymbol v1.2.5 h1:h1ghjJZcm85+n5P+UjJWCiJDXMgy5BUhaFeKgwRJSss= github.com/roffe/ecusymbol v1.2.5/go.mod h1:Y6vMPbT3P6nVXfUetMZBJKc6N4jPuzpNJfk8bHAfx5Q= -github.com/roffe/gocan v1.4.5 h1:4dLsm9ulGWmpteyEcKWmebnilhaXn3r740DIKAkN/Wg= -github.com/roffe/gocan v1.4.5/go.mod h1:vayI3roc38RKMq5B/yeG+hQt7HUog5Izx8EvpQRrmtg= +github.com/roffe/gocan v1.4.6 h1:t0ID9uLYgR/61XAPUwfhhbItpxi54n36dzqQ/G3QcZA= +github.com/roffe/gocan v1.4.6/go.mod h1:vayI3roc38RKMq5B/yeG+hQt7HUog5Izx8EvpQRrmtg= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= diff --git a/pkg/datalogger/t7logger.go b/pkg/datalogger/t7logger.go index c5e3fec4..2233020a 100644 --- a/pkg/datalogger/t7logger.go +++ b/pkg/datalogger/t7logger.go @@ -24,53 +24,76 @@ func NewT7(cfg Config, lw LogWriter) (IClient, error) { return &T7Client{BaseLogger: NewBaseLogger(cfg, lw)}, nil } +// t7BroadcastIDs are the T7 CAN broadcast frames decodeT7Broadcast understands. +// The txbridge path hands this list to the dongle ('b' command) so it collects and +// folds them into the log stream; generic adapters subscribe to them live below. +var t7BroadcastIDs = []uint16{0x1A0, 0x280, 0x3A0 /*, 0x5C0*/} + +// t7BroadcastSymbols are the symbol names decodeT7Broadcast can produce. On the +// txbridge path, requested symbols in this set are sourced from the folded trailer +// instead of the KWP F0 read. +var t7BroadcastSymbols = map[string]struct{}{ + "ActualIn.n_Engine": {}, + "Out.X_AccPedal": {}, + "Out.X_ActualGear": {}, + "Out.ST_BrakeLight": {}, + "In.ST_ClutchBrake1": {}, + "In.v_Vehicle": {}, + // "ActualIn.T_Engine": {}, // enable together with 0x5C0 in t7BroadcastIDs +} + +// decodeT7Broadcast decodes one raw T7 broadcast frame into sysvars (plus a few +// ebus-only status bits). Shared by the live listener (generic adapters) and the +// txbridge path, which feeds the same bytes from the folded 'r' trailer. Guards on +// length because the folded frames carry their own dlc. +func decodeT7Broadcast(id uint16, data []byte, sysvars *ThreadSafeMap) { + switch id { + case 0x1A0: + if len(data) < 6 { + return + } + rpm := binary.BigEndian.Uint16(data[1:3]) + sysvars.Set("ActualIn.n_Engine", float64(rpm)) + sysvars.Set("Out.X_AccPedal", float64(data[5])) + case 0x280: + if len(data) < 5 { + return + } + sysvars.Set("Out.X_ActualGear", float64(data[1])) + sysvars.Set("Out.ST_BrakeLight", float64(data[2]&0x02>>1)) + sysvars.Set("In.ST_ClutchBrake1", float64(data[2]&0x08>>3)) + ebus.Publish("LIMP", float64(data[3]&0x01)) + ebus.Publish("CRUISE", float64(data[4]&0x20>>5)) + ebus.Publish("CEL", float64(data[4]&0x80>>7)) + case 0x3A0: + if len(data) < 5 { + return + } + speed := uint16(data[4]) | uint16(data[3])<<8 + sysvars.Set("In.v_Vehicle", float64(speed)*0.1) + case 0x5C0: + // 0x5C0 COTE_ECS: Data[1]=coolant. byte=(u8)(V+40) + if len(data) < 2 { + return + } + sysvars.Set("ActualIn.T_Engine", float64(data[1])-40) + } +} + func t7broadcastListener(ctx context.Context, cl *gocan.Client, sysvars *ThreadSafeMap) { - broadcast := cl.Subscribe(ctx, 0x1A0, 0x280, 0x3A0 /*, 0x5C0*/) + ids := make([]uint32, len(t7BroadcastIDs)) + for i, id := range t7BroadcastIDs { + ids[i] = uint32(id) + } + broadcast := cl.Subscribe(ctx, ids...) defer broadcast.Close() - var speed uint16 - var rpm uint16 - var throttle float64 - var realSpeed float64 - var limp, cruise, cel uint8 - var gear uint8 - var clutchBreak, brakeLight uint8 for { select { case <-ctx.Done(): return case msg := <-broadcast.Chan(): - switch msg.Identifier { - case 0x1A0: - rpm = binary.BigEndian.Uint16(msg.Data[1:3]) - throttle = float64(msg.Data[5]) - sysvars.Set("ActualIn.n_Engine", float64(rpm)) - sysvars.Set("Out.X_AccPedal", throttle) - case 0x280: - limp = msg.Data[3] & 0x01 - cel = msg.Data[4] & 0x80 >> 7 - cruise = msg.Data[4] & 0x20 >> 5 - gear = msg.Data[1] - sysvars.Set("Out.X_ActualGear", float64(gear)) - brakeLight = msg.Data[2] & 0x02 >> 1 - sysvars.Set("Out.ST_BrakeLight", float64(brakeLight)) - clutchBreak = msg.Data[2] & 0x08 >> 3 - sysvars.Set("In.ST_ClutchBrake1", float64(clutchBreak)) - - ebus.Publish("LIMP", float64(limp)) - ebus.Publish("CRUISE", float64(cruise)) - ebus.Publish("CEL", float64(cel)) - case 0x3A0: - speed = uint16(msg.Data[4]) | uint16(msg.Data[3])<<8 - realSpeed = float64(speed) * 0.1 - sysvars.Set("In.v_Vehicle", realSpeed) - case 0x5C0: - // 0x5C0 COTE_ECS: Data[1]=coolant. byte=(u8)(V+40), - coolant := float64(msg.Data[1]) - 40 - sysvars.Set("ActualIn.T_Engine", coolant) - // log.Printf("0x5C0: % X valid=%t coolant=%v", msg.Data, msg.Data[0]&0x10 != 0, coolant) - - } + decodeT7Broadcast(uint16(msg.Identifier), msg.Data, sysvars) } } } diff --git a/pkg/datalogger/txbridgelogger.go b/pkg/datalogger/txbridgelogger.go index 002f6dd3..6fa669aa 100644 --- a/pkg/datalogger/txbridgelogger.go +++ b/pkg/datalogger/txbridgelogger.go @@ -6,6 +6,7 @@ import ( "time" "github.com/roffe/gocan" + "github.com/roffe/gocan/pkg/serialcommand" "github.com/roffe/txlogger/pkg/debug" ) @@ -104,6 +105,22 @@ func (c *TxBridge) setECU(cl *gocan.Client, ecuType string) error { return nil } +// sendBroadcastCollect tells the dongle which CAN broadcast ids to cache and fold +// into the 'r' log responses (empty clears). The per-ECU id lists live with each +// ECU's broadcast decoder (e.g. t7BroadcastIDs). +func sendBroadcastCollect(cl *gocan.Client, ids []uint16) error { + data := make([]byte, 0, len(ids)*2) + for _, id := range ids { + data = append(data, byte(id), byte(id>>8)) // 11-bit ids, little-endian + } + cmd := serialcommand.SerialCommand{Command: 'b', Data: data} + payload, err := cmd.MarshalBinary() + if err != nil { + return err + } + return cl.Send(gocan.SystemMsg, payload, gocan.Outgoing) +} + func (c *TxBridge) startLogging(cl *gocan.Client) error { return cl.Send(gocan.SystemMsg, []byte("r"), gocan.Outgoing) } diff --git a/pkg/datalogger/txbridgelogger_t7.go b/pkg/datalogger/txbridgelogger_t7.go index 4f8a28f4..59c8d29a 100644 --- a/pkg/datalogger/txbridgelogger_t7.go +++ b/pkg/datalogger/txbridgelogger_t7.go @@ -5,8 +5,8 @@ import ( "context" "encoding/binary" "fmt" + "io" "log" - "strings" "time" symbol "github.com/roffe/ecusymbol" @@ -20,28 +20,21 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { ctx, cancel := context.WithCancel(pctx) defer cancel() - bctx, bcancel := context.WithCancel(ctx) - defer bcancel() - go t7broadcastListener(bctx, cl, c.sysvars) - - c.OnMessage("Watching for broadcast messages") - <-time.After(1550 * time.Millisecond) - if found := c.sysvars.Keys(); len(found) > 0 { - c.OnMessage(fmt.Sprintf("Found: %s", strings.Join(found, ", "))) - } else { - c.OnMessage("No broadcast messages found, stopping broadcast listener") - bcancel() - } - if c.lamb != nil { defer c.lamb.Stop() } + // Tell the dongle to collect the T7 broadcast frames and fold them into the log + // stream, then source those symbols from the folded trailer (decodeT7Broadcast) + // instead of the KWP F0 read, rather than parsing a live broadcast flood here. + c.OnMessage("Enabling T7 broadcast collection") + if err := sendBroadcastCollect(cl, t7BroadcastIDs); err != nil { + return fmt.Errorf("failed to set broadcast collect: %w", err) + } for _, sym := range c.Symbols { - if c.sysvars.Exists(sym.Name) { + if _, ok := t7BroadcastSymbols[sym.Name]; ok { log.Println("Skipping", sym.Name, "in broadcast") sym.Number = -1 - continue } } @@ -210,12 +203,13 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { return } lastData = time.Now() - if msg.DLC() != int(expectedPayloadSize+4) { + // timestamp(4) + fixed symbol payload, then a variable broadcast trailer + // the dongle folded in (see decodeT7Broadcast), so this is a lower bound. + if msg.DLC() < int(expectedPayloadSize+4) { c.onError() - c.OnMessage(fmt.Sprintf("expected %d bytes, got %d", expectedPayloadSize+4, msg.DLC())) + c.OnMessage(fmt.Sprintf("expected at least %d bytes, got %d", expectedPayloadSize+4, msg.DLC())) // log.Printf("unexpected data %X", msg.Data) continue - // return fmt.Errorf("expected %d bytes, got %d", expectedPayloadSize, len(databuff)) } r := bytes.NewReader(msg.Data) @@ -233,15 +227,18 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { timeStamp := c.calculateCompensatedTimestamp() + // Read the fixed symbol payload first; broadcast (-1) symbols carry no + // bytes here — they come from the trailer parsed just below. + readErr := false for _, va := range c.Symbols { if va.Number == -1 { - ebus.Publish(va.Name, c.sysvars.Get(va.Name)) continue } if err := va.Read(r); err != nil { log.Printf("data ex %d %X len %d", expectedPayloadSize, msg.Data, msg.DLC()) c.onError() c.OnMessage(err.Error()) + readErr = true break } @@ -251,9 +248,32 @@ func (c *TxBridge) t7(pctx context.Context, cl *gocan.Client) error { ebus.Publish(va.Name, va.Float64()) } + if readErr { + continue // r is misaligned; skip the trailer for this frame + } + + // Drain the folded broadcast trailer: [idHi, idLo, dlc, data...] per + // collected frame -> update sysvars via the shared T7 decoder. + var bcbuf [8]byte + for r.Len() >= 3 { + idHi, _ := r.ReadByte() + idLo, _ := r.ReadByte() + dlc, _ := r.ReadByte() + if dlc > 8 || r.Len() < int(dlc) { + c.OnMessage("malformed broadcast trailer") + break + } + if _, err := io.ReadFull(r, bcbuf[:dlc]); err != nil { + break + } + decodeT7Broadcast(uint16(idHi)<<8|uint16(idLo), bcbuf[:dlc], c.sysvars) + } - if r.Len() > 0 { - c.OnMessage(fmt.Sprintf("%d leftover bytes!", r.Len())) + // Publish the broadcast-sourced symbols from the freshly updated sysvars. + for _, va := range c.Symbols { + if va.Number == -1 { + ebus.Publish(va.Name, c.sysvars.Get(va.Name)) + } } if c.lamb != nil { diff --git a/pkg/ota/firmware.bin b/pkg/ota/firmware.bin index 3c1465df866e140daa06a955e4d67a5d15033cb6..a578e3c1992b9a7e440498660c198fe71833dace 100644 GIT binary patch delta 176374 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 b>lAn6he|v5S;${|ym@Q0OOd7i)Id7`Fl;+Y delta 174814 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�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 literal 845376 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