mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-25 09:50:27 -04:00
refactor(tracker/gioui): unify default & user config yaml handling
This commit is contained in:
parent
5b260d19f5
commit
32f1e1baea
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user