refactor(tracker/gioui): unify default & user config yaml handling

This commit is contained in:
5684185+vsariola@users.noreply.github.com 2025-05-23 23:35:51 +03:00
parent 5b260d19f5
commit 32f1e1baea
4 changed files with 58 additions and 73 deletions

View File

@ -24,33 +24,17 @@ type (
var keyBindingMap = map[key.Event]string{}
var keyActionMap = map[KeyAction]string{} // holds an informative string of the first key bound to an action
func loadCustomKeyBindings() []KeyBinding {
var keyBindings []KeyBinding
_, err := ReadCustomConfigYml("keybindings.yml", &keyBindings)
if err != nil {
return nil
}
if len(keyBindings) == 0 {
return nil
}
return keyBindings
}
//go:embed keybindings.yml
var defaultKeyBindingsYaml []byte
func loadDefaultKeyBindings() []KeyBinding {
var keyBindings []KeyBinding
err := yaml.UnmarshalStrict(defaultKeyBindingsYaml, &keyBindings)
if err != nil {
panic(fmt.Errorf("failed to unmarshal keybindings: %w", err))
}
return keyBindings
}
var defaultKeyBindings []byte
func init() {
keyBindings := loadDefaultKeyBindings()
keyBindings = append(keyBindings, loadCustomKeyBindings()...)
var keyBindings, userKeybindings []KeyBinding
if err := yaml.UnmarshalStrict(defaultKeyBindings, &keyBindings); err != nil {
panic(fmt.Errorf("failed to unmarshal default keybindings: %w", err))
}
if err := ReadCustomConfig("keybindings.yml", &userKeybindings); err == nil {
keyBindings = append(keyBindings, userKeybindings...)
}
for _, kb := range keyBindings {
var mods key.Modifiers

View File

@ -13,8 +13,7 @@ import (
type (
Preferences struct {
Window WindowPreferences
YmlError error
Window WindowPreferences
}
WindowPreferences struct {
@ -25,39 +24,39 @@ type (
)
//go:embed preferences.yml
var defaultPreferencesYaml []byte
var defaultPreferences []byte
func loadDefaultPreferences() Preferences {
var preferences Preferences
err := yaml.UnmarshalStrict(defaultPreferencesYaml, &preferences)
if err != nil {
panic(fmt.Errorf("failed to unmarshal preferences: %w", err))
}
return preferences
}
// ReadCustomConfigYml modifies the target argument, i.e. needs a pointer
func ReadCustomConfigYml(filename string, target interface{}) (exists bool, err error) {
// ReadCustomConfig modifies the target argument, i.e. needs a pointer. Just
// fails silently if the file cannot be found/read, but will warn about
// malformed files.
func ReadCustomConfig(filename string, target any) error {
configDir, err := os.UserConfigDir()
if err != nil {
return false, err
return nil
}
path := filepath.Join(configDir, "sointu", filename)
bytes, err2 := os.ReadFile(path)
if err2 != nil {
return false, err2
bytes, err := os.ReadFile(path)
if err != nil {
return nil
}
err = yaml.Unmarshal(bytes, target)
return true, err
if err := yaml.Unmarshal(bytes, target); err != nil {
return fmt.Errorf("ReadCustomConfig %v: %w", filename, err)
}
return nil
}
func MakePreferences() Preferences {
preferences := loadDefaultPreferences()
exists, err := ReadCustomConfigYml("preferences.yml", &preferences)
if exists {
preferences.YmlError = err
// ReadConfig first unmarshals the defaultConfig which should be the embedded
// default config, and then tries to read the custom config with
// ReadCustomConfig. It panics right away if the embedded defaultConfig could
// not be parsed as yaml as this should never happen except during development.
// The returned error should be treated as a warning: this function will always
// return at least the default config, and the warning will just tell if there
// was a problem parsing the custom config.
func ReadConfig(defaultConfig []byte, path string, target any) (warn error) {
if err := yaml.UnmarshalStrict(defaultConfig, target); err != nil {
panic(fmt.Errorf("ReadConfig %v failed to unmarshal the embedded default config: %w", path, err))
}
return preferences
return ReadCustomConfig(path, target)
}
func (p Preferences) WindowSize() (unit.Dp, unit.Dp) {

View File

@ -2,14 +2,12 @@ package gioui
import (
_ "embed"
"fmt"
"image/color"
"gioui.org/text"
"gioui.org/widget"
"gioui.org/widget/material"
"golang.org/x/exp/shiny/materialdesign/icons"
"gopkg.in/yaml.v2"
)
type Theme struct {
@ -120,19 +118,16 @@ type CursorStyle struct {
//go:embed theme.yml
var defaultTheme []byte
func NewTheme() *Theme {
var theme Theme
err := yaml.UnmarshalStrict(defaultTheme, &theme)
if err != nil {
panic(fmt.Errorf("failed to default theme: %w", err))
}
ReadCustomConfigYml("theme.yml", &theme)
theme.Material.Shaper = &text.Shaper{}
theme.Material.Icon.CheckBoxChecked = must(widget.NewIcon(icons.ToggleCheckBox))
theme.Material.Icon.CheckBoxUnchecked = must(widget.NewIcon(icons.ToggleCheckBoxOutlineBlank))
theme.Material.Icon.RadioChecked = must(widget.NewIcon(icons.ToggleRadioButtonChecked))
theme.Material.Icon.RadioUnchecked = must(widget.NewIcon(icons.ToggleRadioButtonUnchecked))
return &theme
// NewTheme returns a new theme and potentially a warning if the theme file was not found or could not be read
func NewTheme() (*Theme, error) {
var ret Theme
warn := ReadConfig(defaultTheme, "theme.yml", &ret)
ret.Material.Shaper = &text.Shaper{}
ret.Material.Icon.CheckBoxChecked = must(widget.NewIcon(icons.ToggleCheckBox))
ret.Material.Icon.CheckBoxUnchecked = must(widget.NewIcon(icons.ToggleCheckBoxOutlineBlank))
ret.Material.Icon.RadioChecked = must(widget.NewIcon(icons.ToggleRadioButtonChecked))
ret.Material.Icon.RadioUnchecked = must(widget.NewIcon(icons.ToggleRadioButtonUnchecked))
return &ret, warn
}
func must[T any](ic T, err error) T {

View File

@ -71,7 +71,6 @@ var ZoomFactors = []float32{.25, 1. / 3, .5, 2. / 3, .75, .8, 1, 1.1, 1.25, 1.5,
func NewTracker(model *tracker.Model) *Tracker {
t := &Tracker{
Theme: NewTheme(),
OctaveNumberInput: NewNumberInput(model.Octave().Int()),
InstrumentVoices: NewNumberInput(model.InstrumentVoices().Int()),
@ -93,15 +92,23 @@ func NewTracker(model *tracker.Model) *Tracker {
Model: model,
filePathString: model.FilePath().String(),
preferences: MakePreferences(),
}
t.PopupAlert = NewPopupAlert(model.Alerts())
var warn error
if t.Theme, warn = NewTheme(); warn != nil {
model.Alerts().AddAlert(tracker.Alert{
Priority: tracker.Warning,
Message: warn.Error(),
Duration: 10 * time.Second,
})
}
t.Theme.Material.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
t.PopupAlert = NewPopupAlert(model.Alerts())
if t.preferences.YmlError != nil {
model.Alerts().Add(
fmt.Sprintf("Preferences YML Error: %s", t.preferences.YmlError),
tracker.Warning,
)
if warn := ReadConfig(defaultPreferences, "preferences.yml", &t.preferences); warn != nil {
model.Alerts().AddAlert(tracker.Alert{
Priority: tracker.Warning,
Message: warn.Error(),
Duration: 10 * time.Second,
})
}
t.TrackEditor.scrollTable.Focus()
return t