mirror of
https://github.com/vsariola/sointu.git
synced 2025-11-12 21:02:52 -05:00
drafting
This commit is contained in:
parent
09c93420e4
commit
cc03a7cf91
@ -20,6 +20,8 @@ type (
|
|||||||
userPresetsBtn *Clickable
|
userPresetsBtn *Clickable
|
||||||
builtinPresetsBtn *Clickable
|
builtinPresetsBtn *Clickable
|
||||||
clearSearchBtn *Clickable
|
clearSearchBtn *Clickable
|
||||||
|
saveUserPreset *Clickable
|
||||||
|
deleteUserPreset *Clickable
|
||||||
dirList *DragList
|
dirList *DragList
|
||||||
resultList *DragList
|
resultList *DragList
|
||||||
}
|
}
|
||||||
@ -32,6 +34,8 @@ func NewInstrumentPresets(m *tracker.Model) *InstrumentPresets {
|
|||||||
clearSearchBtn: new(Clickable),
|
clearSearchBtn: new(Clickable),
|
||||||
userPresetsBtn: new(Clickable),
|
userPresetsBtn: new(Clickable),
|
||||||
builtinPresetsBtn: new(Clickable),
|
builtinPresetsBtn: new(Clickable),
|
||||||
|
saveUserPreset: new(Clickable),
|
||||||
|
deleteUserPreset: new(Clickable),
|
||||||
dirList: NewDragList(m.PresetDirList().List(), layout.Vertical),
|
dirList: NewDragList(m.PresetDirList().List(), layout.Vertical),
|
||||||
resultList: NewDragList(m.PresetResultList().List(), layout.Vertical),
|
resultList: NewDragList(m.PresetResultList().List(), layout.Vertical),
|
||||||
}
|
}
|
||||||
@ -44,7 +48,9 @@ func (ip *InstrumentPresets) Tags(level int, yield TagYieldFunc) bool {
|
|||||||
yield(level+1, ip.userPresetsBtn) &&
|
yield(level+1, ip.userPresetsBtn) &&
|
||||||
yield(level+1, ip.gmDlsBtn) &&
|
yield(level+1, ip.gmDlsBtn) &&
|
||||||
yield(level, ip.dirList) &&
|
yield(level, ip.dirList) &&
|
||||||
yield(level, ip.resultList)
|
yield(level, ip.resultList) &&
|
||||||
|
yield(level+1, ip.saveUserPreset) &&
|
||||||
|
yield(level+1, ip.deleteUserPreset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ip *InstrumentPresets) update(gtx C) {
|
func (ip *InstrumentPresets) update(gtx C) {
|
||||||
@ -85,6 +91,8 @@ func (ip *InstrumentPresets) layout(gtx C) D {
|
|||||||
gmDlsBtn := ToggleBtn(tr.NoGmDls(), tr.Theme, ip.gmDlsBtn, "No gm.dls", "Exclude presets using gm.dls")
|
gmDlsBtn := ToggleBtn(tr.NoGmDls(), tr.Theme, ip.gmDlsBtn, "No gm.dls", "Exclude presets using gm.dls")
|
||||||
userPresetsFilterBtn := ToggleBtn(tr.UserPresetFilter(), tr.Theme, ip.userPresetsBtn, "User", "Show only user presets")
|
userPresetsFilterBtn := ToggleBtn(tr.UserPresetFilter(), tr.Theme, ip.userPresetsBtn, "User", "Show only user presets")
|
||||||
builtinPresetsFilterBtn := ToggleBtn(tr.BuiltinPresetsFilter(), tr.Theme, ip.builtinPresetsBtn, "Builtin", "Show only builtin presets")
|
builtinPresetsFilterBtn := ToggleBtn(tr.BuiltinPresetsFilter(), tr.Theme, ip.builtinPresetsBtn, "Builtin", "Show only builtin presets")
|
||||||
|
saveUserPresetBtn := ActionIconBtn(tr.SaveAsUserPreset(), tr.Theme, ip.saveUserPreset, icons.ContentSave, "Save instrument as user preset")
|
||||||
|
deleteUserPresetBtn := ActionIconBtn(tr.TryDeleteUserPreset(), tr.Theme, ip.deleteUserPreset, icons.ActionDelete, "Delete user preset")
|
||||||
dirElem := func(gtx C, i int) D {
|
dirElem := func(gtx C, i int) D {
|
||||||
return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Directory, tr.Model.PresetDirList().Value(i)).Layout(gtx)
|
return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Directory, tr.Model.PresetDirList().Value(i)).Layout(gtx)
|
||||||
}
|
}
|
||||||
@ -96,38 +104,72 @@ func (ip *InstrumentPresets) layout(gtx C) D {
|
|||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
dirSurface := func(gtx C) D {
|
dirSurface := func(gtx C) D {
|
||||||
return Surface{Gray: 30, Focus: tr.PatchPanel.TreeFocused(gtx)}.Layout(gtx, dirs)
|
return Surface{Gray: 36, Focus: tr.PatchPanel.TreeFocused(gtx)}.Layout(gtx, dirs)
|
||||||
}
|
}
|
||||||
resultElem := func(gtx C, i int) D {
|
resultElem := func(gtx C, i int) D {
|
||||||
return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Result, tr.Model.PresetResultList().Value(i)).Layout(gtx)
|
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||||
|
n, d, u := tr.Model.PresetResultList().Value(i)
|
||||||
|
if u {
|
||||||
|
ln := Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Results.User, n)
|
||||||
|
ld := Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Results.UserDir, d)
|
||||||
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
|
layout.Rigid(ln.Layout),
|
||||||
|
layout.Rigid(layout.Spacer{Width: 6}.Layout),
|
||||||
|
layout.Rigid(ld.Layout),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Label(tr.Theme, &tr.Theme.InstrumentEditor.Presets.Results.Builtin, n).Layout(gtx)
|
||||||
|
}
|
||||||
|
floatButtons := func(gtx C) D {
|
||||||
|
if tr.Model.DeleteUserPreset().Enabled() {
|
||||||
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
|
layout.Rigid(deleteUserPresetBtn.Layout),
|
||||||
|
layout.Rigid(saveUserPresetBtn.Layout),
|
||||||
|
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
|
layout.Rigid(saveUserPresetBtn.Layout),
|
||||||
|
layout.Rigid(layout.Spacer{Width: 10}.Layout),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
results := func(gtx C) D {
|
results := func(gtx C) D {
|
||||||
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
||||||
fdl := FilledDragList(tr.Theme, ip.resultList)
|
fdl := FilledDragList(tr.Theme, ip.resultList)
|
||||||
dims := fdl.Layout(gtx, resultElem, nil)
|
dims := fdl.Layout(gtx, resultElem, nil)
|
||||||
|
layout.SE.Layout(gtx, floatButtons)
|
||||||
fdl.LayoutScrollBar(gtx)
|
fdl.LayoutScrollBar(gtx)
|
||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
|
resultSurface := func(gtx C) D {
|
||||||
|
return Surface{Gray: 30, Focus: tr.PatchPanel.TreeFocused(gtx)}.Layout(gtx, results)
|
||||||
|
}
|
||||||
bottom := func(gtx C) D {
|
bottom := func(gtx C) D {
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
layout.Rigid(dirSurface),
|
layout.Rigid(dirSurface),
|
||||||
layout.Flexed(1, results),
|
layout.Flexed(1, resultSurface),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// layout
|
// layout
|
||||||
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Start, Spacing: 6}.Layout(gtx,
|
f := func(gtx C) D {
|
||||||
layout.Rigid(ip.layoutSearch),
|
m := gtx.Constraints.Max
|
||||||
layout.Rigid(func(gtx C) D {
|
gtx.Constraints.Max.X = min(gtx.Dp(360), gtx.Constraints.Max.X)
|
||||||
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
|
layout.Flex{Axis: layout.Vertical, Alignment: layout.Start}.Layout(gtx,
|
||||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
layout.Rigid(ip.layoutSearch),
|
||||||
layout.Rigid(userPresetsFilterBtn.Layout),
|
layout.Rigid(func(gtx C) D {
|
||||||
layout.Rigid(builtinPresetsFilterBtn.Layout),
|
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
|
||||||
layout.Rigid(gmDlsBtn.Layout),
|
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||||
)
|
layout.Rigid(userPresetsFilterBtn.Layout),
|
||||||
})
|
layout.Rigid(builtinPresetsFilterBtn.Layout),
|
||||||
}),
|
layout.Rigid(gmDlsBtn.Layout),
|
||||||
layout.Rigid(bottom),
|
)
|
||||||
)
|
})
|
||||||
|
}),
|
||||||
|
layout.Rigid(bottom),
|
||||||
|
)
|
||||||
|
return D{Size: m}
|
||||||
|
}
|
||||||
|
return Surface{Gray: 24, Focus: tr.PatchPanel.TreeFocused(gtx)}.Layout(gtx, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ip *InstrumentPresets) layoutSearch(gtx C) D {
|
func (ip *InstrumentPresets) layoutSearch(gtx C) D {
|
||||||
@ -137,7 +179,7 @@ func (ip *InstrumentPresets) layoutSearch(gtx C) D {
|
|||||||
bg := func(gtx C) D {
|
bg := func(gtx C) D {
|
||||||
rr := gtx.Dp(18)
|
rr := gtx.Dp(18)
|
||||||
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
|
defer clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
|
||||||
paint.Fill(gtx.Ops, tr.Theme.Material.ContrastFg)
|
paint.Fill(gtx.Ops, tr.Theme.InstrumentEditor.Presets.SearchBg)
|
||||||
return D{Size: gtx.Constraints.Min}
|
return D{Size: gtx.Constraints.Min}
|
||||||
}
|
}
|
||||||
// icon, search editor, clear button
|
// icon, search editor, clear button
|
||||||
@ -154,15 +196,16 @@ func (ip *InstrumentPresets) layoutSearch(gtx C) D {
|
|||||||
return btn.Layout(gtx)
|
return btn.Layout(gtx)
|
||||||
}
|
}
|
||||||
w := func(gtx C) D {
|
w := func(gtx C) D {
|
||||||
gtx.Constraints.Max.X = min(gtx.Dp(360), gtx.Constraints.Max.X)
|
|
||||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Rigid(icon),
|
layout.Rigid(icon),
|
||||||
layout.Flexed(1, ed),
|
layout.Flexed(1, ed),
|
||||||
layout.Rigid(clr),
|
layout.Rigid(clr),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return layout.Stack{}.Layout(gtx,
|
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
|
||||||
layout.Expanded(bg),
|
return layout.Stack{}.Layout(gtx,
|
||||||
layout.Stacked(w),
|
layout.Expanded(bg),
|
||||||
)
|
layout.Stacked(w),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,8 +92,13 @@ type Theme struct {
|
|||||||
Error color.NRGBA
|
Error color.NRGBA
|
||||||
}
|
}
|
||||||
Presets struct {
|
Presets struct {
|
||||||
|
SearchBg color.NRGBA
|
||||||
Directory LabelStyle
|
Directory LabelStyle
|
||||||
Result LabelStyle
|
Results struct {
|
||||||
|
Builtin LabelStyle
|
||||||
|
User LabelStyle
|
||||||
|
UserDir LabelStyle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UnitEditor struct {
|
UnitEditor struct {
|
||||||
|
|||||||
@ -191,8 +191,12 @@ instrumenteditor:
|
|||||||
warning: *warningcolor
|
warning: *warningcolor
|
||||||
error: *errorcolor
|
error: *errorcolor
|
||||||
presets:
|
presets:
|
||||||
|
searchbg: { r: 255, g: 255, b: 255, a: 6 }
|
||||||
directory: { textsize: 12, color: *white, maxlines: 1 }
|
directory: { textsize: 12, color: *white, maxlines: 1 }
|
||||||
result: { textsize: 12, color: *white, maxlines: 1 }
|
results:
|
||||||
|
builtin: { textsize: 12, color: *white, maxlines: 1 }
|
||||||
|
user: { textsize: 12, color: *secondarycolor, maxlines: 1 }
|
||||||
|
userdir: { textsize: 12, color: *mediumemphasis, maxlines: 1 }
|
||||||
cursor:
|
cursor:
|
||||||
active: { r: 100, g: 140, b: 255, a: 48 }
|
active: { r: 100, g: 140, b: 255, a: 48 }
|
||||||
activealt: { r: 255, g: 100, b: 140, a: 48 }
|
activealt: { r: 255, g: 100, b: 140, a: 48 }
|
||||||
|
|||||||
@ -316,6 +316,18 @@ func (t *Tracker) showDialog(gtx C) {
|
|||||||
DialogBtn("Close", t.Cancel()),
|
DialogBtn("Close", t.Cancel()),
|
||||||
)
|
)
|
||||||
dialog.Layout(gtx)
|
dialog.Layout(gtx)
|
||||||
|
case tracker.DeleteUserPresetDialog:
|
||||||
|
dialog := MakeDialog(t.Theme, t.DialogState, "Delete user preset?", "Are you sure you want to delete the selected user preset?\nThis action cannot be undone.",
|
||||||
|
DialogBtn("Delete", t.DeleteUserPreset()),
|
||||||
|
DialogBtn("Cancel", t.Cancel()),
|
||||||
|
)
|
||||||
|
dialog.Layout(gtx)
|
||||||
|
case tracker.OverwriteUserPresetDialog:
|
||||||
|
dialog := MakeDialog(t.Theme, t.DialogState, "Overwrite user preset?", "Are you sure you want to overwrite the existing user preset with the same name?",
|
||||||
|
DialogBtn("Save", t.OverwriteUserPreset()),
|
||||||
|
DialogBtn("Cancel", t.Cancel()),
|
||||||
|
)
|
||||||
|
dialog.Layout(gtx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,6 @@ type (
|
|||||||
OrderRows Model // OrderRows is a list of all the order rows, implementing ListData & MutableListData interfaces
|
OrderRows Model // OrderRows is a list of all the order rows, implementing ListData & MutableListData interfaces
|
||||||
NoteRows Model // NoteRows is a list of all the note rows, implementing ListData & MutableListData interfaces
|
NoteRows Model // NoteRows is a list of all the note rows, implementing ListData & MutableListData interfaces
|
||||||
SearchResults Model // SearchResults is a unmutable list of all the search results, implementing ListData interface
|
SearchResults Model // SearchResults is a unmutable list of all the search results, implementing ListData interface
|
||||||
Presets Model // Presets is a unmutable list of all the presets, implementing ListData interface
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Model methods
|
// Model methods
|
||||||
|
|||||||
@ -86,7 +86,7 @@ type (
|
|||||||
|
|
||||||
MIDI MIDIContext
|
MIDI MIDIContext
|
||||||
|
|
||||||
presets PresetSlice
|
presets Presets
|
||||||
presetIndex int
|
presetIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,6 +165,8 @@ const (
|
|||||||
QuitChanges
|
QuitChanges
|
||||||
QuitSaveExplorer
|
QuitSaveExplorer
|
||||||
License
|
License
|
||||||
|
DeleteUserPresetDialog
|
||||||
|
OverwriteUserPresetDialog
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -205,8 +207,8 @@ func NewModel(broker *Broker, synthers []sointu.Synther, midiContext MIDIContext
|
|||||||
TrySend(broker.ToPlayer, any(m.d.Song.Copy())) // we should be non-blocking in the constructor
|
TrySend(broker.ToPlayer, any(m.d.Song.Copy())) // we should be non-blocking in the constructor
|
||||||
m.signalAnalyzer = NewScopeModel(broker, m.d.Song.BPM)
|
m.signalAnalyzer = NewScopeModel(broker, m.d.Song.BPM)
|
||||||
m.updateDeriveData(SongChange)
|
m.updateDeriveData(SongChange)
|
||||||
|
m.presets.load()
|
||||||
m.updateDerivedPresetSearch()
|
m.updateDerivedPresetSearch()
|
||||||
m.loadPresets()
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,10 @@ type (
|
|||||||
Instr sointu.Instrument
|
Instr sointu.Instrument
|
||||||
}
|
}
|
||||||
|
|
||||||
PresetSlice []Preset
|
Presets struct {
|
||||||
|
Presets []Preset
|
||||||
|
Dirs []string
|
||||||
|
}
|
||||||
|
|
||||||
InstrumentPresetYieldFunc func(index int, item string) (ok bool)
|
InstrumentPresetYieldFunc func(index int, item string) (ok bool)
|
||||||
LoadPreset struct {
|
LoadPreset struct {
|
||||||
@ -54,13 +57,19 @@ type (
|
|||||||
ClearPresetSearch Model
|
ClearPresetSearch Model
|
||||||
PresetDirList Model
|
PresetDirList Model
|
||||||
PresetResultList Model
|
PresetResultList Model
|
||||||
|
SaveUserPreset Model
|
||||||
|
TryDeleteUserPreset Model
|
||||||
|
DeleteUserPreset Model
|
||||||
|
|
||||||
|
ConfirmDeleteUserPresetAction Model
|
||||||
|
OverwriteUserPreset Model
|
||||||
|
|
||||||
derivedPresetSearch struct {
|
derivedPresetSearch struct {
|
||||||
|
dir string
|
||||||
dirIndex int
|
dirIndex int
|
||||||
noGmDls bool
|
noGmDls bool
|
||||||
kind PresetKindEnum
|
kind PresetKindEnum
|
||||||
searchStrings []string
|
searchStrings []string
|
||||||
dirs []string
|
|
||||||
results []Preset
|
results []Preset
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,19 +83,19 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (m *Model) updateDerivedPresetSearch() {
|
func (m *Model) updateDerivedPresetSearch() {
|
||||||
|
// reset derived data, keeping the
|
||||||
|
str := m.derived.presetSearch.searchStrings[:0]
|
||||||
|
m.derived.presetSearch = derivedPresetSearch{searchStrings: str, dirIndex: -1}
|
||||||
// parse filters from the search string. in: dir, gmdls: yes/no, kind: builtin/user/all
|
// parse filters from the search string. in: dir, gmdls: yes/no, kind: builtin/user/all
|
||||||
search := strings.TrimSpace(m.d.PresetSearchString)
|
search := strings.TrimSpace(m.d.PresetSearchString)
|
||||||
parts := strings.Fields(search)
|
parts := strings.Fields(search)
|
||||||
// parse parts to see if they contain :
|
// parse parts to see if they contain :
|
||||||
m.derived.presetSearch.dirIndex = 0
|
|
||||||
m.derived.presetSearch.noGmDls = false
|
|
||||||
m.derived.presetSearch.kind = AllPresets
|
|
||||||
m.derived.presetSearch.searchStrings = m.derived.presetSearch.searchStrings[:0]
|
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
if strings.HasPrefix(part, "d:") && len(part) > 2 {
|
if strings.HasPrefix(part, "d:") && len(part) > 2 {
|
||||||
dir := strings.TrimSpace(part[2:])
|
dir := strings.TrimSpace(part[2:])
|
||||||
ind := slices.IndexFunc(m.derived.presetSearch.dirs, func(c string) bool { return c == dir })
|
m.derived.presetSearch.dir = dir
|
||||||
m.derived.presetSearch.dirIndex = max(ind, 0)
|
ind := slices.IndexFunc(m.presets.Dirs, func(c string) bool { return c == dir })
|
||||||
|
m.derived.presetSearch.dirIndex = ind
|
||||||
} else if strings.HasPrefix(part, "g:n") {
|
} else if strings.HasPrefix(part, "g:n") {
|
||||||
m.derived.presetSearch.noGmDls = true
|
m.derived.presetSearch.noGmDls = true
|
||||||
} else if strings.HasPrefix(part, "t:") && len(part) > 2 {
|
} else if strings.HasPrefix(part, "t:") && len(part) > 2 {
|
||||||
@ -103,14 +112,14 @@ func (m *Model) updateDerivedPresetSearch() {
|
|||||||
}
|
}
|
||||||
// update results
|
// update results
|
||||||
m.derived.presetSearch.results = m.derived.presetSearch.results[:0]
|
m.derived.presetSearch.results = m.derived.presetSearch.results[:0]
|
||||||
for _, p := range m.presets {
|
for _, p := range m.presets.Presets {
|
||||||
if m.derived.presetSearch.kind == BuiltinPresets && p.User {
|
if m.derived.presetSearch.kind == BuiltinPresets && p.User {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if m.derived.presetSearch.kind == UserPresets && !p.User {
|
if m.derived.presetSearch.kind == UserPresets && !p.User {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if m.derived.presetSearch.dirIndex > 0 && m.derived.presetSearch.dirIndex < len(m.derived.presetSearch.dirs) && p.Directory != m.derived.presetSearch.dirs[m.derived.presetSearch.dirIndex] {
|
if m.derived.presetSearch.dir != "" && p.Directory != m.derived.presetSearch.dir {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if m.derived.presetSearch.noGmDls && p.NeedsGmDls {
|
if m.derived.presetSearch.noGmDls && p.NeedsGmDls {
|
||||||
@ -130,37 +139,31 @@ func (m *Model) updateDerivedPresetSearch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) loadPresets() {
|
func (m *Presets) load() {
|
||||||
m.presets = nil
|
*m = Presets{}
|
||||||
m.loadPresetsFromFs(instrumentPresetFS, false)
|
|
||||||
if configDir, err := os.UserConfigDir(); err == nil {
|
|
||||||
userPresets := filepath.Join(configDir, "sointu", "presets")
|
|
||||||
m.loadPresetsFromFs(os.DirFS(userPresets), true)
|
|
||||||
}
|
|
||||||
sort.Sort(m.presets)
|
|
||||||
seenDir := make(map[string]bool)
|
seenDir := make(map[string]bool)
|
||||||
for _, p := range m.presets {
|
m.loadPresetsFromFs(instrumentPresetFS, false, seenDir)
|
||||||
seenDir[p.Directory] = true
|
if configDir, err := os.UserConfigDir(); err == nil {
|
||||||
|
userPresets := filepath.Join(configDir, "sointu")
|
||||||
|
m.loadPresetsFromFs(os.DirFS(userPresets), true, seenDir)
|
||||||
}
|
}
|
||||||
dirs := make([]string, 0, len(seenDir))
|
sort.Sort(m)
|
||||||
|
m.Dirs = make([]string, 0, len(seenDir))
|
||||||
for k := range seenDir {
|
for k := range seenDir {
|
||||||
dirs = append(dirs, k)
|
m.Dirs = append(m.Dirs, k)
|
||||||
}
|
}
|
||||||
sort.Strings(dirs)
|
sort.Strings(m.Dirs)
|
||||||
m.derived.presetSearch.dirs = make([]string, 0, len(dirs)+1)
|
|
||||||
m.derived.presetSearch.dirs = append(m.derived.presetSearch.dirs, "---")
|
|
||||||
m.derived.presetSearch.dirs = append(m.derived.presetSearch.dirs, dirs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) loadPresetsFromFs(fsys fs.FS, userDefined bool) {
|
func (m *Presets) loadPresetsFromFs(fsys fs.FS, userDefined bool, seenDir map[string]bool) {
|
||||||
fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
|
fs.WalkDir(fsys, "presets", func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if d.IsDir() {
|
if d.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data, err := fs.ReadFile(instrumentPresetFS, path)
|
data, err := fs.ReadFile(fsys, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -170,13 +173,17 @@ func (m *Model) loadPresetsFromFs(fsys fs.FS, userDefined bool) {
|
|||||||
splitted := splitPath(noExt)
|
splitted := splitPath(noExt)
|
||||||
splitted = splitted[1:] // remove "presets" from the path
|
splitted = splitted[1:] // remove "presets" from the path
|
||||||
instr.Name = splitted[len(splitted)-1]
|
instr.Name = splitted[len(splitted)-1]
|
||||||
|
dir := strings.Join(splitted[:len(splitted)-1], "/")
|
||||||
preset := Preset{
|
preset := Preset{
|
||||||
Directory: strings.Join(splitted[:len(splitted)-1], "/"),
|
Directory: dir,
|
||||||
User: userDefined,
|
User: userDefined,
|
||||||
Instr: instr,
|
Instr: instr,
|
||||||
NeedsGmDls: checkNeedsGmDls(instr),
|
NeedsGmDls: checkNeedsGmDls(instr),
|
||||||
}
|
}
|
||||||
m.presets = append(m.presets, preset)
|
if dir != "" {
|
||||||
|
seenDir[dir] = true
|
||||||
|
}
|
||||||
|
m.Presets = append(m.Presets, preset)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -246,25 +253,6 @@ func (m *BuiltinPresetsFilter) SetValue(val bool) {
|
|||||||
}
|
}
|
||||||
func (m *BuiltinPresetsFilter) Enabled() bool { return true }
|
func (m *BuiltinPresetsFilter) Enabled() bool { return true }
|
||||||
|
|
||||||
func (m *Model) PresetKind() Int { return MakeInt((*PresetKind)(m)) }
|
|
||||||
func (m *PresetKind) Value() int { return int(m.derived.presetSearch.kind) }
|
|
||||||
func (m *PresetKind) SetValue(val int) bool {
|
|
||||||
if int(m.derived.presetSearch.kind) == val {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
m.d.PresetSearchString = removeFilters(m.d.PresetSearchString, "kind:")
|
|
||||||
switch PresetKindEnum(val) {
|
|
||||||
case BuiltinPresets:
|
|
||||||
m.d.PresetSearchString = "kind:builtin " + m.d.PresetSearchString
|
|
||||||
case UserPresets:
|
|
||||||
m.d.PresetSearchString = "kind:user " + m.d.PresetSearchString
|
|
||||||
}
|
|
||||||
(*Model)(m).updateDerivedPresetSearch()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
func (m *PresetKind) Enabled() bool { return true }
|
|
||||||
func (m *PresetKind) Range() IntRange { return IntRange{Min: -1, Max: 1} }
|
|
||||||
|
|
||||||
func (m *Model) ClearPresetSearch() Action { return MakeAction((*ClearPresetSearch)(m)) }
|
func (m *Model) ClearPresetSearch() Action { return MakeAction((*ClearPresetSearch)(m)) }
|
||||||
func (m *ClearPresetSearch) Enabled() bool { return len(m.d.PresetSearchString) > 0 }
|
func (m *ClearPresetSearch) Enabled() bool { return len(m.d.PresetSearchString) > 0 }
|
||||||
func (m *ClearPresetSearch) Do() {
|
func (m *ClearPresetSearch) Do() {
|
||||||
@ -274,24 +262,24 @@ func (m *ClearPresetSearch) Do() {
|
|||||||
|
|
||||||
func (m *Model) PresetDirList() *PresetDirList { return (*PresetDirList)(m) }
|
func (m *Model) PresetDirList() *PresetDirList { return (*PresetDirList)(m) }
|
||||||
func (v *PresetDirList) List() List { return List{v} }
|
func (v *PresetDirList) List() List { return List{v} }
|
||||||
func (m *PresetDirList) Count() int { return len(m.derived.presetSearch.dirs) }
|
func (m *PresetDirList) Count() int { return len(m.presets.Dirs) + 1 }
|
||||||
func (m *PresetDirList) Selected() int { return m.derived.presetSearch.dirIndex }
|
func (m *PresetDirList) Selected() int { return m.derived.presetSearch.dirIndex + 1 }
|
||||||
func (m *PresetDirList) Selected2() int { return m.derived.presetSearch.dirIndex }
|
func (m *PresetDirList) Selected2() int { return m.derived.presetSearch.dirIndex + 1 }
|
||||||
func (m *PresetDirList) SetSelected2(i int) {}
|
func (m *PresetDirList) SetSelected2(i int) {}
|
||||||
func (m *PresetDirList) Value(i int) string {
|
func (m *PresetDirList) Value(i int) string {
|
||||||
if i < 0 || i >= len(m.derived.presetSearch.dirs) {
|
if i < 1 || i > len(m.presets.Dirs) {
|
||||||
return ""
|
return "---"
|
||||||
}
|
}
|
||||||
return m.derived.presetSearch.dirs[i]
|
return m.presets.Dirs[i-1]
|
||||||
}
|
}
|
||||||
func (m *PresetDirList) SetSelected(i int) {
|
func (m *PresetDirList) SetSelected(i int) {
|
||||||
i = min(max(i, 0), len(m.derived.presetSearch.dirs)-1)
|
i = min(max(i, 0), len(m.presets.Dirs))
|
||||||
if i < 0 || i >= len(m.derived.presetSearch.dirs) {
|
if i < 0 || i > len(m.presets.Dirs) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.d.PresetSearchString = removeFilters(m.d.PresetSearchString, "d:")
|
m.d.PresetSearchString = removeFilters(m.d.PresetSearchString, "d:")
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
m.d.PresetSearchString = "d:" + m.derived.presetSearch.dirs[i] + " " + m.d.PresetSearchString
|
m.d.PresetSearchString = "d:" + m.presets.Dirs[i-1] + " " + m.d.PresetSearchString
|
||||||
}
|
}
|
||||||
(*Model)(m).updateDerivedPresetSearch()
|
(*Model)(m).updateDerivedPresetSearch()
|
||||||
}
|
}
|
||||||
@ -304,11 +292,12 @@ func (m *PresetResultList) Selected() int {
|
|||||||
}
|
}
|
||||||
func (m *PresetResultList) Selected2() int { return m.Selected() }
|
func (m *PresetResultList) Selected2() int { return m.Selected() }
|
||||||
func (m *PresetResultList) SetSelected2(i int) {}
|
func (m *PresetResultList) SetSelected2(i int) {}
|
||||||
func (m *PresetResultList) Value(i int) string {
|
func (m *PresetResultList) Value(i int) (name string, dir string, user bool) {
|
||||||
if i < 0 || i >= len(m.derived.presetSearch.results) {
|
if i < 0 || i >= len(m.derived.presetSearch.results) {
|
||||||
return ""
|
return "", "", false
|
||||||
}
|
}
|
||||||
return m.derived.presetSearch.results[i].Instr.Name
|
p := m.derived.presetSearch.results[i]
|
||||||
|
return p.Instr.Name, p.Directory, p.User
|
||||||
}
|
}
|
||||||
func (m *PresetResultList) SetSelected(i int) {
|
func (m *PresetResultList) SetSelected(i int) {
|
||||||
i = min(max(i, 0), len(m.derived.presetSearch.results)-1)
|
i = min(max(i, 0), len(m.derived.presetSearch.results)-1)
|
||||||
@ -331,7 +320,7 @@ func (m *PresetResultList) SetSelected(i int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func removeFilters(str string, prefix string) string {
|
func removeFilters(str string, prefix string) string {
|
||||||
parts := strings.Fields(str)
|
parts := strings.Split(str, " ")
|
||||||
newParts := make([]string, 0, len(parts))
|
newParts := make([]string, 0, len(parts))
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
if !strings.HasPrefix(strings.ToLower(part), prefix) {
|
if !strings.HasPrefix(strings.ToLower(part), prefix) {
|
||||||
@ -341,6 +330,75 @@ func removeFilters(str string, prefix string) string {
|
|||||||
return strings.Join(newParts, " ")
|
return strings.Join(newParts, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) SaveAsUserPreset() Action { return MakeAction((*SaveUserPreset)(m)) }
|
||||||
|
func (m *SaveUserPreset) Enabled() bool {
|
||||||
|
return m.d.InstrIndex >= 0 && m.d.InstrIndex < len(m.d.Song.Patch)
|
||||||
|
}
|
||||||
|
func (m *SaveUserPreset) Do() {
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userPresetsDir := filepath.Join(configDir, "sointu", "presets", m.derived.presetSearch.dir)
|
||||||
|
instr := m.d.Song.Patch[m.d.InstrIndex]
|
||||||
|
fileName := filepath.Join(userPresetsDir, instr.Name+".yaml")
|
||||||
|
// if exists, do not overwrite
|
||||||
|
if _, err := os.Stat(fileName); err == nil {
|
||||||
|
m.dialog = OverwriteUserPresetDialog
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(*Model)(m).OverwriteUserPreset().Do()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) OverwriteUserPreset() Action { return MakeAction((*OverwriteUserPreset)(m)) }
|
||||||
|
func (m *OverwriteUserPreset) Enabled() bool { return true }
|
||||||
|
func (m *OverwriteUserPreset) Do() {
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userPresetsDir := filepath.Join(configDir, "sointu", "presets", m.derived.presetSearch.dir)
|
||||||
|
instr := m.d.Song.Patch[m.d.InstrIndex]
|
||||||
|
fileName := filepath.Join(userPresetsDir, instr.Name+".yaml")
|
||||||
|
os.MkdirAll(userPresetsDir, 0755)
|
||||||
|
data, err := yaml.Marshal(&instr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.WriteFile(fileName, data, 0644)
|
||||||
|
m.dialog = NoDialog
|
||||||
|
(*Model)(m).presets.load()
|
||||||
|
(*Model)(m).updateDerivedPresetSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) TryDeleteUserPreset() Action { return MakeAction((*TryDeleteUserPreset)(m)) }
|
||||||
|
func (m *TryDeleteUserPreset) Do() { m.dialog = DeleteUserPresetDialog }
|
||||||
|
func (m *TryDeleteUserPreset) Enabled() bool {
|
||||||
|
if m.presetIndex < 0 || m.presetIndex >= len(m.derived.presetSearch.results) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return m.derived.presetSearch.results[m.presetIndex].User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) DeleteUserPreset() Action { return MakeAction((*DeleteUserPreset)(m)) }
|
||||||
|
func (m *DeleteUserPreset) Enabled() bool { return (*Model)(m).TryDeleteUserPreset().Enabled() }
|
||||||
|
func (m *DeleteUserPreset) Do() {
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := m.derived.presetSearch.results[m.presetIndex]
|
||||||
|
userPresetsDir := filepath.Join(configDir, "sointu", "presets")
|
||||||
|
if p.Directory != "" {
|
||||||
|
userPresetsDir = filepath.Join(userPresetsDir, p.Directory)
|
||||||
|
}
|
||||||
|
fileName := filepath.Join(userPresetsDir, p.Instr.Name+".yaml")
|
||||||
|
os.Remove(fileName)
|
||||||
|
m.dialog = NoDialog
|
||||||
|
(*Model)(m).presets.load()
|
||||||
|
(*Model)(m).updateDerivedPresetSearch()
|
||||||
|
}
|
||||||
|
|
||||||
// gmDlsEntryMap is a reverse map, to find the index of the GmDlsEntry in the
|
// gmDlsEntryMap is a reverse map, to find the index of the GmDlsEntry in the
|
||||||
// GmDlsEntries list based on the sample offset. Do not modify during runtime.
|
// GmDlsEntries list based on the sample offset. Do not modify during runtime.
|
||||||
var gmDlsEntryMap = make(map[vm.SampleOffset]int)
|
var gmDlsEntryMap = make(map[vm.SampleOffset]int)
|
||||||
@ -462,14 +520,14 @@ func splitPath(path string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PresetSlice) Len() int { return len(p) }
|
func (p Presets) Len() int { return len(p.Presets) }
|
||||||
func (p PresetSlice) Less(i, j int) bool {
|
func (p Presets) Less(i, j int) bool {
|
||||||
if p[i].Directory == p[j].Directory {
|
if p.Presets[i].Directory == p.Presets[j].Directory {
|
||||||
if p[i].Instr.Name == p[j].Instr.Name {
|
if p.Presets[i].Instr.Name == p.Presets[j].Instr.Name {
|
||||||
return p[i].User && !p[j].User
|
return p.Presets[i].User && !p.Presets[j].User
|
||||||
}
|
}
|
||||||
return p[i].Instr.Name < p[j].Instr.Name
|
return p.Presets[i].Instr.Name < p.Presets[j].Instr.Name
|
||||||
}
|
}
|
||||||
return p[i].Directory < p[j].Directory
|
return p.Presets[i].Directory < p.Presets[j].Directory
|
||||||
}
|
}
|
||||||
func (p PresetSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
func (p Presets) Swap(i, j int) { p.Presets[i], p.Presets[j] = p.Presets[j], p.Presets[i] }
|
||||||
|
|||||||
Reference in New Issue
Block a user