From 30aba0a9293c576badac2fa5dc9be017b358315d Mon Sep 17 00:00:00 2001 From: Moritz Biering <51382157+zMoooooritz@users.noreply.github.com> Date: Mon, 7 Jun 2021 02:13:14 +0200 Subject: [PATCH] Add per widget update intervals with possible configuration in deck files (#31) --- config.go | 5 +-- deck.go | 7 ++++- main.go | 4 +-- widget.go | 65 ++++++++++++++++++++++++++------------- widget_button.go | 67 ++++++++++++++++++++--------------------- widget_recent_window.go | 9 ++++++ 6 files changed, 95 insertions(+), 62 deletions(-) diff --git a/config.go b/config.go index 97a42db..3ee4eef 100644 --- a/config.go +++ b/config.go @@ -26,8 +26,9 @@ type ActionConfig struct { // WidgetConfig describes configuration data for widgets. type WidgetConfig struct { - ID string `toml:"id,omitempty"` - Config map[string]string `toml:"config,omitempty"` + ID string `toml:"id,omitempty"` + Interval uint `toml:"interval,omitempty"` + Config map[string]string `toml:"config,omitempty"` } // KeyConfig holds the entire configuration for a single key. diff --git a/deck.go b/deck.go index 6647df7..231660d 100644 --- a/deck.go +++ b/deck.go @@ -60,7 +60,7 @@ func LoadDeck(dev *streamdeck.Device, base string, deck string) (*Deck, error) { var w Widget if k, found := keyMap[i]; found { - w = NewWidget(k.Index, k.Widget.ID, k.Action, k.ActionHold, bg, k.Widget.Config) + w = NewWidget(k, bg) } else { w = NewBaseWidget(i, nil, nil, bg) } @@ -241,6 +241,11 @@ func (d *Deck) triggerAction(dev *streamdeck.Device, index uint8, hold bool) { // updateWidgets updates/repaints all the widgets. func (d *Deck) updateWidgets(dev *streamdeck.Device) { for _, w := range d.Widgets { + if !w.RequiresUpdate() { + continue + } + + // log.Printf("Repaint %d", w.Key()) if err := w.Update(dev); err != nil { log.Fatalf("error: %v", err) } diff --git a/main.go b/main.go index b0236c1..1d1c79a 100644 --- a/main.go +++ b/main.go @@ -138,7 +138,7 @@ func main() { } for { select { - case <-time.After(900 * time.Millisecond): + case <-time.After(100 * time.Millisecond): deck.updateWidgets(&dev) case k, ok := <-kch: @@ -149,13 +149,11 @@ func main() { } continue } - // spew.Dump(k) var state bool if ks, ok := keyStates.Load(k.Index); ok { state = ks.(bool) } - // log.Println("Storing state", k.Pressed) keyStates.Store(k.Index, k.Pressed) if state && !k.Pressed { diff --git a/widget.go b/widget.go index d51c2df..941846f 100644 --- a/widget.go +++ b/widget.go @@ -7,7 +7,7 @@ import ( "log" "os" "strconv" - "sync" + "time" "github.com/golang/freetype" "github.com/golang/freetype/truetype" @@ -18,6 +18,7 @@ import ( // Widget is an interface implemented by all available widgets. type Widget interface { Key() uint8 + RequiresUpdate() bool Update(dev *streamdeck.Device) error Action() *ActionConfig ActionHold() *ActionConfig @@ -30,7 +31,8 @@ type BaseWidget struct { action *ActionConfig actionHold *ActionConfig background image.Image - init *sync.Once + lastUpdate time.Time + interval uint } // Key returns the key a widget is mapped to. @@ -53,14 +55,20 @@ func (w *BaseWidget) TriggerAction() { // just a stub } +// RequiresUpdate returns true when the widget wants to be repainted. +func (w *BaseWidget) RequiresUpdate() bool { + if !w.lastUpdate.IsZero() && // initial paint done + (w.interval == 0 || // never to be repainted + time.Since(w.lastUpdate) < time.Duration(w.interval)*time.Millisecond) { + return false + } + + return true +} + // Update renders the widget. func (w *BaseWidget) Update(dev *streamdeck.Device) error { - var err error - w.init.Do(func() { - err = w.render(dev, nil) - }) - - return err + return w.render(dev, nil) } // NewBaseWidget returns a new BaseWidget. @@ -70,23 +78,25 @@ func NewBaseWidget(index uint8, action, actionHold *ActionConfig, bg image.Image action: action, actionHold: actionHold, background: bg, - init: &sync.Once{}, } } // NewWidget initializes a widget. -func NewWidget(index uint8, id string, action, actionHold *ActionConfig, bg image.Image, config map[string]string) Widget { - bw := NewBaseWidget(index, action, actionHold, bg) +func NewWidget(kc KeyConfig, bg image.Image) Widget { + bw := NewBaseWidget(kc.Index, kc.Action, kc.ActionHold, bg) + wc := kc.Widget - switch id { + switch wc.ID { case "button": + bw.setInterval(wc.Interval, 0) return &ButtonWidget{ BaseWidget: *bw, - icon: config["icon"], - label: config["label"], + icon: wc.Config["icon"], + label: wc.Config["label"], } case "clock": + bw.setInterval(wc.Interval, 1000) return &TimeWidget{ BaseWidget: *bw, format: "%H;%i;%s", @@ -94,6 +104,7 @@ func NewWidget(index uint8, id string, action, actionHold *ActionConfig, bg imag } case "date": + bw.setInterval(wc.Interval, 1000) return &TimeWidget{ BaseWidget: *bw, format: "%l;%d;%M", @@ -101,14 +112,15 @@ func NewWidget(index uint8, id string, action, actionHold *ActionConfig, bg imag } case "time": + bw.setInterval(wc.Interval, 1000) return &TimeWidget{ BaseWidget: *bw, - format: config["format"], - font: config["font"], + format: wc.Config["format"], + font: wc.Config["font"], } case "recentWindow": - i, err := strconv.ParseUint(config["window"], 10, 64) + i, err := strconv.ParseUint(wc.Config["window"], 10, 64) if err != nil { log.Fatal(err) } @@ -118,15 +130,16 @@ func NewWidget(index uint8, id string, action, actionHold *ActionConfig, bg imag } case "top": + bw.setInterval(wc.Interval, 500) return &TopWidget{ BaseWidget: *bw, - mode: config["mode"], - fillColor: config["fillColor"], + mode: wc.Config["mode"], + fillColor: wc.Config["fillColor"], } default: // unknown widget ID - log.Println("Unknown widget with ID:", id) + log.Println("Unknown widget with ID:", wc.ID) } return nil @@ -134,8 +147,9 @@ func NewWidget(index uint8, id string, action, actionHold *ActionConfig, bg imag // renders the widget including its background image. func (w *BaseWidget) render(dev *streamdeck.Device, fg image.Image) error { - pixels := int(dev.Pixels) + w.lastUpdate = time.Now() + pixels := int(dev.Pixels) img := image.NewRGBA(image.Rect(0, 0, pixels, pixels)) if w.background != nil { draw.Draw(img, img.Bounds(), w.background, image.Point{}, draw.Over) @@ -147,6 +161,15 @@ func (w *BaseWidget) render(dev *streamdeck.Device, fg image.Image) error { return dev.SetImage(w.key, img) } +// change the interval a widget gets rendered in. +func (w *BaseWidget) setInterval(interval uint, defaultInterval uint) { + if interval == 0 { + interval = defaultInterval + } + + w.interval = interval +} + func drawImage(img *image.RGBA, path string, size int, pt image.Point) error { f, err := os.Open(path) if err != nil { diff --git a/widget_button.go b/widget_button.go index 912b97a..83c6440 100644 --- a/widget_button.go +++ b/widget_button.go @@ -3,7 +3,6 @@ package main import ( "image" "path/filepath" - "sync" "github.com/muesli/streamdeck" ) @@ -14,52 +13,50 @@ type ButtonWidget struct { icon string label string fontsize float64 - - init sync.Once } // Update renders the widget. func (w *ButtonWidget) Update(dev *streamdeck.Device) error { - var err error - w.init.Do(func() { - size := int(dev.Pixels) - margin := size / 18 - height := size - (margin * 2) - img := image.NewRGBA(image.Rect(0, 0, size, size)) + size := int(dev.Pixels) + margin := size / 18 + height := size - (margin * 2) + img := image.NewRGBA(image.Rect(0, 0, size, size)) - if w.label != "" { - iconsize := int((float64(height) / 3.0) * 2.0) - bounds := img.Bounds() + if w.label != "" { + iconsize := int((float64(height) / 3.0) * 2.0) + bounds := img.Bounds() - if w.icon != "" { - err = drawImage(img, - findImage(filepath.Dir(deck.File), w.icon), - iconsize, - image.Pt(-1, margin)) + if w.icon != "" { + err := drawImage(img, + findImage(filepath.Dir(deck.File), w.icon), + iconsize, + image.Pt(-1, margin)) - bounds.Min.Y += iconsize + margin - bounds.Max.Y -= margin + if err != nil { + return err } - drawString(img, - bounds, - ttfFont, - w.label, - dev.DPI, - w.fontsize, - image.Pt(-1, -1)) - } else if w.icon != "" { - err = drawImage(img, - findImage(filepath.Dir(deck.File), w.icon), - height, - image.Pt(-1, -1)) + bounds.Min.Y += iconsize + margin + bounds.Max.Y -= margin } + drawString(img, + bounds, + ttfFont, + w.label, + dev.DPI, + w.fontsize, + image.Pt(-1, -1)) + } else if w.icon != "" { + err := drawImage(img, + findImage(filepath.Dir(deck.File), w.icon), + height, + image.Pt(-1, -1)) + if err != nil { - return + return err } - err = w.render(dev, img) - }) + } - return err + return w.render(dev, img) } diff --git a/widget_recent_window.go b/widget_recent_window.go index 06196b9..b96db2e 100644 --- a/widget_recent_window.go +++ b/widget_recent_window.go @@ -17,6 +17,15 @@ type RecentWindowWidget struct { lastClass string } +// RequiresUpdate returns true when the widget wants to be repainted. +func (w *RecentWindowWidget) RequiresUpdate() bool { + if int(w.window) < len(recentWindows) { + return w.lastClass != recentWindows[w.window].Class + } + + return false +} + // Update renders the widget. func (w *RecentWindowWidget) Update(dev *streamdeck.Device) error { img := image.NewRGBA(image.Rect(0, 0, int(dev.Pixels), int(dev.Pixels)))