Skip to content

Commit

Permalink
load and manage pages in HamDeck
Browse files Browse the repository at this point in the history
  • Loading branch information
ftl committed Dec 9, 2023
1 parent 049b6a3 commit a9afcb5
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 27 deletions.
115 changes: 91 additions & 24 deletions pkg/hamdeck/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
const (
ConfigDefaultFilename = "hamdeck.json"
ConfigMainKey = "hamdeck"
ConfigStartPageID = "start_page"
ConfigPages = "pages"
ConfigButtons = "buttons"
ConfigType = "type"
ConfigIndex = "index"
Expand All @@ -25,43 +27,106 @@ func (d *HamDeck) ReadConfig(r io.Reader) error {
return fmt.Errorf("cannot read the configuration: %w", err)
}

var rawData interface{}
var rawData any
err = json.Unmarshal(buffer.Bytes(), &rawData)
if err != nil {
return fmt.Errorf("cannot unmarshal the configuration: %w", err)
}

configuration, ok := rawData.(map[string]interface{})
configuration, ok := rawData.(map[string]any)
if !ok {
return fmt.Errorf("configuration is of wrong type: %T", rawData)
}
effectiveConfiguration := findEffectiveConfiguration(configuration)

d.buttonsPerFactory = make([]int, len(d.factories))
d.pages = make(map[string]Page)
d.startPageID, ok = effectiveConfiguration[ConfigStartPageID].(string)
if !ok {
d.startPageID = legacyPageID
}

pages, ok := effectiveConfiguration[ConfigPages].(map[string]any)
if ok {
err = d.loadPages(pages)
}
if err != nil {
return err
}

buttons, ok := effectiveConfiguration[ConfigButtons].([]any)
if ok {
err = d.loadLegacyPage(buttons)
}
if err != nil {
return err
}

return d.AttachPage(d.startPageID)
}

func findEffectiveConfiguration(configuration map[string]any) map[string]any {
rawSubconfiguration, ok := configuration[ConfigMainKey]
if !ok {
return d.attachConfiguredButtons(configuration)
return configuration
}

subconfiguration, ok := rawSubconfiguration.(map[string]interface{})
subconfiguration, ok := rawSubconfiguration.(map[string]any)
if !ok {
return d.attachConfiguredButtons(configuration)
return configuration
}
return d.attachConfiguredButtons(subconfiguration)
return subconfiguration
}

