mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -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 keyBindingMap = map[key.Event]string{}
|
||||||
var keyActionMap = map[KeyAction]string{} // holds an informative string of the first key bound to an action
|
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
|
//go:embed keybindings.yml
|
||||||
var defaultKeyBindingsYaml []byte
|
var defaultKeyBindings []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
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
keyBindings := loadDefaultKeyBindings()
|
var keyBindings, userKeybindings []KeyBinding
|
||||||
keyBindings = append(keyBindings, loadCustomKeyBindings()...)
|
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 {
|
for _, kb := range keyBindings {
|
||||||
var mods key.Modifiers
|
var mods key.Modifiers
|
||||||
|
@ -13,8 +13,7 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
Preferences struct {
|
Preferences struct {
|
||||||
Window WindowPreferences
|
Window WindowPreferences
|
||||||
YmlError error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowPreferences struct {
|
WindowPreferences struct {
|
||||||
@ -25,39 +24,39 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
//go:embed preferences.yml
|
//go:embed preferences.yml
|
||||||
var defaultPreferencesYaml []byte
|
var defaultPreferences []byte
|
||||||
|
|
||||||
func loadDefaultPreferences() Preferences {
|
// ReadCustomConfig modifies the target argument, i.e. needs a pointer. Just
|
||||||
var preferences Preferences
|
// fails silently if the file cannot be found/read, but will warn about
|
||||||
err := yaml.UnmarshalStrict(defaultPreferencesYaml, &preferences)
|
// malformed files.
|
||||||
if err != nil {
|
func ReadCustomConfig(filename string, target any) error {
|
||||||
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) {
|
|
||||||
configDir, err := os.UserConfigDir()
|
configDir, err := os.UserConfigDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil
|
||||||
}
|
}
|
||||||
path := filepath.Join(configDir, "sointu", filename)
|
path := filepath.Join(configDir, "sointu", filename)
|
||||||
bytes, err2 := os.ReadFile(path)
|
bytes, err := os.ReadFile(path)
|
||||||
if err2 != nil {
|
if err != nil {
|
||||||
return false, err2
|
return nil
|
||||||
}
|
}
|
||||||
err = yaml.Unmarshal(bytes, target)
|
if err := yaml.Unmarshal(bytes, target); err != nil {
|
||||||
return true, err
|
return fmt.Errorf("ReadCustomConfig %v: %w", filename, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakePreferences() Preferences {
|
// ReadConfig first unmarshals the defaultConfig which should be the embedded
|
||||||
preferences := loadDefaultPreferences()
|
// default config, and then tries to read the custom config with
|
||||||
exists, err := ReadCustomConfigYml("preferences.yml", &preferences)
|
// ReadCustomConfig. It panics right away if the embedded defaultConfig could
|
||||||
if exists {
|
// not be parsed as yaml as this should never happen except during development.
|
||||||
preferences.YmlError = err
|
// 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) {
|
func (p Preferences) WindowSize() (unit.Dp, unit.Dp) {
|
||||||
|
@ -2,14 +2,12 @@ package gioui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
"gioui.org/widget"
|
"gioui.org/widget"
|
||||||
"gioui.org/widget/material"
|
"gioui.org/widget/material"
|
||||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Theme struct {
|
type Theme struct {
|
||||||
@ -120,19 +118,16 @@ type CursorStyle struct {
|
|||||||
//go:embed theme.yml
|
//go:embed theme.yml
|
||||||
var defaultTheme []byte
|
var defaultTheme []byte
|
||||||
|
|
||||||
func NewTheme() *Theme {
|
// NewTheme returns a new theme and potentially a warning if the theme file was not found or could not be read
|
||||||
var theme Theme
|
func NewTheme() (*Theme, error) {
|
||||||
err := yaml.UnmarshalStrict(defaultTheme, &theme)
|
var ret Theme
|
||||||
if err != nil {
|
warn := ReadConfig(defaultTheme, "theme.yml", &ret)
|
||||||
panic(fmt.Errorf("failed to default theme: %w", err))
|
ret.Material.Shaper = &text.Shaper{}
|
||||||
}
|
ret.Material.Icon.CheckBoxChecked = must(widget.NewIcon(icons.ToggleCheckBox))
|
||||||
ReadCustomConfigYml("theme.yml", &theme)
|
ret.Material.Icon.CheckBoxUnchecked = must(widget.NewIcon(icons.ToggleCheckBoxOutlineBlank))
|
||||||
theme.Material.Shaper = &text.Shaper{}
|
ret.Material.Icon.RadioChecked = must(widget.NewIcon(icons.ToggleRadioButtonChecked))
|
||||||
theme.Material.Icon.CheckBoxChecked = must(widget.NewIcon(icons.ToggleCheckBox))
|
ret.Material.Icon.RadioUnchecked = must(widget.NewIcon(icons.ToggleRadioButtonUnchecked))
|
||||||
theme.Material.Icon.CheckBoxUnchecked = must(widget.NewIcon(icons.ToggleCheckBoxOutlineBlank))
|
return &ret, warn
|
||||||
theme.Material.Icon.RadioChecked = must(widget.NewIcon(icons.ToggleRadioButtonChecked))
|
|
||||||
theme.Material.Icon.RadioUnchecked = must(widget.NewIcon(icons.ToggleRadioButtonUnchecked))
|
|
||||||
return &theme
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func must[T any](ic T, err error) T {
|
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 {
|
func NewTracker(model *tracker.Model) *Tracker {
|
||||||
t := &Tracker{
|
t := &Tracker{
|
||||||
Theme: NewTheme(),
|
|
||||||
OctaveNumberInput: NewNumberInput(model.Octave().Int()),
|
OctaveNumberInput: NewNumberInput(model.Octave().Int()),
|
||||||
InstrumentVoices: NewNumberInput(model.InstrumentVoices().Int()),
|
InstrumentVoices: NewNumberInput(model.InstrumentVoices().Int()),
|
||||||
|
|
||||||
@ -93,15 +92,23 @@ func NewTracker(model *tracker.Model) *Tracker {
|
|||||||
Model: model,
|
Model: model,
|
||||||
|
|
||||||
filePathString: model.FilePath().String(),
|
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.Theme.Material.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||||
t.PopupAlert = NewPopupAlert(model.Alerts())
|
if warn := ReadConfig(defaultPreferences, "preferences.yml", &t.preferences); warn != nil {
|
||||||
if t.preferences.YmlError != nil {
|
model.Alerts().AddAlert(tracker.Alert{
|
||||||
model.Alerts().Add(
|
Priority: tracker.Warning,
|
||||||
fmt.Sprintf("Preferences YML Error: %s", t.preferences.YmlError),
|
Message: warn.Error(),
|
||||||
tracker.Warning,
|
Duration: 10 * time.Second,
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
t.TrackEditor.scrollTable.Focus()
|
t.TrackEditor.scrollTable.Focus()
|
||||||
return t
|
return t
|
||||||
|
Loading…
Reference in New Issue
Block a user