diff --git a/CHANGELOG.md b/CHANGELOG.md index d57c9b7..453e1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ([#77][i77]) - User can define own keybindings in `os.UserConfigDir()/sointu/keybindings.yml` ([#94][i94], [#151][i151]) +- User can define preferred window size in + `os.UserConfigDir()/sointu/preferences.yml` ([#184][i184]) - A small number above the instrument name identifies the MIDI channel / instrument number, with numbering starting from 1 ([#154][i154]) - The filter unit frequency parameter is displayed in Hz, corresponding roughly diff --git a/tracker/gioui/keyevent.go b/tracker/gioui/keyevent.go index b7e2f96..eaa990c 100644 --- a/tracker/gioui/keyevent.go +++ b/tracker/gioui/keyevent.go @@ -3,8 +3,6 @@ package gioui import ( _ "embed" "fmt" - "os" - "path/filepath" "strconv" "strings" @@ -28,16 +26,7 @@ var keyActionMap = map[KeyAction]string{} // holds an informative string of the func loadCustomKeyBindings() []KeyBinding { var keyBindings []KeyBinding - configDir, err := os.UserConfigDir() - if err != nil { - return nil - } - path := filepath.Join(configDir, "sointu", "keybindings.yml") - bytes, err := os.ReadFile(path) - if err != nil { - return nil - } - err = yaml.Unmarshal(bytes, &keyBindings) + _, err := ReadCustomConfigYml("keybindings.yml", &keyBindings) if err != nil { return nil } diff --git a/tracker/gioui/preferences.go b/tracker/gioui/preferences.go new file mode 100644 index 0000000..c66e95c --- /dev/null +++ b/tracker/gioui/preferences.go @@ -0,0 +1,65 @@ +package gioui + +import ( + _ "embed" + "fmt" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" + + "gioui.org/unit" +) + +type ( + Preferences struct { + Window WindowPreferences + YmlError error + } + + WindowPreferences struct { + Width int + Height int + Maximized bool `yaml:",omitempty"` + } +) + +//go:embed preferences.yml +var defaultPreferencesYaml []byte + +func loadDefaultPreferences() Preferences { + var preferences Preferences + err := yaml.Unmarshal(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) { + configDir, err := os.UserConfigDir() + if err != nil { + return false, err + } + path := filepath.Join(configDir, "sointu", filename) + bytes, err2 := os.ReadFile(path) + if err2 != nil { + return false, err2 + } + err = yaml.Unmarshal(bytes, target) + return true, err +} + +func NewPreferences() Preferences { + preferences := loadDefaultPreferences() + exists, err := ReadCustomConfigYml("preferences.yml", &preferences) + if exists { + preferences.YmlError = err + } + return preferences +} + +func (p Preferences) WindowSize() (unit.Dp, unit.Dp) { + return unit.Dp(p.Window.Width), unit.Dp(p.Window.Height) +} diff --git a/tracker/gioui/preferences.yml b/tracker/gioui/preferences.yml new file mode 100644 index 0000000..bdd80c0 --- /dev/null +++ b/tracker/gioui/preferences.yml @@ -0,0 +1,4 @@ +window: + width: 800 + height: 600 + maximized: false diff --git a/tracker/gioui/tracker.go b/tracker/gioui/tracker.go index f104924..de8b430 100644 --- a/tracker/gioui/tracker.go +++ b/tracker/gioui/tracker.go @@ -18,7 +18,6 @@ import ( "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/text" - "gioui.org/unit" "gioui.org/widget/material" "gioui.org/x/explorer" "github.com/vsariola/sointu/tracker" @@ -51,8 +50,9 @@ type ( filePathString tracker.String - quitWG sync.WaitGroup - execChan chan func() + quitWG sync.WaitGroup + execChan chan func() + preferences Preferences *tracker.Model } @@ -89,9 +89,16 @@ func NewTracker(model *tracker.Model) *Tracker { Model: model, filePathString: model.FilePath().String(), + preferences: NewPreferences(), } t.Theme.Shaper = text.NewShaper(text.WithCollection(fontCollection)) t.PopupAlert = NewPopupAlert(model.Alerts(), t.Theme.Shaper) + if t.preferences.YmlError != nil { + model.Alerts().Add( + fmt.Sprintf("Preferences YML Error: %s", t.preferences.YmlError), + tracker.Warning, + ) + } t.Theme.Palette.Fg = primaryColor t.Theme.Palette.ContrastFg = black t.TrackEditor.scrollTable.Focus() @@ -101,9 +108,7 @@ func NewTracker(model *tracker.Model) *Tracker { func (t *Tracker) Main() { titleFooter := "" - w := new(app.Window) - w.Option(app.Title("Sointu Tracker")) - w.Option(app.Size(unit.Dp(800), unit.Dp(600))) + w := t.newWindow() t.InstrumentEditor.Focus() recoveryTicker := time.NewTicker(time.Second * 30) t.Explorer = explorer.NewExplorer(w) @@ -127,9 +132,7 @@ func (t *Tracker) Main() { } if !t.Quitted() { // TODO: uh oh, there's no way of canceling the destroyevent in gioui? so we create a new window just to show the dialog - w = new(app.Window) - w.Option(app.Title("Sointu Tracker")) - w.Option(app.Size(unit.Dp(800), unit.Dp(600))) + w = t.newWindow() t.Explorer = explorer.NewExplorer(w) go eventLoop(w, events, acks) } @@ -165,6 +168,16 @@ func (t *Tracker) Main() { t.quitWG.Done() } +func (t *Tracker) newWindow() *app.Window { + w := new(app.Window) + w.Option(app.Title("Sointu Tracker")) + w.Option(app.Size(t.preferences.WindowSize())) + if t.preferences.Window.Maximized { + w.Option(app.Fullscreen.Option()) + } + return w +} + func eventLoop(w *app.Window, events chan<- event.Event, acks <-chan struct{}) { // Iterate window events, sending each to the old event loop and waiting for // a signal that processing is complete before iterating again.