func (d *HamDeck) attachConfiguredButtons(configuration map[string]interface{}) error {
rawButtons, ok := configuration[ConfigButtons]
if !ok {
return fmt.Errorf("configuration contains no 'buttons' key")
func (d *HamDeck) loadPages(configuration map[string]any) error {
for id, rawPage := range configuration {
pageConfiguration, ok := rawPage.(map[string]any)
if !ok {
return fmt.Errorf("%s is not a valid page", id)
}

page, err := d.loadPage(id, pageConfiguration)
if err != nil {
return err
}

d.pages[id] = page
}
return nil
}

buttons, ok := rawButtons.([]interface{})
func (d *HamDeck) loadPage(id string, configuration map[string]any) (Page, error) {
buttonsConfiguration, ok := configuration[ConfigButtons].([]any)
if !ok {
return fmt.Errorf("'buttons' is not a list of button objects")
return Page{}, fmt.Errorf("page %s has no buttons defined", id)
}

d.buttonsPerFactory = make([]int, len(d.factories))
for i, rawButtonConfig := range buttons {
buttonConfig, ok := rawButtonConfig.(map[string]interface{})
buttons, err := d.loadButtons(buttonsConfiguration)
if err != nil {
return Page{}, err
}

return Page{
buttons: buttons,
}, nil
}

func (d *HamDeck) loadLegacyPage(configuration []any) error {
buttons, err := d.loadButtons(configuration)
if err != nil {
return err
}

d.pages[legacyPageID] = Page{
buttons: buttons,
}
return nil
}

func (d *HamDeck) loadButtons(configuration []any) ([]Button, error) {
result := make([]Button, len(d.buttons))
for i, rawButtonConfig := range configuration {
buttonConfig, ok := rawButtonConfig.(map[string]any)
if !ok {
log.Printf("buttons[%d] is not a button object", i)
continue
Expand All @@ -72,6 +137,9 @@ func (d *HamDeck) attachConfiguredButtons(configuration map[string]interface{})
log.Printf("buttons[%d] has no valid index", i)
continue
}
if buttonIndex <= 0 || buttonIndex >= len(d.buttons) {
log.Printf("%d is not a valid button index in [0, %d])", buttonIndex, len(d.buttons))
}

var button Button
for j, factory := range d.factories {
Expand All @@ -86,10 +154,9 @@ func (d *HamDeck) attachConfiguredButtons(configuration map[string]interface{})
continue
}

d.Attach(buttonIndex, button)
result[buttonIndex] = button
}

return nil
return result, nil
}

func (d *HamDeck) CloseUnusedFactories() {
Expand All @@ -100,7 +167,7 @@ func (d *HamDeck) CloseUnusedFactories() {
}
}

func ToInt(raw interface{}) (int, bool) {
func ToInt(raw any) (int, bool) {
if raw == nil {
return 0, false
}
Expand All @@ -120,7 +187,7 @@ func ToInt(raw interface{}) (int, bool) {
}
}

func ToFloat(raw interface{}) (float64, bool) {
func ToFloat(raw any) (float64, bool) {
if raw == nil {
return 0, false
}
Expand All @@ -140,7 +207,7 @@ func ToFloat(raw interface{}) (float64, bool) {
}
}

func ToBool(raw interface{}) (bool, bool) {
func ToBool(raw any) (bool, bool) {
if raw == nil {
return false, false
}
Expand All @@ -158,7 +225,7 @@ func ToBool(raw interface{}) (bool, bool) {
}
}

func ToString(raw interface{}) (string, bool) {
func ToString(raw any) (string, bool) {
if raw == nil {
return "", false
}
Expand All @@ -174,11 +241,11 @@ func ToString(raw interface{}) (string, bool) {
}
}

func ToStringArray(raw interface{}) ([]string, bool) {
func ToStringArray(raw any) ([]string, bool) {
if raw == nil {
return []string{}, false
}
rawValues, ok := raw.([]interface{})
rawValues, ok := raw.([]any)
if !ok {
return []string{}, false
}
Expand Down
37 changes: 34 additions & 3 deletions pkg/hamdeck/hamdeck.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ type ButtonFactory interface {
CreateButton(config map[string]interface{}) Button
}

const legacyPageID = ""

type HamDeck struct {
device Device
drawLock *sync.Mutex
Expand All @@ -93,6 +95,13 @@ type HamDeck struct {
flashOn bool
factories []ButtonFactory
buttonsPerFactory []int

startPageID string
pages map[string]Page
}

type Page struct {
buttons []Button
}

func New(device Device) *HamDeck {
Expand All @@ -102,6 +111,7 @@ func New(device Device) *HamDeck {
drawLock: new(sync.Mutex),
gc: NewGraphicContext(device.Pixels()),
buttons: make([]Button, buttonCount),
pages: make(map[string]Page),
}
result.noButton = &noButton{image: result.gc.DrawNoButton()}
for i := range result.buttons {
Expand Down Expand Up @@ -136,11 +146,32 @@ func (d *HamDeck) Redraw(index int, redrawImages bool) {
d.device.SetImage(index, d.buttons[index].Image(d.gc, redrawImages))
}

func (d *HamDeck) AttachPage(id string) error {
page, ok := d.pages[id]
if !ok {
return fmt.Errorf("no page defined with name %s", id)
}

for i, button := range page.buttons {
d.Attach(i, button)
}

return nil
}

func (d *HamDeck) Attach(index int, button Button) {
d.buttons[index] = button
if d.buttons[index] != d.noButton {
d.buttons[index].Detached()
}

if button != nil {
d.buttons[index] = button
ctx := &buttonContext{index: index, deck: d}
button.Attached(ctx)
} else {
d.buttons[index] = d.noButton
}

ctx := &buttonContext{index: index, deck: d}
button.Attached(ctx)
d.Redraw(index, true)
}

Expand Down
128 changes: 128 additions & 0 deletions pkg/hamdeck/pages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package hamdeck

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestReadConfig_SinglePage(t *testing.T) {
runWithConfigString(t, `{
"start_page": "main",
"pages": {
"main": {
"buttons": [
{ "type": "test.Button", "index": 0, "some_config": "some_value" }
]
}
}
}`, func(t *testing.T, deck *HamDeck, device *testDevice, _ chan struct{}) {
assert.Equal(t, "main", deck.startPageID)
assert.Equal(t, 1, len(deck.pages))

page := deck.pages["main"]
require.Equal(t, len(deck.buttons), len(page.buttons))
button, ok := page.buttons[0].(*testButton)
require.True(t, ok)
assert.Equal(t, "some_value", button.config["some_config"])

assert.Same(t, button, deck.buttons[0])
assert.True(t, button.attached)
})
}

func TestReadConfig_SinglePagePlusLegacy(t *testing.T) {
runWithConfigString(t, `{
"pages": {
"main": {
"buttons": [
{ "type": "test.Button", "index": 0, "some_config": "some_value" }
]
}
},
"buttons": [
{ "type": "test.Button", "index": 1, "legacy_config": "legacy_value" }
]
}`, func(t *testing.T, deck *HamDeck, device *testDevice, _ chan struct{}) {
assert.Equal(t, legacyPageID, deck.startPageID)
assert.Equal(t, 2, len(deck.pages))

mainPage := deck.pages["main"]
require.Equal(t, len(deck.buttons), len(mainPage.buttons))
button, ok := mainPage.buttons[0].(*testButton)
require.True(t, ok)
assert.Equal(t, "some_value", button.config["some_config"])

legacyPage := deck.pages[legacyPageID]
require.Equal(t, len(deck.buttons), len(legacyPage.buttons))
button, ok = legacyPage.buttons[1].(*testButton)
require.True(t, ok)
assert.Equal(t, "legacy_value", button.config["legacy_config"])

assert.Same(t, button, deck.buttons[1])
assert.True(t, button.attached)
})
}

func TestReadConfig_OnlyLegacy(t *testing.T) {
runWithConfigString(t, `{
"buttons": [
{ "type": "test.Button", "index": 1, "legacy_config": "legacy_value" }
]
}`, func(t *testing.T, deck *HamDeck, device *testDevice, _ chan struct{}) {
assert.Equal(t, legacyPageID, deck.startPageID)
assert.Equal(t, 1, len(deck.pages))

legacyPage := deck.pages[legacyPageID]
require.Equal(t, len(deck.buttons), len(legacyPage.buttons))
button, ok := legacyPage.buttons[1].(*testButton)
require.True(t, ok)
assert.Equal(t, "legacy_value", button.config["legacy_config"])

assert.Same(t, button, deck.buttons[1])
assert.True(t, button.attached)
})
}

func TestAttachPage(t *testing.T) {
runWithConfigString(t, `{
"pages": {
"main": {
"buttons": [
{ "type": "test.Button", "index": 0, "some_config": "some_value" }
]
}
},
"buttons": [
{ "type": "test.Button", "index": 1, "legacy_config": "legacy_value" }
]
}`, func(t *testing.T, deck *HamDeck, device *testDevice, _ chan struct{}) {
assert.Equal(t, legacyPageID, deck.startPageID)
assert.Equal(t, 2, len(deck.pages))

mainPage := deck.pages["main"]
require.Equal(t, len(deck.buttons), len(mainPage.buttons))
mainButton, ok := mainPage.buttons[0].(*testButton)
require.True(t, ok)
assert.Equal(t, "some_value", mainButton.config["some_config"])

legacyPage := deck.pages[legacyPageID]
require.Equal(t, len(deck.buttons), len(legacyPage.buttons))
legacyButton, ok := legacyPage.buttons[1].(*testButton)
require.True(t, ok)
assert.Equal(t, "legacy_value", legacyButton.config["legacy_config"])

assert.Same(t, legacyButton, deck.buttons[1])
assert.True(t, legacyButton.attached)

err := deck.AttachPage("main")
assert.NoError(t, err)

assert.True(t, legacyButton.detached)
assert.True(t, mainButton.attached)

assert.Same(t, mainButton, deck.buttons[0])
assert.Same(t, deck.noButton, deck.buttons[1])
})
}

0 comments on commit a9afcb5

Please sign in to comment.