diff --git a/CHANGELOG.md b/CHANGELOG.md index 66b8ac1..76797b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added -- Help menu, with a menu item to show the license ([#196][i196]) +- Help menu, with a menu item to show the license in a dialog, and also menu + items to open manual, Github Discussions & Github Issues in a browser + ([#196][i196]) - Show CPU load percentage in the song panel ([#192][i192]) - Theme can be user configured, in theme.yml. This theme.yml should be placed in the usual sointu config directory (i.e. diff --git a/tracker/gioui/keybindings.go b/tracker/gioui/keybindings.go index da32728..35383dd 100644 --- a/tracker/gioui/keybindings.go +++ b/tracker/gioui/keybindings.go @@ -185,6 +185,12 @@ func (t *Tracker) KeyEvent(e key.Event, gtx C) { t.SplitTrack().Do() case "SplitInstrument": t.SplitInstrument().Do() + case "ShowManual": + t.ShowManual().Do() + case "AskHelp": + t.AskHelp().Do() + case "ReportBug": + t.ReportBug().Do() case "ShowLicense": t.ShowLicense().Do() // Booleans diff --git a/tracker/gioui/songpanel.go b/tracker/gioui/songpanel.go index 9de5b45..8b6bdad 100644 --- a/tracker/gioui/songpanel.go +++ b/tracker/gioui/songpanel.go @@ -37,15 +37,15 @@ type SongPanel struct { PlayBar *PlayBar } -func NewSongPanel(model *tracker.Model) *SongPanel { +func NewSongPanel(tr *Tracker) *SongPanel { ret := &SongPanel{ BPM: NewNumericUpDownState(), RowsPerPattern: NewNumericUpDownState(), RowsPerBeat: NewNumericUpDownState(), Step: NewNumericUpDownState(), SongLength: NewNumericUpDownState(), - Scope: NewOscilloscope(model), - MenuBar: NewMenuBar(model), + Scope: NewOscilloscope(tr.Model), + MenuBar: NewMenuBar(tr), PlayBar: NewPlayBar(), WeightingTypeBtn: new(Clickable), @@ -313,7 +313,7 @@ type MenuBar struct { PanicBtn *Clickable } -func NewMenuBar(model *tracker.Model) *MenuBar { +func NewMenuBar(tr *Tracker) *MenuBar { ret := &MenuBar{ Clickables: make([]Clickable, 4), Menus: make([]Menu, 4), @@ -321,29 +321,32 @@ func NewMenuBar(model *tracker.Model) *MenuBar { panicHint: makeHint("Panic", " (%s)", "PanicToggle"), } ret.fileMenuItems = []MenuItem{ - {IconBytes: icons.ContentClear, Text: "New Song", ShortcutText: keyActionMap["NewSong"], Doer: model.NewSong()}, - {IconBytes: icons.FileFolder, Text: "Open Song", ShortcutText: keyActionMap["OpenSong"], Doer: model.OpenSong()}, - {IconBytes: icons.ContentSave, Text: "Save Song", ShortcutText: keyActionMap["SaveSong"], Doer: model.SaveSong()}, - {IconBytes: icons.ContentSave, Text: "Save Song As...", ShortcutText: keyActionMap["SaveSongAs"], Doer: model.SaveSongAs()}, - {IconBytes: icons.ImageAudiotrack, Text: "Export Wav...", ShortcutText: keyActionMap["ExportWav"], Doer: model.Export()}, + {IconBytes: icons.ContentClear, Text: "New Song", ShortcutText: keyActionMap["NewSong"], Doer: tr.NewSong()}, + {IconBytes: icons.FileFolder, Text: "Open Song", ShortcutText: keyActionMap["OpenSong"], Doer: tr.OpenSong()}, + {IconBytes: icons.ContentSave, Text: "Save Song", ShortcutText: keyActionMap["SaveSong"], Doer: tr.SaveSong()}, + {IconBytes: icons.ContentSave, Text: "Save Song As...", ShortcutText: keyActionMap["SaveSongAs"], Doer: tr.SaveSongAs()}, + {IconBytes: icons.ImageAudiotrack, Text: "Export Wav...", ShortcutText: keyActionMap["ExportWav"], Doer: tr.Export()}, } if canQuit { - ret.fileMenuItems = append(ret.fileMenuItems, MenuItem{IconBytes: icons.ActionExitToApp, Text: "Quit", ShortcutText: keyActionMap["Quit"], Doer: model.RequestQuit()}) + ret.fileMenuItems = append(ret.fileMenuItems, MenuItem{IconBytes: icons.ActionExitToApp, Text: "Quit", ShortcutText: keyActionMap["Quit"], Doer: tr.RequestQuit()}) } ret.editMenuItems = []MenuItem{ - {IconBytes: icons.ContentUndo, Text: "Undo", ShortcutText: keyActionMap["Undo"], Doer: model.Undo()}, - {IconBytes: icons.ContentRedo, Text: "Redo", ShortcutText: keyActionMap["Redo"], Doer: model.Redo()}, - {IconBytes: icons.ImageCrop, Text: "Remove unused data", ShortcutText: keyActionMap["RemoveUnused"], Doer: model.RemoveUnused()}, + {IconBytes: icons.ContentUndo, Text: "Undo", ShortcutText: keyActionMap["Undo"], Doer: tr.Undo()}, + {IconBytes: icons.ContentRedo, Text: "Redo", ShortcutText: keyActionMap["Redo"], Doer: tr.Redo()}, + {IconBytes: icons.ImageCrop, Text: "Remove unused data", ShortcutText: keyActionMap["RemoveUnused"], Doer: tr.RemoveUnused()}, } - for input := range model.MIDI.InputDevices { + for input := range tr.MIDI.InputDevices { ret.midiMenuItems = append(ret.midiMenuItems, MenuItem{ IconBytes: icons.ImageControlPoint, Text: input.String(), - Doer: model.SelectMidiInput(input), + Doer: tr.SelectMidiInput(input), }) } ret.helpMenuItems = []MenuItem{ - {IconBytes: icons.ActionCopyright, Text: "License", ShortcutText: keyActionMap["ShowLicense"], Doer: model.ShowLicense()}, + {IconBytes: icons.AVLibraryBooks, Text: "Manual", ShortcutText: keyActionMap["ShowManual"], Doer: tr.ShowManual()}, + {IconBytes: icons.ActionHelp, Text: "Ask help", ShortcutText: keyActionMap["AskHelp"], Doer: tr.AskHelp()}, + {IconBytes: icons.ActionBugReport, Text: "Report bug", ShortcutText: keyActionMap["ReportBug"], Doer: tr.ReportBug()}, + {IconBytes: icons.ActionCopyright, Text: "License", ShortcutText: keyActionMap["ShowLicense"], Doer: tr.ShowLicense()}, } return ret } diff --git a/tracker/gioui/tracker.go b/tracker/gioui/tracker.go index da1bc12..e32578b 100644 --- a/tracker/gioui/tracker.go +++ b/tracker/gioui/tracker.go @@ -4,7 +4,9 @@ import ( "fmt" "image" "io" + "os/exec" "path/filepath" + "runtime" "time" "gioui.org/app" @@ -58,6 +60,10 @@ type ( *tracker.Model } + ShowManual Tracker + AskHelp Tracker + ReportBug Tracker + C = layout.Context D = layout.Dimensions ) @@ -85,7 +91,6 @@ func NewTracker(model *tracker.Model) *Tracker { InstrumentEditor: NewInstrumentEditor(model), OrderEditor: NewOrderEditor(model), TrackEditor: NewNoteEditor(model), - SongPanel: NewSongPanel(model), Zoom: 6, @@ -93,6 +98,7 @@ func NewTracker(model *tracker.Model) *Tracker { filePathString: model.FilePath(), } + t.SongPanel = NewSongPanel(t) t.KeyNoteMap = MakeKeyboard[key.Name](model.Broker()) t.PopupAlert = NewPopupAlert(model.Alerts()) var warn error @@ -352,3 +358,32 @@ func (t *Tracker) layoutTop(gtx layout.Context) layout.Dimensions { }, ) } + +func (t *Tracker) ShowManual() tracker.Action { return tracker.MakeEnabledAction((*ShowManual)(t)) } +func (t *ShowManual) Do() { (*Tracker)(t).openUrl("https://github.com/vsariola/sointu/wiki") } + +func (t *Tracker) AskHelp() tracker.Action { return tracker.MakeEnabledAction((*AskHelp)(t)) } +func (t *AskHelp) Do() { + (*Tracker)(t).openUrl("https://github.com/vsariola/sointu/discussions/categories/help-needed") +} + +func (t *Tracker) ReportBug() tracker.Action { return tracker.MakeEnabledAction((*ReportBug)(t)) } +func (t *ReportBug) Do() { (*Tracker)(t).openUrl("https://github.com/vsariola/sointu/issues") } + +func (t *Tracker) openUrl(url string) { + var err error + // following https://gist.github.com/hyg/9c4afcd91fe24316cbf0 + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", url).Start() + default: + err = fmt.Errorf("unsupported platform for opening urls %s", runtime.GOOS) + } + if err != nil { + t.Alerts().Add(err.Error(), tracker.Error) + } +}