Skip to content

Commit

Permalink
Add per widget update intervals with possible configuration in deck f…
Browse files Browse the repository at this point in the history
…iles (#31)
  • Loading branch information
zMoooooritz authored Jun 7, 2021
1 parent 77d9326 commit 30aba0a
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 62 deletions.
5 changes: 3 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 6 additions & 1 deletion deck.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
4 changes: 1 addition & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 {
Expand Down
65 changes: 44 additions & 21 deletions widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"log"
"os"
"strconv"
"sync"
"time"

"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -70,45 +78,49 @@ 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",
font: "bold;regular;thin",
}

case "date":
bw.setInterval(wc.Interval, 1000)
return &TimeWidget{
BaseWidget: *bw,
format: "%l;%d;%M",
font: "regular;bold;regular",
}

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)
}
Expand All @@ -118,24 +130,26 @@ 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
}

// 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)
Expand All @@ -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 {
Expand Down
67 changes: 32 additions & 35 deletions widget_button.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"image"
"path/filepath"
"sync"

"github.com/muesli/streamdeck"
)
Expand All @@ -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)
}
9 changes: 9 additions & 0 deletions widget_recent_window.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down

0 comments on commit 30aba0a

Please sign in to comment.